Тонування MenuItem на панелі інструментів AppCompat


93

Коли я використовую малюнки з AppCompatбібліотеки для своїх Toolbarпунктів меню, тонування працює належним чином. Подобається це:

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

Але якщо я використовую власні малюнки або навіть фактично скопіюю малюнки з AppCompatбібліотеки у свій власний проект, це взагалі не забарвиться.

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

Чи є якась особлива магія в тому, AppCompat Toolbarщо лише відтінки малюнків із цієї бібліотеки? Будь-який спосіб змусити це працювати зі своїми власними малюнками?

Запуск цього на пристрої API рівня 19 з compileSdkVersion = 21та targetSdkVersion = 21, а також використання всього відAppCompat

abc_ic_clear_mtrl_alpha_copyє точною копією abc_ic_clear_mtrl_alphaPNG ізAppCompat

Редагувати:

Тонування базується на значенні, яке я встановив android:textColorPrimaryу своїй темі.

Наприклад, <item name="android:textColorPrimary">#00FF00</item>це дало б мені зелений відтінок.

Знімки екрана

Тонування працює, як очікувалося, з можливістю малювання з AppCompat Тонування працює, як очікувалося, з можливістю малювання з AppCompat

Тонування не працює з копіюваним з AppCompat малюнком Тонування не працює з копіюваним з AppCompat малюнком


В обох стилях однаковий батько? Що робити, якщо ви подовжите топ-стиль своїм?
G_V

У стилях немає різниці. Єдина відмінність - це файли у
форматі

Малюнок, що малюється, виглядає як точна копія оригінального AppCombat, який можна малювати в коді?
G_V

Це файли png, які я скопіював. Вони абсолютно однакові.
greve

То де саме ваш код відрізняється від оригіналу, якщо він має однаковий стиль і однакове зображення?
G_V

Відповіді:


31

Тому що, якщо ви подивитесь на вихідний код TintManager в AppCompat, ви побачите:

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

Що в значній мірі означає, що вони мають конкретні ідентифікатори ресурсів, включені до білого списку, щоб бути тонованими.

Але я думаю, ви завжди можете побачити, як вони тонують ці зображення, і зробити те саме. Це так само просто, як встановити ColorFilter на висувний.


Тьфу, цього я боявся. Я не знайшов вихідний код для AppCompat у SDK, тому я сам не знайшов цю частину. Думаю, мені тоді доведеться переглянути на googlesource.com. Дякую!
greve

8
Я знаю, що це дотичне питання, але чому існує білий список? Якщо він може виконувати тонування за допомогою цих значків, то чому ми не можемо тонувати наші власні значки? Плюс, який сенс робити майже все зворотньо сумісним (з AppCompat), коли ви залишаєте одне з найважливіших: наявність піктограм на панелі дій (зі спеціальним кольором).
Zsolt Safrany

1
Існує проблема з цим в програмі відстеження випусків Google, яку позначено як виправлену, але вона не працює для мене, але ви можете відслідковувати її тут: issueetracker.google.com/issues/37127128
niknetniko

Вони стверджують, що це виправлено, але ні. Боже, я огидую тематичний движок для Android, AppCompat та все те, що з цим пов’язано. Він працює лише для зразків додатків “Github repo browser”.
Martin Marconcini

97

Після нової бібліотеки підтримки v22.1 ви можете використовувати щось подібне до цього:

  @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
Я б сказав, що в цьому випадку старіше setColorFilter()переважно.
Натаріо,

@mvai, чому setColorFilter () є кращим?
wilddev

4
@wilddev стислість. Навіщо турбувати службу підтримки DrawableCompat, коли ви можете перейти до меню.findItem (). GetIcon (). SetColorFilter ()? Один лайнер і прозорий.
Натаріо,

4
Аргумент з одним вкладишем не має значення, коли ви абстрагуєте всю логіку у свій власний метод TintingUtils.tintMenuIcon (...) або як ви хочете його назвати. Якщо вам потрібно змінити або відрегулювати логіку в майбутньому, ви робите це в одному місці, а не в усій програмі.
Dan Dar3,

1
Це круто!
шалений іслам,

82

Встановити ColorFilter(відтінок) на a MenuItemдуже просто. Ось приклад:

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

Наведений вище код дуже корисний, якщо ви хочете підтримувати різні теми, і ви не хочете мати додаткових копій лише для кольору або прозорості.

