AppCompat 工具栏上的 MenuItem 着色

Posted

技术标签:

【中文标题】AppCompat 工具栏上的 MenuItem 着色【英文标题】:MenuItem tinting on AppCompat Toolbar 【发布时间】:2015-01-02 23:50:42 【问题描述】:

当我将 AppCompat 库中的可绘制对象用于我的 Toolbar 菜单项时,着色按预期工作。像这样:

<item
    android:id="@+id/action_clear"
    android:icon="@drawable/abc_ic_clear_mtrl_alpha"  <-- from AppCompat
    android:title="@string/clear" />

但是,如果我使用自己的可绘制对象,甚至将 AppCompat 库中的可绘制对象复制到我自己的项目中,它根本不会着色。

<item
    android:id="@+id/action_clear"
    android:icon="@drawable/abc_ic_clear_mtrl_alpha_copy"  <-- copy from AppCompat
    android:title="@string/clear" />

AppCompatToolbar 中是否有一些特殊的魔法,只为该库中的可绘制对象着色?有什么方法可以让它与我自己的可绘制对象一起使用?

使用 compileSdkVersion = 21targetSdkVersion = 21 在 API 级别 19 设备上运行此程序,并使用 AppCompat 中的所有内容

abc_ic_clear_mtrl_alpha_copy 是来自AppCompatabc_ic_clear_mtrl_alpha png 的精确副本

编辑:

着色基于我在主题中为android:textColorPrimary 设置的值。

例如&lt;item name="android:textColorPrimary"&gt;#00FF00&lt;/item&gt; 会给我一个绿色的色调。

截图

使用 AppCompat 中的 drawable 可以按预期进行着色

着色不适用于从 AppCompat 复制的可绘制对象

【问题讨论】:

两种样式有相同的父级?如果你用自己的扩展顶部样式怎么办? 样式没有区别。唯一的区别是drawable,它们都是.png文件 drawable 看起来像是代码中原始 AppCombat drawable 的精确副本? 它们是我复制的 png 文件。它们完全相同。 那么,如果你的代码具有相同的样式和相同的图像,那么你的代码与原始代码到底有什么不同呢? 【参考方案1】:

在新的支持库 v22.1 之后,你可以使用类似这样的东西:

  @Override
    public boolean onCreateOptionsMenu(Menu menu) 
        getMenuInflater().inflate(R.menu.menu_home, menu);
        Drawable drawable = menu.findItem(R.id.action_clear).getIcon();

        drawable = DrawableCompat.wrap(drawable);
        DrawableCompat.setTint(drawable, ContextCompat.getColor(this,R.color.textColorPrimary));
        menu.findItem(R.id.action_clear).setIcon(drawable);
        return true;
    

【讨论】:

我想说,在这种情况下,旧的setColorFilter() 更可取。 @mvai ,为什么 setColorFilter() 更受欢迎? @wilddev 简洁。当您可以使用 menu.findItem().getIcon().setColorFilter() 时,为什么还要打扰支持 DrawableCompat 类?一个衬里和透明。 当您将整个逻辑抽象为您自己的 TintingUtils.tintMenuIcon(...) 方法或您想调用的任何方法时,单线参数是无关紧要的。如果您将来需要更改或调整逻辑,请在一个地方进行,而不是在整个应用程序中进行。 不需要不同的逻辑来着色,也不需要以后不改变。【参考方案2】:

MenuItem 上设置ColorFilter(色调)很简单。这是一个例子:

Drawable drawable = menuItem.getIcon();
if (drawable != null) 
    // If we don't mutate the drawable, then all drawable's with this id will have a color
    // filter applied to it.
    drawable.mutate();
    drawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
    drawable.setAlpha(alpha);


如果你想支持不同的主题并且你不想为了颜色或透明度而有额外的副本,上面的代码非常有用。

Click here 用于帮助程序类,用于在菜单中的所有可绘制对象上设置 ColorFilter,包括溢出图标。

onCreateOptionsMenu(Menu menu) 中,只需在为您的菜单充气后拨打MenuColorizer.colorMenu(this, menu, color); 即可;瞧;你的图标是有色的。

【讨论】:

我一直在用头撞我的桌子,试图弄清楚为什么我的所有图标都被着色了,感谢您对 drawable.mutate() 的提醒!【参考方案3】:

