以编程方式更改Android溢出菜单图标

17

我一直在寻找一种以编程方式更改安卓溢出菜单图标颜色的方法。

我找到的唯一选择是通过添加自定义样式来永久更改图标。问题是,将来我们需要在应用程序使用期间更改它。

我们的应用程序是在线平台系列的扩展,因此用户可以输入其平台的 Web URL。这些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);
}

现在我必须使用这些样式。


我认为唯一的方法是使用自定义样式。我认为您无法通过编程方式更改它。 - Robby Pond
我有点有同样的感觉,只是希望我错了。 - Mathijs Segers
10个回答

33

你实际上可以使用一个小技巧以编程方式更改溢出图标。以下是一个示例:

创建一个用于溢出菜单的样式并传递内容描述

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

结果

示例


1
@MathijsSegers 我编辑了我的答案,最近意识到你可以动态更改它。 - adneal
有趣,我会研究一下,但我不知道我是否会需要它,但有可能需要。谢谢! - Mathijs Segers
你可以使用 ViewTreeObserver.OnPreDrawListener 来代替超时:https://dev59.com/J2855IYBdhLWcg3wXC5- - Hannes Struß
这样糟糕的编程调用延迟方法(1秒钟后)。 - user25

22

Adneal的回答很好,我一直在使用它,直到最近。但是,我想让我的应用程序使用Material Design,因此需要使用Theme.AppCompat.*样式和android.support.v7.widget.Toolbar

是的,它停止工作了,我试图通过将Your.Theme的父级设置为@style/Widget.AppCompat.ActionButton.Overflow来修复它。通过正确设置contentDescription,它可以正常工作,但是在转换为ImageButton时失败了。原来在最新的(version 23) android.support.v7类中,OverflowMenuButton扩展自AppCompatImageView。更改强制转换类就足以使其在运行Lollipop的Nexus 5上与Toolbar一起工作。

然后我在装有KitKat的Galaxy S4上运行它,无论我尝试什么,都无法将overflow的contentDescription设置为我的自定义值。但是在AppCompat styles中,我发现它已经有默认值:

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

那么为什么不使用它呢?此外,根据Hannes的想法(在评论中),我实现了监听器,以消除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);
        }
    });
}

根据另一个StackOverflow答案

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);”
编辑: 添加了对最新的AppCompat库(23)的支持,它使用AppCompatImageView 对于AppCompat 22,您应该将溢出按钮转换为TintImageView

1
太棒了!谢谢 :) - Peter

21

从23.1版本开始,Toolbar现在有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);
    }
}

我已经更新到23.1版本并尝试了您的代码,但它对我来说不起作用:S。是否有其他建议或帮助来实现它? - MiguelHincapieC
嗯...我已经测试过它在运行6.0、5.0.1和4.4.4的多个设备上工作正常。我的targetSdkVersion设置为23。 - Lorne Laliberte
尝试了你在我的答案中提出的建议以及这个方法,但在4.2.2设备上没有成功...我错过了什么 O_O' - MiguelHincapieC
1
@0mahc0,你已经更新到23.1了,但是你是否也将编译器选项/targetSdkVersion设置为23?我实际上将appcompat和其他库构建到23而不是19(通过更改它们的project.properties、构建设置等目标)。除此之外,你确定你实际上在布局中使用的是工具栏,而不是支持操作栏吗?(如果你正在从旧代码中工作,那么可能活动仍然在使用操作栏,定义了一个未使用的工具栏。你在运行时看到的实际上取决于你在代码中为活动分配的内容。) - Lorne Laliberte
你猜得对,我将会更新所有这些东西。 - MiguelHincapieC
显示剩余5条评论

13

有更好的解决方案。您可以在运行时以编程方式完成。

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

威拉!


当然现在有了,不过这个问题已经三年了,是KitKat之前的 :-)。那时候我想我们还没有使用工具栏。 - Mathijs Segers
我希望我能给你十个大拇指。我已经为解决这个问题工作了数小时。有些人建议创建自己的三点图标,并将一个设置为一种颜色,另一个设置为另一种颜色,并在它们之间进行切换。这是绝对过火的。答案竟然是非常简单的东西。 - Peter Griffin

