Изменение значка меню "Переполнение Android" программно

Я искал способ программного изменения цвета значка меню переполнения в android.

Единственный вариант, который я нашел, - это изменить значок навсегда, добавив собственный стиль. Проблема в том, что в ближайшем будущем нам нужно будет изменить это во время использования нашего приложения.

Наше приложение является расширением серии онлайн-платформ, и поэтому пользователь может ввести свой веб-URL своей платформы. Они имеют свои собственные стили и будут вызваны вызовом API к приложению.

Они могут обратиться ко мне, чтобы изменить цвет значка...

В настоящее время я меняю другие значки в панели действий следующим образом:

if (ib != null){
            Drawable resIcon = getResources().getDrawable(R.drawable.navigation_refresh);
            resIcon.mutate().setColorFilter(StyleClass.getColor("color_navigation_icon_overlay"), PorterDuff.Mode.SRC_ATOP);
            ib.setIcon(resIcon);
}

Теперь мне придется использовать стили.

Ответ 1

Фактически вы можете программно изменить значок переполнения, используя небольшой трюк. Вот пример:

Создать стиль для меню переполнения и передать описание содержимого

<style name="Widget.ActionButton.Overflow" parent="@android:style/Widget.Holo.ActionButton.Overflow">
    <item name="android:contentDescription">@string/accessibility_overflow</item>
</style>

<style name="Your.Theme" parent="@android:style/Theme.Holo.Light.DarkActionBar">
    <item name="android:actionOverflowButtonStyle">@style/Widget.ActionButton.Overflow</item>
</style>

Теперь вызовите ViewGroup.findViewsWithText и передайте описание своего содержимого. Итак, что-то вроде:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // The content description used to locate the overflow button
    final String overflowDesc = getString(R.string.accessibility_overflow);
    // The top-level window
    final ViewGroup decor = (ViewGroup) getWindow().getDecorView();
    // Wait a moment to ensure the overflow button can be located
    decor.postDelayed(new Runnable() {

        @Override
        public void run() {
            // The List that contains the matching views
            final ArrayList<View> outViews = new ArrayList<>();
            // Traverse the view-hierarchy and locate the overflow button
            decor.findViewsWithText(outViews, overflowDesc,
                    View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION);
            // Guard against any errors
            if (outViews.isEmpty()) {
                return;
            }
            // Do something with the view
            final ImageButton overflow = (ImageButton) outViews.get(0);
            overflow.setImageResource(R.drawable.ic_action_overflow_round_red);

        }

    }, 1000);
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Add a dummy item to the overflow menu
    menu.add("Overflow");
    return super.onCreateOptionsMenu(menu);
}

View.findViewsWithText был добавлен в уровне API 14, поэтому вам придется использовать свой собственный метод совместимости:

static void findViewsWithText(List<View> outViews, ViewGroup parent, String targetDescription) {
    if (parent == null || TextUtils.isEmpty(targetDescription)) {
        return;
    }
    final int count = parent.getChildCount();
    for (int i = 0; i < count; i++) {
        final View child = parent.getChildAt(i);
        final CharSequence desc = child.getContentDescription();
        if (!TextUtils.isEmpty(desc) && targetDescription.equals(desc.toString())) {
            outViews.add(child);
        } else if (child instanceof ViewGroup && child.getVisibility() == View.VISIBLE) {
            findViewsWithText(outViews, (ViewGroup) child, targetDescription);
        }
    }
}

Результаты

Example

Ответ 2

Аднеальный ответ велик, и я использовал его до недавнего времени. Но тогда я хотел, чтобы мое приложение использовало материальный дизайн и, таким образом, стиль Theme.AppCompat.* и android.support.v7.widget.Toolbar.

Да, он перестает работать, и я пытался его исправить, установив Your.Theme parent на @style/Widget.AppCompat.ActionButton.Overflow. Он работал путем правильной установки contentDescription, но затем он не удался при нажатии на ImageButton. Оказалось, что в последнем (version 23) android.support.v7 классе OverflowMenuButton выходит из AppCompatImageView. Изменение класса литья было достаточно, чтобы заставить его работать с Панели инструментов на Nexus 5, работающей с Lollipop.

Затем я запустил его на Galaxy S4 с KitKat, и независимо от того, что я пытался, я не мог установить переполнение contentDescription в свое пользовательское значение. Но в стилях AppCompat Я обнаружил, что уже имеет значение по умолчанию:

<item name="android:contentDescription">@string/abc_action_menu_overflow_description</item>

Так почему бы не использовать его? Также по идее Ханнеса (в комментариях) я реализовал прослушиватель, чтобы избавиться от некоторого случайного времени задержки в postDelayed. И поскольку значок переполнения уже находится в библиотеке AppCompat, я также буду использовать его - я применяю цветовой фильтр, поэтому мне не нужен какой-либо ресурс значков сам по себе.

Мой код, основанный на работе Adneal с улучшением Android Lollipop:

