MenuItem tinting di AppCompat Toolbar

95

Saat saya menggunakan drawable dari AppCompatperpustakaan untuk Toolbaritem menu saya , pewarnaan berfungsi seperti yang diharapkan. Seperti ini:

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

Tetapi jika saya menggunakan drawable saya sendiri atau bahkan menyalin drawable dari AppCompatperpustakaan ke proyek saya sendiri, gambar tersebut tidak akan menghasilkan warna sama sekali.

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

Apakah ada keajaiban khusus dalam satu AppCompat Toolbar-satunya warna yang dapat digambar dari perpustakaan itu? Adakah cara untuk membuatnya bekerja dengan drawable saya sendiri?

Menjalankan ini di perangkat API Level 19 dengan compileSdkVersion = 21dan targetSdkVersion = 21, dan juga menggunakan semuanya dariAppCompat

abc_ic_clear_mtrl_alpha_copyadalah salinan persis dari abc_ic_clear_mtrl_alphapng dariAppCompat

Edit:

Pewarnaannya didasarkan pada nilai yang telah saya tetapkan android:textColorPrimarydi tema saya.

Misalnya <item name="android:textColorPrimary">#00FF00</item>akan memberi saya warna tint hijau.

Screenshot

Tinting berfungsi seperti yang diharapkan dengan drawable dari AppCompat Tinting berfungsi seperti yang diharapkan dengan drawable dari AppCompat

Pewarnaan tidak berfungsi dengan drawable yang disalin dari AppCompat Pewarnaan tidak berfungsi dengan drawable yang disalin dari AppCompat

Greve
sumber
Kedua gaya memiliki induk yang sama? Bagaimana jika Anda memperluas gaya top dengan gaya Anda sendiri?
G_V
Tidak ada perbedaan gaya. Satu-satunya perbedaan adalah drawable, yang keduanya merupakan file .png
greve
Drawable terlihat seperti salinan persis dari drawable AppCombat asli dalam kode?
G_V
Itu adalah file png, yang saya salin. Mereka persis sama.
Greve
Jadi di mana tepatnya kode Anda berbeda dari aslinya jika memiliki gaya dan gambar yang sama?
G_V

Jawaban:

31

Karena jika Anda melihat kode sumber TintManager di AppCompat, Anda akan melihat:

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

Yang berarti mereka memiliki resourceIds tertentu yang masuk daftar putih untuk diwarnai.

Tapi saya rasa Anda selalu bisa melihat bagaimana mereka mewarnai gambar-gambar itu dan melakukan hal yang sama. Ini semudah mengatur ColorFilter pada drawable.

EvilDuck
sumber
Ugh, itulah yang aku takutkan. Saya tidak menemukan kode sumber untuk AppCompat di SDK, itulah mengapa saya tidak menemukan bagian ini sendiri. Sepertinya saya harus menjelajahi googlesource.com. Terima kasih!
Greve
8
Saya tahu ini pertanyaan tangensial, tetapi mengapa ada daftar putih? Jika itu bisa melakukan pewarnaan dengan ikon-ikon ini lalu mengapa kita tidak bisa mewarnai ikon kita sendiri? Plus, apa gunanya membuat hampir semuanya kompatibel ke belakang (dengan AppCompat) saat Anda meninggalkan salah satu hal terpenting: memiliki ikon bilah tindakan (dengan warna khusus).
Zsolt Safrany
1
Ada masalah untuk ini di pelacak isu Google yang telah ditandai sebagai tetap, tetapi tidak bekerja untuk saya, tetapi Anda dapat melacak sini: issuetracker.google.com/issues/37127128
niknetniko
Mereka mengklaim ini sudah diperbaiki, tetapi ternyata tidak. Astaga, saya benci mesin tema Android, AppCompat dan semua omong kosong yang terkait dengannya. Ini hanya berfungsi untuk aplikasi contoh "browser repo Github".
Martin Marconcini
98

Setelah pustaka Dukungan v22.1 baru, Anda dapat menggunakan sesuatu yang mirip dengan ini:

  @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;
    }
Mahdi Hijazi
sumber
1
Saya akan mengatakan bahwa dalam hal ini yang lama setColorFilter()lebih disukai.
natario
@mvai, mengapa setColorFilter () lebih disukai?
wilddev
4
@wildev singkatnya. Mengapa repot-repot kelas dukungan DrawableCompat ketika Anda bisa membuka menu.findItem (). GetIcon (). SetColorFilter ()? Satu kapal dan jelas.
natario
5
Argumen satu baris tidak relevan saat Anda mengabstraksi seluruh logika ke dalam metode TintingUtils.tintMenuIcon (...) Anda sendiri atau apa pun yang Anda ingin menyebutnya. Jika Anda perlu mengubah atau menyesuaikan logika di masa mendatang, Anda melakukannya di satu tempat, bukan di seluruh aplikasi.
Dan Dar3
Tidak perlu logika yang berbeda untuk mewarnai dan tidak perlu berubah di masa depan.
Jemshit Iskenderov
84

Menetapkan ColorFilter(tint) pada a MenuItemitu sederhana. Berikut ini contohnya:

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

Kode di atas sangat membantu jika Anda ingin mendukung tema yang berbeda dan Anda tidak ingin memiliki salinan tambahan hanya untuk warna atau transparansi.

Klik di sini untuk kelas helper untuk menyetelColorFiltersemua drawable dalam menu, termasuk ikon luapan.

