When I use drawables from the AppCompat
library for my Toolbar
menu items the tinting works as expected. Like this:
<item
android:id="@+id/action_clear"
android:icon="@drawable/abc_ic_clear_mtrl_alpha" <-- from AppCompat
android:title="@string/clear" />
But if I use my own drawables or actually even copy the drawables from the AppCompat
library to my own project it will not tint at all.
<item
android:id="@+id/action_clear"
android:icon="@drawable/abc_ic_clear_mtrl_alpha_copy" <-- copy from AppCompat
android:title="@string/clear" />
Is there some special magic in the AppCompat
Toolbar
that only tint drawables from that library? Any way to get this to work with my own drawables?
Running this on API Level 19 device with compileSdkVersion = 21
and targetSdkVersion = 21
, and also using everything from AppCompat
abc_ic_clear_mtrl_alpha_copy
is an exact copy of the abc_ic_clear_mtrl_alpha
png from AppCompat
Edit:
The tinting is based on the value I have set for android:textColorPrimary
in my theme.
E.g. <item name="android:textColorPrimary">#00FF00</item>
would give me a green tint color.
Screenshots
Tinting working as expected with drawable from AppCompat
Tinting not working with drawable copied from AppCompat
Because if you take a look at the source code of the TintManager in AppCompat, you will see:
/**
* 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
};
Which pretty much means they have particular resourceIds whitelisted to be tinted.
But I guess you can always see how they're tinting those images and do the same. It's as easy as set the ColorFilter on a drawable.
After the new Support library v22.1, you can use something similar to this:
@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;
}
Setting a ColorFilter
(tint) on a MenuItem
is simple. Here is an example:
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);
}
The above code is very helpful if you want to support different themes and you don't want to have extra copies just for the color or transparency.
Click here for a helper class to set a ColorFilter
on all the drawables in a menu, including the overflow icon.
In onCreateOptionsMenu(Menu menu)
just call MenuColorizer.colorMenu(this, menu, color);
after inflating your menu and voila; your icons are tinted.
I personally preferred this approach from this link
Create an XML layout with the following:
<?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"/>
and reference this drawable from your menu:
<item
android:id="@+id/option_menu_item_something"
android:icon="@drawable/ic_action_something_tined"
app:iconTint
attribute is implemented in SupportMenuInflater
from the support library (at least in 28.0.0).
Tested successfully with API 15 and up.
Menu resource file:
<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>
(In this case ?attr/appIconColorEnabled
was a custom color attribute in the app's themes, and the icon resources were vector drawables.)
Most of the solutions in this thread either use a newer API, or use reflection, or use intensive view lookup to get to the inflated MenuItem
.
However, there's a more elegant approach to do that. You need a custom Toolbar, as your "apply custom tint" use case does not play well with public styling/theming 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)
);
}
}
Just make sure you call in your Activity/Fragment code:
toolbar.inflateMenu(R.menu.some_menu);
toolbar.setOnMenuItemClickListener(someListener);
No reflection, no view lookup, and not so much code, huh?
And now you can ignore the ridiculous onCreateOptionsMenu/onOptionsItemSelected
.
Here is the solution that I use; you can call it after onPrepareOptionsMenu() or the equivalent place. The reason for mutate() is if you happen to use the icons in more than one location; without the mutate, they will all take on the same tint.
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);
}
}
}
}
}
This won't take care of the overflow, but for that, you can do this:
Layout:
<android.support.v7.widget.Toolbar
...
android:theme="@style/myToolbarTheme" />
Styles:
<style name="myToolbarTheme">
<item name="colorControlNormal">#FF0000</item>
</style>
This works as of appcompat v23.1.0.
来源:https://stackoverflow.com/questions/26780046/menuitem-tinting-on-appcompat-toolbar