public static void setOverflowButtonColor(final Activity activity) {
    final String overflowDescription = activity.getString(R.string.abc_action_menu_overflow_description);
    final ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
    final ViewTreeObserver viewTreeObserver = decorView.getViewTreeObserver();
    viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            final ArrayList<View> outViews = new ArrayList<View>();
            decorView.findViewsWithText(outViews, overflowDescription,
                    View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION);
            if (outViews.isEmpty()) {
                return;
            }
            AppCompatImageView overflow=(AppCompatImageView) outViews.get(0);
            overflow.setColorFilter(Color.CYAN);
            removeOnGlobalLayoutListener(decorView,this);
        }
    });
}

и в соответствии с fooobar.com/questions/20225/...:

public static void removeOnGlobalLayoutListener(View v, ViewTreeObserver.OnGlobalLayoutListener listener) {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
        v.getViewTreeObserver().removeGlobalOnLayoutListener(listener);
    }
    else {
        v.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
    }
}

конечно, вместо Color.CYAN вы можете использовать свой собственный цвет - activity.getResources().getColor(R.color.black);

EDIT: Добавлена ​​поддержка последней библиотеки AppCompat (23), которая использует AppCompatImageView Для AppCompat 22 вы должны использовать кнопку переполнения для TintImageView

Ответ 3

В качестве поддержки 23.1 на панели инструментов теперь есть методы getOverflowIcon() и setOverflowIcon(), поэтому мы можем сделать это гораздо проще:

public static void setOverflowButtonColor(final Toolbar toolbar, final int color) {
    Drawable drawable = toolbar.getOverflowIcon();
    if(drawable != null) {
        drawable = DrawableCompat.wrap(drawable);
        DrawableCompat.setTint(drawable.mutate(), color);
        toolbar.setOverflowIcon(drawable);
    }
}

Ответ 4

Существует менее хеш-решение для изменения значка переполнения. Существует пример того, как изменить цвет значка переполнения, но вы можете адаптировать его для изменения изображения:

 private void setOverflowIconColor(int color) {
        Drawable overflowIcon = toolbar.getOverflowIcon();

        if (overflowIcon != null) {
            Drawable newIcon = overflowIcon.mutate();
            newIcon.setColorFilter(color, PorterDuff.Mode.MULTIPLY);
            toolbar.setOverflowIcon(newIcon);
        }
    }

Ответ 5

Существует гораздо лучшее решение. Вы можете сделать это программно во время выполнения

toolbar.overflowIcon?.setColorFilter(colorInt, PorterDuff.Mode.SRC_ATOP)

Viola!

Ответ 6

Основываясь на ответе @michalbrz, я использовал ниже, чтобы изменить сам значок.:)

public static void setOverflowButtonColor(final Activity activity) {
        final String overflowDescription = activity.getString(R.string.abc_action_menu_overflow_description);
        final ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
        final ViewTreeObserver viewTreeObserver = decorView.getViewTreeObserver();
        viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
        public void onGlobalLayout() {
            final ArrayList<View> outViews = new ArrayList<View>();
            decorView.findViewsWithText(outViews, overflowDescription,
                    View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION);
            if (outViews.isEmpty()) {
                return;
            }
            TintImageView overflow = (TintImageView) outViews.get(0);
            //overflow.setColorFilter(Color.CYAN); //changes color
            overflow.setImageResource(R.drawable.dots);
            removeOnGlobalLayoutListener(decorView, this);
        }
    });

Ответ 7

Используя appcompat-v7: 23.0.1, ни один из @adneal или @michalbrz не работал у меня. Мне пришлось изменить 2 строки кода ответа @michalbrz, чтобы они работали.

Я добавляю ответ, потому что оба текущих ответа могут быть полезны для кого-то, но если вы используете последнюю версию appcompat, такую ​​как я, вы должны использовать ее на основе @michalbrz:

private static void setOverflowButtonColor(final AppCompatActivity activity, final PorterDuffColorFilter colorFilter) {
    final String overflowDescription = activity.getString(R.string.abc_action_menu_overflow_description);
    final ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
    final ViewTreeObserver viewTreeObserver = decorView.getViewTreeObserver();
    viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            final ArrayList<View> outViews = new ArrayList<>();
            decorView.findViewsWithText(outViews, overflowDescription,
                    View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION);
            if (outViews.isEmpty()) {
                return;
            }
            ActionMenuItemView overflow = (ActionMenuItemView)outViews.get(0);
            overflow.getCompoundDrawables()[0].setColorFilter(colorFilter);
            removeOnGlobalLayoutListener(decorView,this);
        }
    });
}

Используя код michalbrz, я получал эту ошибку:

java.lang.ClassCastException: android.support.v7.internal.view.menu.ActionMenuItemView cannot be cast to android.support.v7.internal.widget.TintImageView

Итак, после копания немного в ActionMenuItemView -коде, я нашел, как получить пиктограмму (смотри в setIcon()), затем я просто изменил кастинг на ActionMenuItemView, примененный цветовой фильтр, чтобы левый drawable получил от getCompoundDrawables() и Voila! он работает!

Ответ 8

Ребята, я сделал это простым способом. Посмотрите на этот фрагмент следующим образом:

@Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_dashboard, menu);
        MenuItem item = menu.findItem(R.id.help);
        Drawable drawable = item.getIcon();
        drawable.setColorFilter(Color.RED, PorterDuff.Mode.SRC_ATOP);
        return super.onCreateOptionsMenu(menu);
    }

Пожалуйста, дайте мне знать, если есть какие-либо разъяснения здесь.