app:iconTint 属性在支持库的SupportMenuInflater 中实现(至少在 28.0.0 中)。

使用 API 15 及更高版本成功测试。

菜单资源文件:

<menu
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
        android:id="@+id/menu_settings"
        android:icon="@drawable/ic_settings_white_24dp"
        app:iconTint="?attr/appIconColorEnabled"        <!-- using app name space instead of android -->
        android:menuCategory="system"
        android:orderInCategory="1"
        android:title="@string/menu_settings"
        app:showAsAction="never"
        />

    <item
        android:id="@+id/menu_themes"
        android:icon="@drawable/ic_palette_white_24dp"
        app:iconTint="?attr/appIconColorEnabled"
        android:menuCategory="system"
        android:orderInCategory="2"
        android:title="@string/menu_themes"
        app:showAsAction="never"
        />

    <item
        android:id="@+id/action_help"
        android:icon="@drawable/ic_help_white_24dp"
        app:iconTint="?attr/appIconColorEnabled"
        android:menuCategory="system"
        android:orderInCategory="3"
        android:title="@string/menu_help"
        app:showAsAction="never"
        />

</menu>

(在这种情况下,?attr/appIconColorEnabled 是应用主题中的自定义颜色属性,图标资源是矢量可绘制对象。)

【讨论】:

这应该是新接受的答案!另外,请注意 android:iconTintandroid:iconTintMode 不起作用,但前缀为 app: 而不是 android: 就像一个魅力(在我自己的矢量绘图中,API >=21) 如果以编程方式调用:请注意,如果菜单不是 SupportMenu(如 MenuBuilder),SupportMenuInflater 将不会应用任何自定义逻辑,它只会退回到常规 MenuInflater 在这种情况下,使用AppCompatActivity.startSupportActionMode(callback) 并且来自androidx.appcompat 的适当支持实现将被传递到回调中。 这是正确的答案!【参考方案4】:

因为如果你看一下 AppCompat 中 TintManager 的源代码,你会看到:

/**
 * Drawables which should be tinted with the value of @code R.attr.colorControlNormal,
 * using the default mode.
 */
private static final int[] TINT_COLOR_CONTROL_NORMAL = 
        R.drawable.abc_ic_ab_back_mtrl_am_alpha,
        R.drawable.abc_ic_go_search_api_mtrl_alpha,
        R.drawable.abc_ic_search_api_mtrl_alpha,
        R.drawable.abc_ic_commit_search_api_mtrl_alpha,
        R.drawable.abc_ic_clear_mtrl_alpha,
        R.drawable.abc_ic_menu_share_mtrl_alpha,
        R.drawable.abc_ic_menu_copy_mtrl_am_alpha,
        R.drawable.abc_ic_menu_cut_mtrl_alpha,
        R.drawable.abc_ic_menu_selectall_mtrl_alpha,
        R.drawable.abc_ic_menu_paste_mtrl_am_alpha,
        R.drawable.abc_ic_menu_moreoverflow_mtrl_alpha,
        R.drawable.abc_ic_voice_search_api_mtrl_alpha,
        R.drawable.abc_textfield_search_default_mtrl_alpha,
        R.drawable.abc_textfield_default_mtrl_alpha
;

/**
 * Drawables which should be tinted with the value of @code R.attr.colorControlActivated,
 * using the default mode.
 */
private static final int[] TINT_COLOR_CONTROL_ACTIVATED = 
        R.drawable.abc_textfield_activated_mtrl_alpha,
        R.drawable.abc_textfield_search_activated_mtrl_alpha,
        R.drawable.abc_cab_background_top_mtrl_alpha
;

/**
 * Drawables which should be tinted with the value of @code android.R.attr.colorBackground,
 * using the @link android.graphics.PorterDuff.Mode#MULTIPLY mode.
 */
private static final int[] TINT_COLOR_BACKGROUND_MULTIPLY = 
        R.drawable.abc_popup_background_mtrl_mult,
        R.drawable.abc_cab_background_internal_bg,
        R.drawable.abc_menu_hardkey_panel_mtrl_mult
;

/**
 * Drawables which should be tinted using a state list containing values of
 * @code R.attr.colorControlNormal and @code R.attr.colorControlActivated
 */
