web-dev-qa-db-fra.com

MenuItem teinter sur AppCompat Toolbar

Lorsque j'utilise des éléments dessinables de la bibliothèque AppCompat pour mes éléments de menu Toolbar, 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 si je les copie de la bibliothèque AppCompat dans mon propre projet, cela ne se colorera 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" />

Existe-t-il une magie spéciale dans le AppCompatToolbar qui ne te permet de dessiner que les tirages de cette bibliothèque? Un moyen d'obtenir que cela fonctionne avec mes propres dessinables?

Exécuter ceci sur un périphérique API de niveau 19 avec compileSdkVersion = 21 et targetSdkVersion = 21, et aussi en utilisant tout de AppCompat

abc_ic_clear_mtrl_alpha_copy est une copie exacte de la abc_ic_clear_mtrl_alpha _ png de AppCompat

Edit:

La teinte est basée sur la valeur que j'ai définie pour Android:textColorPrimary dans mon thème.

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

Captures d'écran

La teinte fonctionne comme prévu avec la possibilité d'être dessiné à partir d'AppCompat Tinting working as expected with drawable from AppCompat

La teinte ne fonctionne pas avec la copie imprimable à partir d'AppCompat Tinting not working with drawable copied from AppCompat

81
greve

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 quasiment qu'ils ont des ressources particulières dans leur liste blanche à teinter.

Mais je suppose que vous pouvez toujours voir comment ils colorent ces images et font de même. C'est aussi simple que de régler le ColorFilter sur un dessinable.

32
EvilDuck

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;
    }
83
Mahdi Hijazi

Définir un ColorFilter (teinte) sur un MenuItem est 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 voulez pas de copies supplémentaires uniquement pour la couleur ou la transparence.

Cliquez ici pour qu'une classe d'assistance définisse un ColorFilter sur tous les éléments dessinables d'un menu, y compris le icône de débordement.

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

80
Jared Rummler

Personnellement, j'ai préféré cette approche à partir de cette 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-le dans votre menu:

<item
    Android:id="@+id/option_menu_item_something"
    Android:icon="@drawable/ic_action_something_tined"
29
N Jay

L'attribut app:iconTint Est implémenté dans SupportMenuInflater à partir de la bibliothèque de support (au moins dans 28.0.0).

Testé avec succès avec 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, ?attr/appIconColorEnabled Était un attribut de couleur personnalisé dans les thèmes de l'application et les ressources d'icône étaient des vecteurs dessiables.)

19
Afilu

La plupart des solutions de ce fil de discussion utilisent soit une API plus récente, soit une réflexion, soit une recherche intensive dans les vues pour accéder à la variable MenuItem gonflée.

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 styling/theming 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 que vous appelez 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.

10
Drew

Voici la solution que j'utilise; vous pouvez l'appeler après onPrepareOptionsMenu () ou son emplacement équivalent. La raison de la mutation () est si vous utilisez les icônes à plusieurs endroits; sans la mutation, 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 résoudra pas le problème de 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 à partir de appcompat v23.1.0.

8
Learn OpenGL ES