Colorazione MenuItem sulla barra degli strumenti AppCompat


93

Quando uso i drawable dalla AppCompatlibreria per le mie Toolbarvoci di menu, la colorazione funziona come previsto. Come questo:

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

Ma se uso i miei disegnabili o addirittura copio i disegnabili dalla AppCompatlibreria al mio progetto, non si colorerà affatto.

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

C'è qualche magia speciale AppCompat Toolbarnell'unica tinta drawables da quella libreria? Qualche modo per farlo funzionare con i miei drawable?

Eseguendolo su un dispositivo API di livello 19 con compileSdkVersion = 21e targetSdkVersion = 21e utilizzando anche tutto daAppCompat

abc_ic_clear_mtrl_alpha_copyè una copia esatta del abc_ic_clear_mtrl_alphapng daAppCompat

Modificare:

La colorazione si basa sul valore che ho impostato android:textColorPrimarynel mio tema.

Ad esempio <item name="android:textColorPrimary">#00FF00</item>, mi darebbe una tinta verde.

Screenshot

La colorazione funziona come previsto con drawable da AppCompat La colorazione funziona come previsto con drawable da AppCompat

La colorazione non funziona con il disegno copiato da AppCompat La colorazione non funziona con il disegno copiato da AppCompat


Entrambi gli stili hanno lo stesso genitore? E se estendessi lo stile migliore con il tuo?
G_V

Non c'è differenza negli stili. L'unica differenza è il drawable, che sono entrambi file .png
greve

Il drawable sembra una copia esatta del drawable originale di AppCombat nel codice?
G_V

Sono file png, che ho copiato. Sono esattamente la stessa cosa.
greve

Quindi dove si differenzia esattamente il tuo codice dall'originale se ha lo stesso stile e la stessa immagine?
G_V

Risposte:


31

Perché se dai un'occhiata al codice sorgente di TintManager in AppCompat, vedrai:

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

Il che significa più o meno che hanno particolari resourceId nella lista bianca per essere colorati.

Ma immagino che tu possa sempre vedere come colorano quelle immagini e fare lo stesso. È facile come impostare ColorFilter su un disegnabile.


Ugh, ecco di cosa avevo paura. Non ho trovato il codice sorgente per AppCompat nell'SDK, ecco perché non ho trovato questa parte da solo. Immagino che dovrò navigare su googlesource.com allora. Grazie!
greve

8
So che è una domanda tangenziale, ma perché c'è una lista bianca? Se è in grado di colorare queste icone, perché non possiamo colorare le nostre icone? Inoltre, qual è il punto nel rendere quasi tutto compatibile con le versioni precedenti (con AppCompat) quando lasci fuori una delle cose più importanti: avere icone della barra delle azioni (con colore personalizzato).
Zsolt Safrany

1
C'è un problema nel tracker dei problemi di Google che è stato contrassegnato come risolto, ma non funziona per me, ma puoi rintracciarlo qui: issuetracker.google.com/issues/37127128
niknetniko

Affermano che questo è stato risolto, ma non lo è. Accidenti, detesto il motore dei temi Android, AppCompat e tutte le stronzate ad esso associate. Funziona solo per le app di esempio "Github repo browser".
Martin Marconcini

97

Dopo la nuova libreria di supporto v22.1, puoi usare qualcosa di simile a questo:

  @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
Direi che in questo caso setColorFilter()è preferibile il vecchio .
natario

@mvai, perché setColorFilter () è più preferibile?
Wilddev

4
@wilddev brevità. Perché disturbare la classe DrawableCompat di supporto quando puoi andare su menu.findItem (). GetIcon (). SetColorFilter ()? Una fodera e trasparente.
natario

4
L'argomento di una riga è irrilevante quando si astrae l'intera logica nel proprio metodo TintingUtils.tintMenuIcon (...) o come si desidera chiamarlo. Se in futuro è necessario modificare o adattare la logica, lo si fa in un unico punto, non in tutta l'applicazione.
Dan Dar3

1
Questo e spettacolare!
Shariful islam

82

Impostare una ColorFilter(tinta) su a MenuItemè semplice. Ecco un esempio:

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

Il codice sopra è molto utile se vuoi supportare temi diversi e non vuoi avere copie extra solo per il colore o la trasparenza.

Fare clic qui per una classe helper per impostare unColorFiltersu tutti i drawables in un menu, inclusa l'icona di overflow.

In onCreateOptionsMenu(Menu menu)appena chiamata MenuColorizer.colorMenu(this, menu, color);dopo aver gonfiato il vostro menu e voilà; le tue icone sono colorate.