Клацніть тут, щоб допоміжний клас встановив aColorFilter для всіх малюнків у меню, включаючи піктограму переповнення.

В onCreateOptionsMenu(Menu menu)тільки виклик MenuColorizer.colorMenu(this, menu, color);після накачування вашого меню і вуаля; ваші іконки тоновані.


Дякую, неодмінно спробую це!
greve

3
Я вдарився головою об стіл, намагаючись зрозуміти, чому всі мої значки тоновані, дякую за голови щодо drawable.mutate ()!
Скотт Купер

49

app:iconTintатрибут реалізований у SupportMenuInflaterз бібліотеки підтримки (принаймні в 28.0.0).

Успішно протестовано з API 15 та новіших версій.

Файл ресурсу меню:

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

(У цьому випадку ?attr/appIconColorEnabledв темах програми був атрибут власного кольору, а ресурси піктограм - векторні малюнки.)


5
Це має бути новою прийнятою відповіддю! Також, будь ласка, зверніть увагу android:iconTintі android:iconTintModeне працюйте, але префікс app:замість android:працює як шарм (на моїх власних векторних малюнках, API> = 21)
Себастіан Альварес Родрігес

Якщо телефонувати програмно: зверніть увагу, що SupportMenuInflaterне застосовуватиметься будь-яка спеціальна логіка, якщо меню не SupportMenuсхоже на лайк MenuBuilder, воно просто повертається до звичайного MenuInflater.
geekley

У цьому випадку використання AppCompatActivity.startSupportActionMode(callback)та відповідні реалізації підтримки з androidx.appcompatбудуть передані у зворотний виклик.
geekley

30

Я особисто віддав перевагу такому підходу з цього посилання

Створіть XML-макет із наступним:

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

і зверніться до цього малюнка зі свого меню:

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

2
Хоча це посилання може відповісти на питання, краще включити сюди основні частини відповіді та надати посилання для довідки. Відповіді лише на посилання можуть стати недійсними, якщо пов’язана сторінка зміниться.
tomloprod

Дякую за ваш коментар. Я відредагував запитання. @tomloprod
N Jay

4
Це моє найкраще рішення. Однак важливо зазначити, що наразі це рішення не працює, коли ви використовуєте нові векторні типи, які можна малювати як джерело.
Майкл Де Сото

1
@haagmm для цього рішення потрібен API> = 21. Також воно працює і для векторів.
Нейромедіатор

1
І це не повинно працювати з векторами, кореневий тег є bitmap. Є й інші способи кольорових векторів. Можливо, хтось міг би додати векторну забарвлення і тут ...
milosmns

11

Більшість рішень у цьому потоці або використовують новіший API, або використовують відображення, або використовують інтенсивний пошук подання, щоб дістатися до завищеного MenuItem.

Однак для цього існує більш елегантний підхід. Вам потрібна власна панель інструментів, оскільки ваш варіант використання "застосувати спеціальний відтінок" погано поєднується з API загальнодоступних стилів / тематики.

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

}

Просто переконайтеся, що ви зателефонували до коду Activity / Fragment:

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

Ніякого роздуму, жодного перегляду перегляду і не так багато коду, так?

І тепер ви можете ігнорувати смішне onCreateOptionsMenu/onOptionsItemSelected.


Технічно ви виконуєте пошук виду. Ви повторюєте подання та переконуєтесь, що вони не є нульовими. ;)
Мартін Маркончіні

Ви точно маєте рацію :-) Тим не менше, Menu#getItem()складність на панелі інструментів - O (1), оскільки елементи зберігаються в ArrayList. Що відрізняється від View#findViewByIdобходу (який у своїй відповіді я назвав пошуком перегляду ), складність якого далеко не є постійною :-)
Дрю

Погоджений, насправді я зробив дуже подібну річ. Я все ще вражений тим, що Android не спростив усе це через стільки років ...
Мартін Маркончіні

Як я можу змінити піктограму переповнення та кольори піктограми гамбургера за допомогою цього підходу?
Сандра

8

Ось рішення, яке я використовую; ви можете зателефонувати йому після onPrepareOptionsMenu () або еквівалентного місця. Причиною мутації () є те, що випадково ви використовуєте піктограми в кількох місцях; без мутації всі вони набудуть однаковий відтінок.

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

Це не подбає про переповнення, але для цього ви можете зробити це:

Макет:

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

Стилі:

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

Це працює з appcompat v23.1.0.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.