7

有一种不太糟糕的解决方法可以更改溢出图标。以下是一个示例,说明如何更改溢出图标的颜色,但您可以将其适应于更改图像:

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

1
FYI,我相信这需要 API v23。 - John Gibb
@JohnGibb 不是的,已经在API 19级别上进行了测试。 - artem
1
在此处显示“API级别23中添加”的内容:https://developer.android.com/reference/android/widget/Toolbar.html#setOverflowIcon(android.graphics.drawable.Drawable)。同时在Android Studio中给了我一个警告。 - John Gibb
1
@JohnGibb 确保你正在使用 Toolbar 的 AppCompat 版本。https://developer.android.com/reference/android/support/v7/widget/Toolbar.html#setOverflowIcon(android.graphics.drawable.Drawable) - artem

1

大家好,我用简单的方式完成了这个任务,请看以下代码片段:

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

如果还需要更多澄清,请告诉我。


1
这是用于更改菜单项颜色的,而不是溢出图标。 - micwallace

1
不需要创建新的样式资源,只需使用setOverflowIcon(drawable)方法将所需图标传递给工具栏对象即可。请注意,保留html标签。
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
       toolbar.setOverflowIcon(getResources().getDrawable(R.drawable.ic_notifications_black_24dp));

1
除此之外,这篇文章已经四年了,我需要一个不需要我将所有可能的颜色图标添加到我的应用程序中的解决方案。这是被支持的。 - Mathijs Segers
但是兄弟,这是溢出图标,不是你必须在每个活动中更改的颜色。另外,在动态情况下,如果我的主题是“Theme.AppCompat.Light.DarkActionBar”,那么它就找不到“holo actionbar”主题。所以请好好考虑一下。 - Sumit Monapara
主题基于从 API 获取的颜色字符串。再次强调,您的答案没有回答实际问题。 - Mathijs Segers
没问题,因为它在我的应用程序中完美运行。谢谢。 - Sumit Monapara

1

根据@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);
        }
    });

1

使用appcompat-v7:23.0.1,@adneal或@michalbrz的方法都对我无效。我必须更改@michalbrz答案的2行代码才能使其工作。

我添加了一个答案,因为当前的两个答案对某些人可能很有用,但如果你像我一样使用最新的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,对从getCompoundDrawables()获取的左侧可绘制对象应用颜色过滤器,就完成了!


这是有道理的,因为它是一个新的支持库,做着原始问题所没有的其他事情。但这可能对许多人很有价值。 - Mathijs Segers
这又不行了!从23.1开始,你需要将其转换为“ImageView”,并使用“overflow.setColorFilter(colorFilter)”来解决问题。(该按钮现在被实现为“ActionMenuPresenter.OverflowMenuButton”,但是它是私有的。) - Lorne Laliberte
哎呀!他们改了这么多:S,我要试着更新答案(或者如果你想的话修改它) - MiguelHincapieC
实际上,自23.1版本以来,有一种更简单的方法可以实现,因为他们添加了getOverflowIcon()和setOverflowIcon()函数。即便如此,我还是给你的答案点了赞,因为在它存在期间,我发现它很有用。 :) - Lorne Laliberte
谢谢@Lorne Laliberte,我正在尝试使用toolbar.getOverflowIcon().setColorFilter(),但它不起作用,你能给我一些建议吗?顺便说一下,我已经更新到23.1了,我的代码仍然可以工作,但会有一个关于私有字符串资源的警告... 我刚刚意识到了你的答案哈哈哈;) - MiguelHincapieC
你是否也在调用 toolbar.setOverflowIcon() 来更新可绘制对象?类似 toolbar.setOverflowIcon(toolbar.getOverflowIcon().mutate().setColorFilter(colorFilter)) 的代码可能更接近你所需的。你可能想要使用 setColorFilter 的重载方法,让你传递过滤器,例如,这样你可以让它使用 PorterDuff.Mode.SRC_IN 而不是 SRC_ATOP(通常更适合此操作,但这取决于你想要做什么)。 - Lorne Laliberte

0

toolBar.overflowIcon?.setTint(Color.WHITE)

在 Kotlin 中,你可以随意更改任何颜色 :)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接