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

95

Когда я использую чертежи из 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

Тонирование не работает с drawable, скопированным из AppCompat Тонирование не работает с drawable, скопированным из AppCompat

Greve
источник
У обоих стилей один и тот же родительский элемент? Что, если дополнить верхний стиль своим собственным?
G_V 06
Никакой разницы в стилях нет. Единственное отличие - это файлы с
расширением
Drawable выглядит как точная копия оригинального AppCombat, который можно рисовать в коде?
G_V 06
Это файлы png, которые я скопировал. Они точно такие же.
greve
Так где же ваш код отличается от оригинала, если он имеет тот же стиль и такое же изображение?
G_V 06

Ответы:

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 на чертеже.

EvilDuck
источник
Ух, вот чего я боялся. Исходный код AppCompat в SDK я не нашел, поэтому сам не нашел эту часть. Думаю, тогда мне придется зайти на googlesource.com. Благодарность!
greve
8
Я знаю, что это косвенный вопрос, но почему там белый список? Если он может тонировать с помощью этих значков, почему мы не можем тонировать наши собственные значки? Кроме того, какой смысл делать почти все обратно совместимым (с AppCompat), если вы упускаете одну из самых важных вещей: наличие значков панели действий (с настраиваемым цветом).
Zsolt Safrany
1
Для этого есть проблема в трекере проблем Google, которая была помечена как исправленная, но у меня она не работает, но вы можете отслеживать ее здесь: Issues/37127128
niknetniko
Они утверждают, что это исправлено, но это не так. Черт возьми, я ненавижу движок тем Android, AppCompat и всю чушь, связанную с ним. Он работает только для примеров приложений «Github repo browser».
Мартин Маркончини
98

После новой библиотеки поддержки 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, если вы можете использовать menu.findItem (). GetIcon (). SetColorFilter ()? Один лайнер и прозрачный.
Натарио
5
Однострочный аргумент не имеет значения, когда вы абстрагируете всю логику в своем собственном методе TintingUtils.tintMenuIcon (...) или как вы хотите его назвать. Если вам понадобится изменить или скорректировать логику в будущем, вы делаете это в одном месте, а не во всем приложении.
Дэн Дар3
Нет необходимости подкрашивать разную логику и не менять в будущем.
Джемшит Искендеров
84

Установить 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);
}

Приведенный выше код очень полезен, если вы хотите поддерживать разные темы и не хотите иметь дополнительные копии только для цвета или прозрачности.

Щелкните здесь, чтобы получить вспомогательный класс, чтобы установить егоColorFilterдля всех элементов меню, включая значок переполнения.

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

Джаред Раммлер
источник
4
Я бился головой о стол, пытаясь понять, почему все мои значки окрашиваются, спасибо за внимание по поводу drawable.mutate ()!
Скотт Купер
52

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"
N Джей
источник
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)
        );
    }

}

Просто убедитесь, что вы вызываете свой код активности / фрагмента:

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

Без отражения, без просмотра и не так много кода, да?

А теперь можно игнорировать нелепое onCreateOptionsMenu/onOptionsItemSelected.

Нарисовался
источник
Технически вы выполняете поиск по представлению. Вы повторяете представления и гарантируете, что они не равны нулю. ;)
Мартин Маркончини
В каком-то смысле вы определенно правы :-) Тем не менее, Menu#getItem()сложность на панели инструментов равна O (1), потому что элементы хранятся в ArrayList. Это отличается от View#findViewByIdобхода (который я назвал в своем ответе поиском представления ), сложность которого далеко не постоянна :-)
Дрю
Согласился, на самом деле я сделал очень похожую вещь. Я все еще шокирован тем, что Android не упростил все это после стольких лет…
Мартин Маркончини,
Как я могу изменить цвета значка переполнения и значка гамбургера с помощью этого подхода?
Sandra
8

Вот решение, которое я использую; вы можете вызвать его после onPrepareOptionsMenu () или аналогичного места. Причина использования mutate () заключается в том, что вы используете значки более чем в одном месте; без мутации все они приобретут одинаковый оттенок.

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.

Изучите OpenGL ES
источник
2

Это сработало для меня:

override fun onCreateOptionsMenu(menu: Menu?): Boolean {

        val inflater = menuInflater
        inflater.inflate(R.menu.player_menu, menu)

        //tinting menu item:
        val typedArray = theme.obtainStyledAttributes(IntArray(1) { android.R.attr.textColorSecondary })
        val textColor = typedArray.getColor(0, 0)
        typedArray.recycle()

        val item = menu?.findItem(R.id.action_chapters)
        val icon = item?.icon

        icon?.setColorFilter(textColor, PorterDuff.Mode.SRC_IN);
        item?.icon = icon
        return true
    }

Или вы можете использовать оттенок в drawable xml:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:tint="?android:textColorSecondary"
    android:viewportWidth="384"
    android:viewportHeight="384">
    <path
        android:fillColor="#FF000000"

        android:pathData="M0,277.333h384v42.667h-384z" />
    <path
        android:fillColor="#FF000000"
        android:pathData="M0,170.667h384v42.667h-384z" />
    <path
        android:fillColor="#FF000000"
        android:pathData="M0,64h384v42.667h-384z" />
</vector>
Селена Смиртин
источник