AppCompat工具栏上的MenuItem着色


93

当我将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" />

在该AppCompat Toolbar库中仅对可绘制对象进行着色中是否有一些特殊的魔术?有什么办法可以将其与我自己的可绘制对象一起使用?

使用compileSdkVersion = 21和在API Level 19设备上运行此命令targetSdkVersion = 21,还使用AppCompat

abc_ic_clear_mtrl_alpha_copyabc_ic_clear_mtrl_alpha来自的png 的精确副本AppCompat

编辑:

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

例如<item name="android:textColorPrimary">#00FF00</item>会给我绿色的颜色。

屏幕截图

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

着色不适用于从AppCompat复制的drawable 着色不适用于从AppCompat复制的drawable


两种样式具有相同的父项?如果您用自己的样式扩展顶级样式怎么办?
G_V 2014年

样式没有区别。唯一的区别是可绘制对象,它们都是.png文件
严重的2014年

可绘制对象看起来像代码中原始AppCombat可绘制对象的精确副本吗?
2014年

它们是我复制的png文件。他们是完全一样的。
希腊

因此,如果代码具有相同的样式和图像,那么您的代码与原始代码到底有何不同?
2014年

Answers:


31

因为如果您查看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
};

这几乎意味着他们将特定的resourceIds列入了白名单。

但是我想您总是可以看到它们如何着色这些图像并执行相同的操作。就像在可绘制对象上设置ColorFilter一样简单。


gh,那是我所担心的。我没有在SDK中找到AppCompat的源代码,这就是为什么我自己没有找到这部分的原因。猜猜我将不得不在googlesource.com上浏览。谢谢!
格林威治

8
我知道这是一个切线问题,但是为什么会有白名单?如果它可以对这些图标进行着色,那么为什么我们不能对自己的图标进行着色呢?另外,当您忽略最重要的事情之一:拥有操作栏图标(具有自定义颜色)时,使几乎所有内容都向后兼容(与AppCompat)有什么意义?
Zsolt Safrany 2015年

1
Google的问题跟踪器中有一个已标记为已修复的问题,但对我而言不起作用,但是您可以在此处跟踪它:issuetracker.google.com/issues/37127128
niknetniko

他们声称这是固定的,但事实并非如此。天哪,我讨厌Android主题引擎,AppCompat和与其相关的所有废话。它仅适用于“ Github存储库浏览器”示例应用程序。
Martin Marconcini

97

在新的支持库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;
    }

1
我想说,在这种情况下,旧setColorFilter()方法更可取。
natario

@mvai,为什么setColorFilter()更可取?
wilddev 2015年

4
@wilddev简洁。为什么可以在进入menu.findItem()。getIcon()。setColorFilter()时打扰支持DrawableCompat的类?一线清晰。
natario

4
当您将整个逻辑抽象为自己的TintingUtils.tintMenuIcon(...)方法或任何您想调用的方法时,单线参数就无关紧要。如果将来需要更改或调整逻辑,则可以在一个地方而不是整个应用程序中进行。
Dan Dar3

1
这太棒了!
伊斯兰教徒伊斯兰教'18

82

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

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);
}

如果您想支持不同的主题,并且不想只为颜色或透明度添加多余的副本,以上代码将非常有帮助。

单击此处以获取帮助程序类,以ColorFilter在菜单中的所有可绘制对象上设置a,包括溢出图标。

在夸大菜单和瞧之后的onCreateOptionsMenu(Menu menu)通话中MenuColorizer.colorMenu(this, menu, color);;您的图标有色。


谢谢,一定会尝试一下!
2015年

3
我一直把头撞在桌子上,试图弄清楚为什么我所有的图标都被着色了,谢谢您对drawable.mutate()的注意!
Scott Cooper

48

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是应用程序主题中的自定义颜色属性,图标资源是矢量可绘制对象。)


5
这应该是新接受的答案!另外,请注意android:iconTint并且android:iconTintMode不起作用,而是使用app:而不是android:像魅力一样工作(在我自己的矢量可绘制对象上,API> = 21)作为前缀
Sebastiaan Alvarez Rodriguez

如果以编程方式调用:请注意,SupportMenuInflater如果菜单不是一个SupportMenu类似菜单,它将不会应用任何自定义逻辑MenuBuilder,它只会退回到常规菜单MenuInflater
geekley

在这种情况下,use AppCompatActivity.startSupportActionMode(callback)和适当的支持实现将从androidx.appcompat传递到回调中。
geekley

30

我个人更喜欢此链接中的这种方法

使用以下内容创建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"

2
尽管此链接可以回答问题,但最好在此处包括答案的基本部分,并提供链接以供参考。如果链接页面发生更改,仅链接的答案可能会无效。
tomloprod

谢谢您的评论,我已编辑问题。@tomloprod
N Jay

4
这是我的首选解决方案。但是,请务必注意,目前,当您使用新的矢量可绘制类型作为源时,此解决方案似乎无法正常工作。
Michael De Soto 2015年

1
@haagmm该解决方案需要API> =21。它也适用于矢量。
神经递质

1
而且它不适用于向量,根标记为bitmap。还有其他为矢量着色的方法。也许有人也可以在此处添加矢量着色…
milosmns 17-10-22

11

该线程中的大多数解决方案要么使用更新的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)
        );
    }

}

只需确保您输入“活动/片段”代码即可:

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

没有反射,没有视图查找,没有太多代码,是吗?

现在您可以忽略荒谬的事情了onCreateOptionsMenu/onOptionsItemSelected


从技术上讲,您正在执行视图查找。您正在迭代视图并确保它们不为null。;)
Martin Marconcini

您绝对是对的:-)尽管如此,Menu#getItem()工具栏中的复杂度为O(1),因为项目存储在ArrayList中。这与View#findViewById遍历(在我的回答中称为“ 视图查找”)不同,其复杂性远非恒定的:-)
Drew

同意,事实上,我做了一件非常类似的事情。这么多年后,Android仍未精简所有功能,我仍然感到震惊……
Martin Marconcini

如何通过这种方法更改溢出图标和汉堡包图标的颜色?
桑德拉(Sandra)

8

这是我使用的解决方案;您可以在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开始运行。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.