MenuItem teinte sur la barre d'outils AppCompat

93

Lorsque j'utilise des éléments de dessin de la AppCompatbibliothèque pour mes Toolbaréléments de menu, la teinte fonctionne comme prévu. Comme ça:

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

Mais si j'utilise mes propres dessinables ou même que je copie les dessinables de la AppCompatbibliothèque dans mon propre projet, cela ne teinte pas du tout.

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

Y a-t-il une magie spéciale dans le AppCompat Toolbarseul dessinable de teinte de cette bibliothèque? Un moyen de faire fonctionner cela avec mes propres drawables?

Exécuter ceci sur un appareil de niveau API 19 avec compileSdkVersion = 21et targetSdkVersion = 21, et également utiliser tout ce quiAppCompat

abc_ic_clear_mtrl_alpha_copyest une copie exacte du abc_ic_clear_mtrl_alphapng deAppCompat

Éditer:

La teinte est basée sur la valeur que j'ai définie android:textColorPrimarydans mon thème.

Par exemple, <item name="android:textColorPrimary">#00FF00</item>me donnerait une teinte verte.

Captures d'écran

La teinture fonctionne comme prévu avec drawable depuis AppCompat La teinture fonctionne comme prévu avec drawable depuis AppCompat

La teinture ne fonctionne pas avec le dessin copié depuis AppCompat La teinture ne fonctionne pas avec le dessin copié depuis AppCompat

grève
la source
Les deux styles ont le même parent? Et si vous étendiez le style supérieur avec le vôtre?
G_V
Il n'y a aucune différence dans les styles. La seule différence est le dessinable, qui sont tous deux des fichiers .png
greve
Le dessinable ressemble à une copie exacte du dessinable AppCombat original dans le code?
G_V
Ce sont des fichiers png que j'ai copiés. Ce sont exactement les mêmes.
greve
Alors, en quoi votre code diffère-t-il exactement de l'original s'il a le même style et la même image?
G_V

Réponses:

31

Parce que si vous regardez le code source du TintManager dans AppCompat, vous verrez:

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

Ce qui signifie à peu près qu'ils ont des resourceIds particuliers sur la liste blanche pour être teintés.

Mais je suppose que vous pouvez toujours voir comment elles teignent ces images et faire de même. C'est aussi simple que de définir le ColorFilter sur un dessin.

EvilDuck
la source
Ugh, c'est ce dont j'avais peur. Je n'ai pas trouvé le code source d'AppCompat dans le SDK, c'est pourquoi je n'ai pas trouvé cette partie moi-même. Je suppose que je vais devoir naviguer sur googlesource.com alors. Merci!
greve
8
Je sais que c'est une question tangentielle, mais pourquoi y a-t-il une liste blanche? S'il peut teinter ces icônes, pourquoi ne pouvons-nous pas teinter nos propres icônes? De plus, quel est l'intérêt de rendre presque tout rétrocompatible (avec AppCompat) lorsque vous omettez l'une des choses les plus importantes: avoir des icônes de la barre d'action (avec une couleur personnalisée).
Zsolt Safrany
1
Il y a un problème pour cela dans le suivi des problèmes de Google qui a été marqué comme corrigé, mais cela ne fonctionne pas pour moi, mais vous pouvez le suivre ici: issuetracker.google.com/issues/37127128
niknetniko
Ils prétendent que c'est corrigé, mais ce n'est pas le cas. Gosh, je déteste le moteur de thème Android, AppCompat et toute la merde qui lui est associée. Cela ne fonctionne que pour les exemples d'applications de «navigateur de référentiel Github».
Martin Marconcini
97

Après la nouvelle bibliothèque de support v22.1, vous pouvez utiliser quelque chose de similaire à ceci:

  @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
la source
1
Je dirais que dans ce cas, l'ancien setColorFilter()est bien préférable.
natario
@mvai, pourquoi setColorFilter () est plus préférable?
wilddev
4
@wilddev brièveté. Pourquoi déranger la classe de support DrawableCompat quand vous pouvez aller menu.findItem (). GetIcon (). SetColorFilter ()? Une doublure et claire.
natario
4
L'argument à une seule ligne n'est pas pertinent lorsque vous extrayez toute la logique dans votre propre méthode TintingUtils.tintMenuIcon (...) ou comme vous voulez l'appeler. Si vous avez besoin de changer ou d'ajuster la logique à l'avenir, vous le faites au même endroit, pas partout dans l'application.
Dan Dar3 du
1
C'est génial!
islam shariful
82

Définir une ColorFilter(teinte) sur a MenuItemest simple. Voici un exemple:

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

Le code ci-dessus est très utile si vous souhaitez prendre en charge différents thèmes et que vous ne souhaitez pas avoir de copies supplémentaires uniquement pour la couleur ou la transparence.

Cliquez ici pour une classe d'assistance pour définir unColorFilter sur tous les dessinables dans un menu, y compris l'icône de débordement.