private static final int[] TINT_COLOR_CONTROL_STATE_LIST = 
        R.drawable.abc_edit_text_material,
        R.drawable.abc_tab_indicator_material,
        R.drawable.abc_textfield_search_material,
        R.drawable.abc_spinner_mtrl_am_alpha,
        R.drawable.abc_btn_check_material,
        R.drawable.abc_btn_radio_material
;

/**
 * Drawables which contain other drawables which should be tinted. The child drawable IDs
 * should be defined in one of the arrays above.
 */
private static final int[] CONTAINERS_WITH_TINT_CHILDREN = 
        R.drawable.abc_cab_background_top_material
;

这几乎意味着他们将特定的 resourceId 列入白名单以进行着色。

但我想你总能看到他们如何为这些图像着色并做同样的事情。就像在 drawable 上设置 ColorFilter 一样简单。

【讨论】:

呃,这就是我害怕的。我在 SDK 中没有找到 AppCompat 的源代码,这就是我自己没有找到这部分的原因。猜猜我将不得不在 googlesource.com 上浏览。谢谢! 我知道这是个无关紧要的问题,但为什么会有白名单?如果它可以用这些图标着色,那为什么我们不能给我们自己的图标着色呢?另外,当您遗漏最重要的事情之一时,使几乎所有东西都向后兼容(与 AppCompat)有什么意义:拥有操作栏图标(具有自定义颜色)。 Google 的问题跟踪器中有一个已标记为已修复的问题,但它对我不起作用,但您可以在这里跟踪它:issuetracker.google.com/issues/37127128 他们声称这是固定的,但事实并非如此。天哪,我讨厌 Android 主题引擎、AppCompat 以及与之相关的所有废话。它仅适用于“Github repo browser”示例应用程序。【参考方案5】:

我个人更喜欢这个link的方法

使用以下内容创建 XML 布局:

<?xml version="1.0" encoding="utf-8"?>
<bitmap
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:src="@drawable/ic_action_something"
    android:tint="@color/color_action_icons_tint"/>

并从您的菜单中引用此可绘制对象:

<item
    android:id="@+id/option_menu_item_something"
    android:icon="@drawable/ic_action_something_tined"

【讨论】:

虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接的答案可能会失效。 感谢您的评论,我已编辑问题。 @tomloprod 这是我的首选解决方案。但是,重要的是要注意,目前,当您使用新的矢量可绘制类型作为源时,此解决方案似乎不起作用。 @haagmm 这个解决方案需要 API >= 21。它也适用于向量。 它不应该与向量一起使用,根标签是bitmap。还有其他方法可以为矢量着色。也许有人也可以在这里添加矢量着色...【参考方案6】:

此线程中的大多数解决方案要么使用更新的 API,要么使用反射,要么使用密集的视图查找来获取膨胀的 MenuItem

但是,有一种更优雅的方法可以做到这一点。您需要一个自定义工具栏,因为您的“应用自定义色调”用例不能很好地与公共样式/主题 API 配合使用。

public class MyToolbar extends Toolbar 
    ... some constructors, extracting mAccentColor from AttrSet, etc

    @Override
    public void inflateMenu(@MenuRes int resId) 
        super.inflateMenu(resId);
        Menu menu = getMenu();
        for (int i = 0; i < menu.size(); i++) 
            MenuItem item = menu.getItem(i);
            Drawable icon = item.getIcon();
            if (icon != null) 
                item.setIcon(applyTint(icon));
            
        
    
    void applyTint(Drawable icon)
        icon.setColorFilter(
           new PorterDuffColorFilter(mAccentColor, PorterDuff.Mode.SRC_IN)
        );
    


只要确保调用 Activity/Fragment 代码即可:

toolbar.inflateMenu(R.menu.some_menu);
toolbar.setOnMenuItemClickListener(someListener);

没有反射,没有视图查找,也没有那么多代码,对吧?

现在你可以忽略可笑的onCreateOptionsMenu/onOptionsItemSelected

【讨论】:

从技术上讲,您正在执行视图查找。您正在迭代视图并确保它们不为空。 ;) 在某种程度上你绝对是对的 :-) 尽管如此,Menu#getItem() 在工具栏中的复杂度是 O(1),因为项目存储在 ArrayList 中。这与 View#findViewById 遍历(我在回答中称为 view lookup )不同,其复杂性远非恒定:-) 同意,其实我也做过非常类似的事情。这么多年过去了,Android 还没有简化这一切,我仍然感到震惊…… 如何使用这种方法更改溢出图标和汉堡图标的颜色?【参考方案7】:

这是我使用的解决方案;您可以在 onPrepareOptionsMenu() 或同等位置之后调用它。 mutate() 的原因是如果您碰巧在多个位置使用图标;如果没有变异,它们都会呈现相同的色调。

public class MenuTintUtils 
    public static void tintAllIcons(Menu menu, final int color) 
        for (int i = 0; i < menu.size(); ++i) 
            final MenuItem item = menu.getItem(i);
            tintMenuItemIcon(color, item);
            tintShareIconIfPresent(color, item);
        
    

    private static void tintMenuItemIcon(int color, MenuItem item) 
        final Drawable drawable = item.getIcon();
        if (drawable != null) 
            final Drawable wrapped = DrawableCompat.wrap(drawable);
            drawable.mutate();
            DrawableCompat.setTint(wrapped, color);
            item.setIcon(drawable);
        
    

    private static void tintShareIconIfPresent(int color, MenuItem item) 
        if (item.getActionView() != null) 
            final View actionView = item.getActionView();
            final View expandActivitiesButton = actionView.findViewById(R.id.expand_activities_button);
            if (expandActivitiesButton != null) 
                final ImageView image = (ImageView) expandActivitiesButton.findViewById(R.id.image);
                if (image != null) 
                    final Drawable drawable = image.getDrawable();
                    final Drawable wrapped = DrawableCompat.wrap(drawable);
                    drawable.mutate();
                    DrawableCompat.setTint(wrapped, color);
                    image.setImageDrawable(drawable);
                
            
        
    

这不会处理溢出问题,但为此,您可以这样做:

布局:

<android.support.v7.widget.Toolbar
    ...
    android:theme="@style/myToolbarTheme" />

样式:

<style name="myToolbarTheme">
        <item name="colorControlNormal">#FF0000</item>
</style>

这适用于 appcompat v23.1.0。

【讨论】:

【参考方案8】:

这对我有用:

override fun onCreateOptionsMenu(menu: Menu?): Boolean 

        val inflater = menuInflater
        inflater.inflate(R.menu.player_menu, menu)

        //tinting menu item:
        val typedArray = theme.obtainStyledAttributes(IntArray(1)  android.R.attr.textColorSecondary )
        val textColor = typedArray.getColor(0, 0)
        typedArray.recycle()

        val item = menu?.findItem(R.id.action_chapters)
        val icon = item?.icon

        icon?.setColorFilter(textColor, PorterDuff.Mode.SRC_IN);
        item?.icon = icon
        return true
    

或者你可以在drawable xml中使用tint:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:
    android:
    android:tint="?android:textColorSecondary"
    android:viewportWidth="384"
    android:viewportHeight="384">
    <path
        android:fillColor="#FF000000"

        android:pathData="M0,277.333h384v42.667h-384z" />
    <path
        android:fillColor="#FF000000"
        android:pathData="M0,170.667h384v42.667h-384z" />
    <path
        android:fillColor="#FF000000"
        android:pathData="M0,64h384v42.667h-384z" />
</vector>

【讨论】:

【参考方案9】:
@Override
public boolean onCreateOptionsMenu(Menu menu) 
    getMenuInflater().inflate(R.menu.menu_home, menu);
    //One item tint
    menu.get(itemId).getIcon().setTint(Color);
   //or all
    for(int i=0;i<menu.size();i++)
    menu.get(i).getIcon().setTint(Color);
    
    return true;

【讨论】:

您的答案可以通过额外的支持信息得到改进。请edit 添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。你可以找到更多关于如何写好答案的信息in the help center。

以上是关于AppCompat 工具栏上的 MenuItem 着色的主要内容,如果未能解决你的问题,请参考以下文章

后退按钮不折叠 SearchView

Android 上的工具栏图标着色

如何更改 Android 上选项菜单上的 MenuItem?

GWT MenuItem 上的复选框

Failed to resolve:com.android.support:appcompat-v7第一次运行安卓程序报错

Android Studio 上的向后兼容性 (AppCompat)