Di onCreateOptionsMenu(Menu menu)panggil saja MenuColorizer.colorMenu(this, menu, color);setelah menggembungkan menu dan voila Anda; ikon Anda diwarnai.

Jared Rummler
sumber
4
Saya telah membenturkan kepala ke meja saya mencoba mencari tahu mengapa semua ikon saya diwarnai, terima kasih atas perhatiannya tentang drawable.mutate ()!
Scott Cooper
52

app:iconTintatribut diimplementasikan SupportMenuInflaterdari pustaka dukungan (setidaknya di 28.0.0).

Berhasil diuji dengan API 15 dan lebih tinggi.

File sumber daya menu:

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

(Dalam hal ini ?attr/appIconColorEnabledadalah atribut warna khusus dalam tema aplikasi, dan sumber daya ikonnya adalah vektor yang dapat digambar.)

Afilu
sumber
5
Ini harus menjadi jawaban baru yang diterima! Juga, harap perhatikan android:iconTintdan android:iconTintModetidak berfungsi, tetapi mengawali dengan app:alih - alih android:berfungsi seperti pesona (pada drawable vektor saya sendiri, API> = 21)
Sebastiaan Alvarez Rodriguez
Jika memanggil secara terprogram: perhatikan bahwa SupportMenuInflatertidak akan menerapkan logika khusus apa pun jika menu bukan SupportMenusejenisnya MenuBuilder, itu hanya kembali ke reguler MenuInflater.
geekley
Dalam kasus ini, penggunaan AppCompatActivity.startSupportActionMode(callback)dan implementasi dukungan yang sesuai dari androidx.appcompatakan diteruskan ke callback.
geekley
Cukup luar biasa .. terima kasih atas jawabannya ..
Kalpesh Kundanani
30

Saya pribadi lebih suka pendekatan ini dari tautan ini

Buat tata letak XML dengan berikut ini:

<?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"/>

dan rujuk drawable ini dari menu Anda:

<item
    android:id="@+id/option_menu_item_something"
    android:icon="@drawable/ic_action_something_tined"
N Jay
sumber
2
Meskipun tautan ini mungkin menjawab pertanyaan, lebih baik menyertakan bagian penting dari jawaban di sini dan menyediakan tautan untuk referensi. Jawaban link saja bisa menjadi tidak valid jika halaman tertaut berubah.
tomloprod
Terima kasih atas komentar Anda saya telah mengedit pertanyaan. @tomloprod
N Jay
4
Ini adalah solusi pilihan saya. Namun, penting untuk diperhatikan bahwa, untuk saat ini, solusi ini tampaknya tidak berfungsi saat Anda menggunakan jenis vector drawable baru sebagai sumber.
Michael De Soto
1
@haagmm solusi ini membutuhkan API> = 21. Juga berfungsi untuk vektor juga.
Neurotransmitter
1
Dan itu seharusnya tidak bekerja dengan vektor, tag root-nya bitmap. Ada cara lain untuk mewarnai vektor. Mungkin seseorang bisa menambahkan pewarnaan vektor di sini juga ...
milosmns
11

Sebagian besar solusi di utas ini menggunakan API yang lebih baru, atau menggunakan refleksi, atau menggunakan pencarian tampilan intensif untuk menuju ke peningkatan MenuItem.

Namun, ada pendekatan yang lebih elegan untuk melakukan itu. Anda memerlukan Toolbar kustom, karena kasus penggunaan "terapkan warna kustom" tidak cocok dengan API penataan gaya / tema publik.

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

}

Pastikan Anda memanggil kode Aktivitas / Fragmen Anda:

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

Tidak ada refleksi, tidak ada pencarian tampilan, dan tidak banyak kode, ya?

Dan sekarang Anda bisa mengabaikan yang konyol onCreateOptionsMenu/onOptionsItemSelected.

Drew
sumber
Secara teknis, Anda sedang melakukan pencarian tampilan. Anda mengulangi tampilan dan memastikannya bukan null. ;)
Martin Marconcini
Anda pasti benar dalam beberapa hal :-) Meskipun demikian, Menu#getItem()kompleksitas adalah O (1) di toolbar, karena item disimpan di ArrayList. Yang berbeda dari View#findViewByIdtraversal (yang saya sebut view lookup dalam jawaban saya), kompleksitasnya jauh dari konstan :-)
Drew
Setuju, pada kenyataannya, saya melakukan hal yang sangat mirip. Saya masih terkejut bahwa Android belum merampingkan semua ini setelah bertahun-tahun…
Martin Marconcini
Bagaimana cara mengubah ikon luapan dan warna ikon hamburger dengan pendekatan ini?
Sandra
8

Berikut adalah solusi yang saya gunakan; Anda bisa memanggilnya setelah onPrepareOptionsMenu () atau tempat yang setara. Alasan mutate () adalah jika Anda menggunakan ikon di lebih dari satu lokasi; tanpa mutasi, semuanya akan memiliki warna yang sama.

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

Ini tidak akan menangani overflow, tetapi untuk itu, Anda dapat melakukan ini:

Tata Letak:

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

Gaya:

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

Ini berfungsi pada appcompat v23.1.0.

Pelajari OpenGL ES
sumber
2

Ini berhasil untuk saya:

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
    }

Atau Anda dapat menggunakan tint dalam drawable xml:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    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>
Selena Smiertin
sumber