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そのライブラリからのドローアブルのみに色を付けるという点で、特別な魔法はありますか?これを自分のドローアブルで機能させる方法はありますか?

とを使用してAPIレベル19デバイスでこれを実行compileSdkVersion = 21targetSdkVersion = 21、さらにAppCompat

abc_ic_clear_mtrl_alpha_copyabc_ic_clear_mtrl_alphaからのpngの正確なコピーですAppCompat

編集:

着色はandroid:textColorPrimary、テーマで設定した値に基づいています。

たとえば<item name="android:textColorPrimary">#00FF00</item>、緑の色合いが得られます。

スクリーンショット

AppCompatからのドローアブルで期待どおりに色付けする AppCompatからのドローアブルで期待どおりに色付けする

AppCompatからコピーしたドローアブルでティンティングが機能しない AppCompatからコピーしたドローアブルでティンティングが機能しない


両方のスタイルに同じ親がありますか?トップスタイルを独自のもので拡張するとどうなりますか?
G_V 2014年

スタイルに違いはありません。唯一の違いは、両方とも.pngファイルであるドローアブルです
greve

ドローアブルは、コード内の元のAppCombatドローアブルの正確なコピーのように見えますか?
G_V 2014年

それらは私がコピーしたpngファイルです。それらはまったく同じです。
2014年

それで、同じスタイルと同じイメージがある場合、コードは元のコードとどこが正確に異なりますか?
G_V 2014年

回答:


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

つまり、特定のリソースIDがホワイトリストに登録されていることを意味します。

しかし、あなたはいつでも彼らがそれらの画像に色を付けている様子を見ることができ、同じことをすることができると思います。ドローアブルにColorFilterを設定するのと同じくらい簡単です。


ああ、それは私が恐れていたものです。SDKでAppCompatのソースコードを見つけられなかったため、この部分を自分で見つけられませんでした。私はgooglesource.comを閲覧する必要があると思います。ありがとう!
2014年

8
私はそれが正接の質問であることを知っていますが、なぜホワイトリストがあるのですか?これらのアイコンで色付けできる場合は、なぜ独自のアイコンに色付けできないのでしょうか。さらに、最も重要なことの1つであるアクションバーアイコン(カスタムカラー付き)を省くときに、ほとんどすべての後方互換性(AppCompatを使用)を作成するポイントは何ですか。
Zsolt Safrany 2015年

1
修正済みとマークされているGoogleの課題追跡に問題がありますが、私には機能しませんが、こちらで追跡できます:issuetracker.google.com/issues/37127128
niknetniko

彼らはこれは修正されたと主張していますが、修正されていません。まあ、私はAndroidテーマエンジン、AppCompat、それに関連するすべてのがらくたを嫌っています。「Github repo browser」サンプルアプリでのみ機能します。
マルティンマルコンチーニ

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 2015

@mvai、なぜsetColorFilter()がより好ましいのですか?
wilddev

4
@wilddev簡潔。menu.findItem()。getIcon()。setColorFilter()にアクセスできるのに、なぜサポートDrawableCompatクラスに迷惑をかけるのですか?ライナー1枚とクリア。
natario 2015

4
ロジック全体を独自のTintingUtils.tintMenuIcon(...)メソッドまたは呼び出したいものに抽象化する場合、ワンライナー引数は無関係です。将来ロジックを変更または調整する必要がある場合は、アプリケーション全体ではなく、1か所で行います。
Dan Dar3

1
これはすごい!
恥ずべきイスラム教

82

ColorFilter(色合い)を設定するの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オーバーフローアイコンを含むメニューのすべてのドローアブルにを設定するヘルパークラスについては、ここをクリックしてください

onCreateOptionsMenu(Menu menu)ちょうど呼び出しMenuColorizer.colorMenu(this, menu, color);、メニューと出来上がりを膨張した後、アイコンに色が付いています。


よろしくお願いいたします。
グレーヴェ

3
drawable.mutate()について頭を上げてくれてありがとう!
スコットクーパー

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:機能する代わりにプレフィックスを付けます(私自身のベクター
Sebastiaan Alvarez Rodriguez

プログラムで呼び出す場合:SupportMenuInflaterメニューがSupportMenulike MenuBuilderではない場合、カスタムロジックは適用されず、通常のにフォールバックするだけMenuInflaterです。
オタク

この場合、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

1
@haagmmこのソリューションには、API> = 21が必要です。また、ベクターに対しても機能します。
神経伝達物質2016年

1
また、ベクターでは機能しないはずですbitmap。ルートタグはです。ベクトルに色を付ける方法は他にもあります。たぶん、誰かがここでもベクトルのカラーリングを追加できます...
milosmns

11

このスレッドのほとんどのソリューションは、新しいAPIを使用するか、リフレクションを使用するか、集中的なビュールックアップを使用してインフレートされた状態に到達します MenuItemます。

ただし、これを行うためのよりエレガントな方法があります。「apply custom tint」のユースケースはパブリックスタイリング/テーマ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 2017年

あなたは間違いなく正しい方法です:-)それでも、Menu#getItem()アイテムはArrayListに格納されているため、ツールバーの複雑さはO(1)です。これはView#findViewByIdトラバーサル(私の回答ではビュールックアップと呼んでいます)とは異なり、その複雑さは一定ではありません:-)
Drew

実際、私は非常に似たようなことをしました。何年も経っても、Androidがこれほど効率化されていないことにまだショックを受けています…
Martin Marconcini 2017年

この方法でオーバーフローアイコンとハンバーガーアイコンの色を変更するにはどうすればよいですか?
サンドラ

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以降で機能します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.