Android 上的工具栏图标着色
Toolbar icon tinting on Android
我注意到使用 AppCompat 主题时,默认工具栏图标会根据我的风格 colorControlNormal
属性着色。
<style name="MyTheme" parent="Theme.AppCompat">
<item name="colorControlNormal">@color/yellow</item>
</style>
但是,正如您在上面看到的那样,并非所有图标都会出现这种情况。我提供了 "plus" 标志,这是我从官方图标中获得的,它没有着色(我使用了 png 的 "white" 版本)。根据我从这个 question 中了解到的情况,系统仅使用 alpha 通道为图标着色。这是真的吗?
如果是:有没有地方可以找到alpha定义的官方材质图标?如果不是 - 如果工具栏图标需要仅 alpha 才能着色 - Google 期望我们如何在工具栏中使用提供的图标?
我在 SDK 的某处发现了一些以 _alpha.png
结尾的图标,它们实际上着色得很好。但是我需要全套的material图标,从官方资源中我只能找到white
、grey600
和black
。
在运行时应用 ColorFilter
会有点痛苦,而我的实际工具栏 - 有些图标有色,有些没有 - 看起来很糟糕。
我看到这个问题得到了一些意见,所以我要 post 为那些没有阅读评论的人提供答案。
我在问题中的猜想都是错误的,这不是alpha通道的问题,至少不是外部问题。事实很简单,引用@alanv,
AppCompat only tints its own icons. For now, you will need to manually
tint any icons that you're providing separately from AppCompat.
这在未来可能会改变,但也可能不会。从this answer你还可以看到自动着色的图标列表(它们都属于appcompat的内部资源文件夹,所以你不能更改它们)和颜色。
我个人使用 colorControlNormal
黑色或白色(或类似的阴影),并导入具有该特定颜色的图标。彩色背景上的彩色图标看起来有点糟糕。然而,我发现另一个令人满意的解决方案是 github 上的 this class。您只需在创建菜单时调用 MenuColorizer.colorMenu()
。
这是我使用的解决方案。在 onPrepareOptionsMenu 或等效位置之后调用 tintAllIcons。 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。
您可以创建一个自定义工具栏,在展开菜单时使用您的色调。
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
,如果你使用这种方法
另一种选择是使用支持库中对矢量绘图的新支持。
在博客中查看 res/xml/ic_search.xml
post AppCompat — Age of the vectors
注意对 ?attr/colorControlNormal
的引用
<vector xmlns:android="..."
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0"
android:tint="?attr/colorControlNormal">
<path
android:pathData="..."
android:fillColor="@android:color/white"/>
</vector>
我实际上能够在 API 10(姜饼)上做到这一点,而且效果很好。
编辑:它在 API 22 上也有效...
这是最终结果。
注意:该图标是可绘制文件夹中的可绘制资源。
现在是这样完成的:
@Override
public void onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
MenuItem item = menu.findItem(R.id.action_refresh);
Drawable icon = getResources().getDrawable(R.drawable.ic_refresh_white_24dp);
icon.setColorFilter(getResources().getColor(R.color.colorAccent), PorterDuff.Mode.SRC_IN);
item.setIcon(icon);
}
此时你可以把它改成任何你想要的颜色!
@NonNull
public static Drawable setTintDrawable(@NonNull Drawable drawable, @ColorInt int color) {
drawable.clearColorFilter();
drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
drawable.invalidateSelf();
Drawable wrapDrawable = DrawableCompat.wrap(drawable).mutate();
DrawableCompat.setTint(wrapDrawable, color);
return wrapDrawable;
}
and call in this manner:
MenuItem location = menu.findItem(R.id.action_location);
DrawableUtils.setTintDrawable(location.getIcon(), Color.WHITE);
这是最终的真实答案
首先像这样为工具栏创建样式:
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" >
<item name="iconTint">@color/primaryTextColor</item>
<!--choice your favorite color-->
</style>
然后在您的主应用程序或 activity 主题中添加此行
<item name="actionBarPopupTheme">@style/AppTheme.PopupOverlay</item>
最后在您的布局文件中将此行添加到工具栏
android:theme="?attr/actionBarPopupTheme"
然后您会看到工具栏图标以您喜欢的颜色着色
对于 sdk 23 或更高版本:
<style name="AppThemeToolbar" parent="MyAppTheme">
....
<item name="android:drawableTint">@color/secondaryLightColor</item>
</style>
我的工具栏
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar
android:theme="@style/AppThemeToolbar"
android:layout_width="match_parent"
android:layout_height="attr/actionBarSize">
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
使用 androidX,您可以像这样定义工具栏
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:theme="@style/Toolbar" />
然后,扩展一个 AppCompat 主题并根据需要设置 colorControlNormal 属性:
<style name="Toolbar" parent="Theme.AppCompat.Light">
<item name="colorControlNormal">@color/colorBaseWhite</item>
<item name="android:background">@color/colorPrimary</item>
</style>
这可以在 Kotlin 中通过以下方式完成:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
menu.getItem(0)?.icon?.setTint(Color.WHITE)
}
else {
@Suppress("DEPRECATION")
menu.getItem(0)?.icon?.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN)
}
它应该适用于 Android 的所有现代版本,并且如果 getItem
或 icon
returns null.
将失败而不会崩溃
基本上,当您设置菜单时,三点图标会占用 AppTheme 中 android:textColorSecondary 的颜色,默认设置为黑色.
因此,如果您没有在项目的任何地方使用 textColorSecondary,那么您只需添加以下行
<item name="android:textColorSecondary">@color/White</item>
添加后可能看起来像这样。
<style name="AppTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
<!-- Customise your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:textColorSecondary">@color/White</item>
</style>
试试这个……
menu.getItem(0).getIcon().setTint(Color.parseColor("#22CC34"));
我注意到使用 AppCompat 主题时,默认工具栏图标会根据我的风格 colorControlNormal
属性着色。
<style name="MyTheme" parent="Theme.AppCompat">
<item name="colorControlNormal">@color/yellow</item>
</style>
但是,正如您在上面看到的那样,并非所有图标都会出现这种情况。我提供了 "plus" 标志,这是我从官方图标中获得的,它没有着色(我使用了 png 的 "white" 版本)。根据我从这个 question 中了解到的情况,系统仅使用 alpha 通道为图标着色。这是真的吗?
如果是:有没有地方可以找到alpha定义的官方材质图标?如果不是 - 如果工具栏图标需要仅 alpha 才能着色 - Google 期望我们如何在工具栏中使用提供的图标?
我在 SDK 的某处发现了一些以 _alpha.png
结尾的图标,它们实际上着色得很好。但是我需要全套的material图标,从官方资源中我只能找到white
、grey600
和black
。
在运行时应用 ColorFilter
会有点痛苦,而我的实际工具栏 - 有些图标有色,有些没有 - 看起来很糟糕。
我看到这个问题得到了一些意见,所以我要 post 为那些没有阅读评论的人提供答案。
我在问题中的猜想都是错误的,这不是alpha通道的问题,至少不是外部问题。事实很简单,引用@alanv,
AppCompat only tints its own icons. For now, you will need to manually tint any icons that you're providing separately from AppCompat.
这在未来可能会改变,但也可能不会。从this answer你还可以看到自动着色的图标列表(它们都属于appcompat的内部资源文件夹,所以你不能更改它们)和颜色。
我个人使用 colorControlNormal
黑色或白色(或类似的阴影),并导入具有该特定颜色的图标。彩色背景上的彩色图标看起来有点糟糕。然而,我发现另一个令人满意的解决方案是 github 上的 this class。您只需在创建菜单时调用 MenuColorizer.colorMenu()
。
这是我使用的解决方案。在 onPrepareOptionsMenu 或等效位置之后调用 tintAllIcons。 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。
您可以创建一个自定义工具栏,在展开菜单时使用您的色调。
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
,如果你使用这种方法
另一种选择是使用支持库中对矢量绘图的新支持。
在博客中查看 res/xml/ic_search.xml
post AppCompat — Age of the vectors
注意对 ?attr/colorControlNormal
<vector xmlns:android="..."
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0"
android:tint="?attr/colorControlNormal">
<path
android:pathData="..."
android:fillColor="@android:color/white"/>
</vector>
我实际上能够在 API 10(姜饼)上做到这一点,而且效果很好。
编辑:它在 API 22 上也有效...
这是最终结果。
注意:该图标是可绘制文件夹中的可绘制资源。
现在是这样完成的:
@Override
public void onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
MenuItem item = menu.findItem(R.id.action_refresh);
Drawable icon = getResources().getDrawable(R.drawable.ic_refresh_white_24dp);
icon.setColorFilter(getResources().getColor(R.color.colorAccent), PorterDuff.Mode.SRC_IN);
item.setIcon(icon);
}
此时你可以把它改成任何你想要的颜色!
@NonNull
public static Drawable setTintDrawable(@NonNull Drawable drawable, @ColorInt int color) {
drawable.clearColorFilter();
drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
drawable.invalidateSelf();
Drawable wrapDrawable = DrawableCompat.wrap(drawable).mutate();
DrawableCompat.setTint(wrapDrawable, color);
return wrapDrawable;
}
and call in this manner:
MenuItem location = menu.findItem(R.id.action_location);
DrawableUtils.setTintDrawable(location.getIcon(), Color.WHITE);
这是最终的真实答案 首先像这样为工具栏创建样式:
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" >
<item name="iconTint">@color/primaryTextColor</item>
<!--choice your favorite color-->
</style>
然后在您的主应用程序或 activity 主题中添加此行
<item name="actionBarPopupTheme">@style/AppTheme.PopupOverlay</item>
最后在您的布局文件中将此行添加到工具栏
android:theme="?attr/actionBarPopupTheme"
然后您会看到工具栏图标以您喜欢的颜色着色
对于 sdk 23 或更高版本:
<style name="AppThemeToolbar" parent="MyAppTheme">
....
<item name="android:drawableTint">@color/secondaryLightColor</item>
</style>
我的工具栏
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar
android:theme="@style/AppThemeToolbar"
android:layout_width="match_parent"
android:layout_height="attr/actionBarSize">
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
使用 androidX,您可以像这样定义工具栏
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:theme="@style/Toolbar" />
然后,扩展一个 AppCompat 主题并根据需要设置 colorControlNormal 属性:
<style name="Toolbar" parent="Theme.AppCompat.Light">
<item name="colorControlNormal">@color/colorBaseWhite</item>
<item name="android:background">@color/colorPrimary</item>
</style>
这可以在 Kotlin 中通过以下方式完成:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
menu.getItem(0)?.icon?.setTint(Color.WHITE)
}
else {
@Suppress("DEPRECATION")
menu.getItem(0)?.icon?.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN)
}
它应该适用于 Android 的所有现代版本,并且如果 getItem
或 icon
returns null.
基本上,当您设置菜单时,三点图标会占用 AppTheme 中 android:textColorSecondary 的颜色,默认设置为黑色.
因此,如果您没有在项目的任何地方使用 textColorSecondary,那么您只需添加以下行
<item name="android:textColorSecondary">@color/White</item>
添加后可能看起来像这样。
<style name="AppTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
<!-- Customise your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:textColorSecondary">@color/White</item>
</style>
试试这个……
menu.getItem(0).getIcon().setTint(Color.parseColor("#22CC34"));