En onCreateOptionsMenu(Menu menu)appelez juste MenuColorizer.colorMenu(this, menu, color);après avoir gonflé votre menu et le tour est joué; vos icônes sont teintées.

Jared Rummler
la source
Merci, je vais certainement essayer ça!
greve le
3
Je me suis cogné la tête contre mon bureau en essayant de comprendre pourquoi toutes mes icônes sont teintées, merci de m'avoir informé sur drawable.mutate ()!
Scott Cooper le
49

app:iconTintL'attribut est implémenté dans à SupportMenuInflaterpartir de la bibliothèque de support (au moins dans 28.0.0).

Testé avec succès avec l'API 15 et plus.

Fichier de ressources de 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>

(Dans ce cas, il y ?attr/appIconColorEnabledavait un attribut de couleur personnalisé dans les thèmes de l'application et les ressources d'icônes étaient des dessins vectoriels.)

Afilu
la source
5
Cela devrait être la nouvelle réponse acceptée! Aussi, s'il vous plaît noter android:iconTintet android:iconTintModene fonctionne pas, mais préfixer avec app:au lieu de android:fonctionne comme un charme (sur mes propres dessins vectoriels, API> = 21)
Sebastiaan Alvarez Rodriguez
Si vous appelez par programme: notez que SupportMenuInflatercela n'appliquera aucune logique personnalisée si le menu n'est pas SupportMenusimilaire MenuBuilder, il revient simplement à normal MenuInflater.
geekley
Dans ce cas, l'utilisation AppCompatActivity.startSupportActionMode(callback)et les implémentations de support appropriées de androidx.appcompatseront passées dans le rappel.
geekley
30

J'ai personnellement préféré cette approche à partir de ce lien

Créez une mise en page XML avec les éléments suivants:

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

et référencez ce dessinable à partir de votre menu:

<item
    android:id="@+id/option_menu_item_something"
    android:icon="@drawable/ic_action_something_tined"
N Jay
la source
2
Bien que ce lien puisse répondre à la question, il est préférable d'inclure les parties essentielles de la réponse ici et de fournir le lien pour référence. Les réponses aux liens uniquement peuvent devenir invalides si la page liée change.
tomloprod
Merci pour ton commentaire j'ai édité la question. @tomloprod
N Jay
4
C'est ma solution préférée. Cependant, il est important de noter que, pour le moment, cette solution ne semble pas fonctionner lorsque vous utilisez les nouveaux types de dessin vectoriel comme source.
Michael De Soto
1
@haagmm cette solution nécessite une API> = 21. Cela fonctionne également pour les vecteurs.
Neurotransmetteur
1
Et cela ne devrait pas fonctionner avec des vecteurs, la balise racine l'est bitmap. Il existe d'autres façons de colorer les vecteurs. Peut-être que quelqu'un pourrait ajouter une coloration vectorielle ici aussi ...
milosmns
11

La plupart des solutions de ce thread utilisent une API plus récente, ou utilisent la réflexion, ou utilisent une recherche de vue intensive pour obtenir le gonflé MenuItem.

Cependant, il existe une approche plus élégante pour le faire. Vous avez besoin d'une barre d'outils personnalisée, car votre cas d'utilisation «appliquer une teinte personnalisée» ne fonctionne pas bien avec l'API de style / thématisation publique.

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

}

Assurez-vous simplement d'appeler votre code d'activité / fragment:

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

Pas de réflexion, pas de recherche de vue, et pas tellement de code, hein?

Et maintenant, vous pouvez ignorer le ridicule onCreateOptionsMenu/onOptionsItemSelected.

A dessiné
la source
Techniquement, vous effectuez une recherche de vue. Vous itérez les vues et vous vous assurez qu'elles ne sont pas nulles. ;)
Martin Marconcini
Vous avez certainement raison d'une certaine manière :-) Néanmoins, Menu#getItem() complexité est O (1) dans la barre d'outils, car les éléments sont stockés dans ArrayList. Ce qui est différent de la View#findViewByIdtraversée (que j'ai appelée recherche de vue dans ma réponse), dont la complexité est loin d'être constante :-)
Dessiné
D'accord, en fait, j'ai fait une chose très similaire. Je suis toujours choqué qu'Android n'ait pas rationalisé tout cela après tant d'années…
Martin Marconcini
Comment puis-je changer les couleurs de l'icône de débordement et de l'icône de hamburger avec cette approche?
Sandra le
8

Voici la solution que j'utilise; vous pouvez l'appeler après onPrepareOptionsMenu () ou le lieu équivalent. La raison de mutate () est si vous utilisez les icônes à plusieurs endroits; sans le mutate, ils prendront tous la même teinte.

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

Cela ne prendra pas en charge le débordement, mais pour cela, vous pouvez le faire:

Disposition:

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

Modes:

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

Cela fonctionne depuis appcompat v23.1.0.

Apprenez OpenGL ES
la source