MenuItem-Tönung in der AppCompat-Symbolleiste

93

Wenn ich Drawables aus dem AppCompat für meine ToolbarMenüpunkte Bibliothek die Tönung wie erwartet. So was:

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

Aber wenn ich meine eigenen Drawables benutze oder sogar die Drawables aus dem kopiere AppCompat Bibliothek in mein eigenes Projekt , wird es überhaupt nicht .

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

Gibt es eine besondere Magie in den AppCompat Toolbarnur Farbtönen aus dieser Bibliothek? Gibt es eine Möglichkeit, dies mit meinen eigenen Drawables zum Laufen zu bringen?

Ausführen auf einem API Level 19-Gerät mit compileSdkVersion = 21 und aus targetSdkVersion = 21und verwenden Sie auch alles vonAppCompat

abc_ic_clear_mtrl_alpha_copy ist eine exakte Kopie der abc_ic_clear_mtrl_alpha PNG vonAppCompat

Bearbeiten:

Die Tönung basiert auf dem Wert, den ich festgelegt habe android:textColorPrimary in meinem Thema festgelegt habe.

Z.B <item name="android:textColorPrimary">#00FF00</item> würde mir eine grün gefärbte Farbe geben.

Screenshots

Tönung funktioniert erwartungsgemäß mit Drawable von AppCompat Tönung funktioniert erwartungsgemäß mit Drawable von AppCompat

Tönung funktioniert nicht mit aus AppCompat kopiertem Zeichen Tönung funktioniert nicht mit aus AppCompat kopiertem Zeichen

Greve
quelle
Beide Stile haben das gleiche Elternteil? Was ist, wenn Sie den Top-Stil mit Ihrem eigenen erweitern?
G_V
Es gibt keinen Unterschied in den Stilen. Der einzige Unterschied ist die ziehbar, die beide .png - Dateien sind
greve
Das Drawable sieht aus wie eine exakte Kopie des ursprünglichen AppCombat Drawable im Code?
G_V
Es sind PNG-Dateien, die ich kopiert habe. Sie sind genau das gleiche.
Greve
Wo genau unterscheidet sich Ihr Code vom Original, wenn er denselben Stil und dasselbe Bild hat?
G_V

Antworten:

31

Wenn Sie sich den Quellcode des TintManager in AppCompat ansehen, werden Sie Folgendes sehen:

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

Was ziemlich genau bedeutet, dass sie bestimmte Ressourcen-IDs auf der Whitelist haben, die getönt werden sollen.

Aber ich denke, Sie können immer sehen, wie sie diese Bilder tönen und dasselbe tun. Es ist so einfach, wie den ColorFilter auf ein Zeichenelement zu setzen.

EvilDuck
quelle
Ugh, davor hatte ich Angst. Ich habe den Quellcode für AppCompat nicht im SDK gefunden, deshalb habe ich diesen Teil selbst nicht gefunden. Dann muss ich wohl auf googlesource.com stöbern. Vielen Dank!
Greve
8
Ich weiß, es ist eine tangentiale Frage, aber warum gibt es eine weiße Liste? Wenn es mit diesen Symbolen tönen kann, warum können wir dann nicht unsere eigenen Symbole tönen? Und was bringt es, fast alles abwärtskompatibel zu machen (mit AppCompat), wenn Sie eines der wichtigsten Dinge weglassen: Aktionsleistensymbole (mit benutzerdefinierten Farben).
Zsolt Safrany
1
Es gibt ein Problem in Googles Issue-Tracker, das als behoben markiert wurde, aber bei mir nicht funktioniert. Sie können es jedoch hier verfolgen
niknetniko
Sie behaupten, dies sei behoben, aber nicht. Meine Güte, ich hasse die Android-Theme-Engine, AppCompat und den ganzen Mist, der damit verbunden ist. Es funktioniert nur für Beispiel-Apps für den „Github-Repo-Browser“.
Martin Marconcini
97

Nach der neuen Support-Bibliothek v22.1 können Sie Folgendes verwenden:

  @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
quelle
1
Ich würde sagen, dass in diesem Fall das Alte setColorFilter()viel besser ist.
Natario
@mvai, warum ist setColorFilter () vorzuziehen?
Wilddev
4
@ Wilddev Kürze. Warum die unterstützende DrawableCompat-Klasse stören, wenn Sie auf menu.findItem (). GetIcon (). SetColorFilter () gehen können? Ein Liner und klar.
Natario
4
Das einzeilige Argument ist irrelevant, wenn Sie die gesamte Logik in Ihre eigene TintingUtils.tintMenuIcon (...) -Methode oder wie auch immer Sie sie nennen möchten abstrahieren. Wenn Sie die Logik in Zukunft ändern oder anpassen müssen, tun Sie dies an einem Ort, nicht in der gesamten Anwendung.
Dan Dar3
1
Das ist fantastisch!
Shariful Islam
82

Das Einstellen von a ColorFilter(Farbton) auf a MenuItemist einfach. Hier ist ein Beispiel:

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

Der obige Code ist sehr hilfreich, wenn Sie verschiedene Themen unterstützen möchten und keine zusätzlichen Kopien nur für die Farbe oder Transparenz haben möchten.

Klicken Sie hier, um eineColorFilterHilfsklasse für alle Zeichen in einem Menü festzulegen, einschließlich des Überlaufsymbols.