Grazie, lo proverò sicuramente!
greve

3
Ho sbattuto la testa contro la mia scrivania cercando di capire perché tutte le mie icone sono state colorate, grazie per l'avvertenza su drawable.mutate ()!
Scott Cooper

48

app:iconTintl'attributo è implementato SupportMenuInflaterdalla libreria di supporto (almeno nella 28.0.0).

Testato con successo con API 15 e versioni successive.

File di risorse del 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>

(In questo caso ?attr/appIconColorEnabledera un attributo di colore personalizzato nei temi dell'app e le risorse delle icone erano disegnabili con vettori.)


5
Questa dovrebbe essere la nuova risposta accettata! Inoltre, tieni presente android:iconTinte android:iconTintModenon funziona, ma il prefisso con app:invece di android:funziona come un incantesimo (sui miei disegnatori vettoriali, API> = 21)
Sebastiaan Alvarez Rodriguez

Se si chiama in modo programmatico: nota che SupportMenuInflaternon applicherà alcuna logica personalizzata se il menu non è SupportMenusimile MenuBuilder, torna alla normalità MenuInflater.
geekley

In questo caso, l'utilizzo AppCompatActivity.startSupportActionMode(callback)e le implementazioni di supporto appropriate da androidx.appcompatverranno passati al callback.
geekley

30

Personalmente ho preferito questo approccio da questo collegamento

Crea un layout XML con quanto segue:

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

e fai riferimento a questo disegnabile dal tuo menu:

<item
    android:id="@+id/option_menu_item_something"
    android:icon="@drawable/ic_action_something_tined"

2
Sebbene questo collegamento possa rispondere alla domanda, è meglio includere le parti essenziali della risposta qui e fornire il collegamento come riferimento. Le risposte di solo collegamento possono diventare non valide se la pagina collegata cambia.
tomloprod,

Grazie per il tuo commento ho modificato la domanda. @tomloprod
N Jay,

4
Questa è la mia soluzione preferita. Tuttavia, è importante notare che, per ora, questa soluzione non sembra funzionare quando si utilizzano i nuovi tipi disegnabili vettoriali come origine.
Michael De Soto

1
@haagmm questa soluzione richiede API> = 21. Funziona anche per i vettori.
Neurotrasmettitore

1
E non dovrebbe funzionare con i vettori, il tag radice lo è bitmap. Esistono altri modi per colorare i vettori. Forse qualcuno potrebbe aggiungere la colorazione vettoriale anche qui ...
milosmns

11

La maggior parte delle soluzioni in questo thread utilizza un'API più recente o la riflessione o utilizza la ricerca della vista intensiva per arrivare al gonfiato MenuItem .

Tuttavia, c'è un approccio più elegante per farlo. Hai bisogno di una barra degli strumenti personalizzata, poiché il tuo caso d'uso "applica tinta personalizzata" non funziona bene con l'API pubblica per stili / temi.

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

}

Assicurati solo di chiamare il tuo codice attività / frammento:

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

Nessun riflesso, nessuna ricerca vista e non tanto codice, eh?

E ora puoi ignorare il ridicolo onCreateOptionsMenu/onOptionsItemSelected.


Tecnicamente, stai facendo una ricerca vista. Stai iterando le visualizzazioni e assicurandoti che non siano nulle. ;)
Martin Marconcini

Hai decisamente ragione in un certo senso :-) Tuttavia, la Menu#getItem()complessità è O (1) nella barra degli strumenti, perché gli elementi sono memorizzati in ArrayList. Che è diverso dall'attraversamento View#findViewById(che ho indicato come ricerca della vista nella mia risposta), la cui complessità è lungi dall'essere costante :-)
Drew

D'accordo, infatti, ho fatto una cosa molto simile. Sono ancora scioccato dal fatto che Android non abbia semplificato tutto questo dopo così tanti anni ...
Martin Marconcini

Come posso cambiare l'icona di overflow e i colori dell'icona dell'hamburger con questo approccio?
Sandra

8

Ecco la soluzione che utilizzo; puoi chiamarlo dopo onPrepareOptionsMenu () o il posto equivalente. Il motivo per mutate () è se ti capita di usare le icone in più di una posizione; senza il mutato, assumeranno tutti la stessa tinta.

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

Questo non si prenderà cura dell'overflow, ma per quello, puoi farlo:

Disposizione:

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

Stili:

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

Funziona a partire da appcompat v23.1.0.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.