In onCreateOptionsMenu(Menu menu)nur Anruf MenuColorizer.colorMenu(this, menu, color);nach dem Menü und voila Aufblasen; Ihre Symbole sind getönt.

Jared Rummler
quelle
Danke, werde das auf jeden Fall ausprobieren!
Greve
3
Ich habe meinen Kopf gegen meinen Schreibtisch geschlagen, um herauszufinden, warum alle meine Symbole getönt werden. Vielen Dank für die Hinweise zu drawable.mutate ()!
Scott Cooper
47

app:iconTintDas Attribut wird in SupportMenuInflaterder Support-Bibliothek implementiert (mindestens in 28.0.0).

Erfolgreich mit API 15 und höher getestet.

Menü Ressourcendatei:

<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 diesem Fall ?attr/appIconColorEnabledwar ein benutzerdefiniertes Farbattribut in den Themen der App enthalten, und die Symbolressourcen waren Vektorzeichnungen.)

Afilu
quelle
5
Dies sollte die neu akzeptierte Antwort sein! Beachten Sie auch, android:iconTintund android:iconTintModenicht Arbeit, sondern voran mit app:statt android:wirkt wie ein Zauber (auf meinem eigenen Vektor Drawables, API> = 21)
Sebastiaan Alvarez Rodriguez
Wenn Sie programmgesteuert aufrufen: Beachten Sie, dass SupportMenuInflaterkeine benutzerdefinierte Logik angewendet wird, wenn das Menü nicht SupportMenuähnlich ist MenuBuilder, sondern nur auf normal zurückgegriffen wird MenuInflater.
Geekley
In diesem Fall werden die Verwendung AppCompatActivity.startSupportActionMode(callback)und die entsprechenden Support-Implementierungen von androidx.appcompatan den Rückruf übergeben.
Geekley
30

Ich persönlich habe diesen Ansatz über diesen Link bevorzugt

Erstellen Sie ein XML-Layout mit folgenden Elementen:

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

und verweisen Sie auf dieses Zeichen aus Ihrem Menü:

<item
    android:id="@+id/option_menu_item_something"
    android:icon="@drawable/ic_action_something_tined"
N Jay
quelle
2
Während dieser Link die Frage beantworten kann, ist es besser, die wesentlichen Teile der Antwort hier aufzunehmen und den Link als Referenz bereitzustellen. Nur-Link-Antworten können ungültig werden, wenn sich die verknüpfte Seite ändert.
Tomloprod
Vielen Dank für Ihren Kommentar Ich habe Frage bearbeitet. @ Tomloprod
N Jay
4
Dies ist meine bevorzugte Lösung. Es ist jedoch wichtig zu beachten, dass diese Lösung derzeit nicht zu funktionieren scheint, wenn Sie die neuen vektorzeichnbaren Typen als Quelle verwenden.
Michael De Soto
1
@haagmm Diese Lösung benötigt API> = 21. Auch funktioniert sie für Vektoren.
Neurotransmitter
1
Und es sollte nicht mit Vektoren funktionieren, das Root-Tag ist bitmap. Es gibt andere Möglichkeiten, Vektoren zu färben. Vielleicht könnte hier auch jemand eine
Vektorfärbung
11

Die meisten Lösungen in diesem Thread verwenden entweder eine neuere API oder Reflection oder verwenden eine intensive Ansichtssuche, um zum aufgeblasenen zu gelangen MenuItem .

Es gibt jedoch einen eleganteren Ansatz, um dies zu tun. Sie benötigen eine benutzerdefinierte Symbolleiste, da Ihr Anwendungsfall "Benutzerdefinierten Farbton anwenden" mit der öffentlichen Styling- / Theming-API nicht gut funktioniert.

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

}

Stellen Sie einfach sicher, dass Sie Ihren Aktivitäts- / Fragmentcode eingeben:

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

Keine Reflexion, keine Ansichtssuche und nicht so viel Code, oder?

Und jetzt können Sie das Lächerliche ignorieren onCreateOptionsMenu/onOptionsItemSelected.

Drew
quelle
Technisch gesehen führen Sie eine Ansichtssuche durch. Sie iterieren die Ansichten und stellen sicher, dass sie nicht null sind. ;)
Martin Marconcini
In gewisser Hinsicht haben Sie definitiv Recht :-) Trotzdem ist die Menu#getItem()Komplexität in der Symbolleiste O (1), da Elemente in ArrayList gespeichert sind. Was sich von der View#findViewByIdDurchquerung unterscheidet (die ich in meiner Antwort als Ansichtssuche bezeichnet habe), deren Komplexität bei weitem nicht konstant ist :-)
Drew
Tatsächlich stimmte ich zu, dass ich etwas sehr Ähnliches tat. Ich bin immer noch schockiert, dass Android das alles nach so vielen Jahren nicht rationalisiert hat…
Martin Marconcini
Wie kann ich mit diesem Ansatz die Farben des Überlaufsymbols und des Hamburger-Symbols ändern?
Sandra
8

Hier ist die Lösung, die ich benutze; Sie können es nach onPrepareOptionsMenu () oder dem entsprechenden Ort aufrufen. Der Grund für mutate () ist, wenn Sie die Symbole an mehreren Stellen verwenden. Ohne die Mutation werden sie alle den gleichen Farbton annehmen.

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

Der Überlauf wird dadurch nicht behoben, aber dafür können Sie Folgendes tun:

Layout:

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

Stile:

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

Dies funktioniert ab Appcompat v23.1.0.

Lernen Sie OpenGL ES
quelle