【问题标题】:Changing the Android Overflow menu icon programmatically以编程方式更改 Android 溢出菜单图标
【发布时间】:2014-03-29 14:33:33
【问题描述】:

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

我发现的唯一选择是通过添加自定义样式永久更改图标。 问题是在不久的将来我们将需要在使用我们的应用程序期间更改此设置。

我们的应用是一系列在线平台的扩展,因此用户可以输入他们平台的网络网址。它们有自己的样式,将通过对应用的 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);
}

现在我必须使用样式。

【问题讨论】:

  • 我相信唯一的方法是使用自定义样式。我不相信您可以以编程方式更改它。
  • 我也有同样的感觉,只是希望我错了。

标签: android android-actionbar


【解决方案1】:

toolBar.overflowIcon?.setTint(Color.WHITE)

在科特林中, 改变你想要的任何颜色:)

【讨论】:

    【解决方案2】:

    不需要创建新的样式资源,只需将 setOvewflowIcon(drawable) 方法传递给工具栏对象,然后将要使用的可绘制对象作为图标传递

    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);
           toolbar.setOverflowIcon(getResources().getDrawable(R.drawable.ic_notifications_black_24dp));
    

    【讨论】:

    • 除了这篇文章已经有 4 年历史了,我需要一个不需要我将所有可能的颜色图标添加到我的应用程序的解决方案。支持哪个。
    • 但是兄弟,这是溢出图标,不是每次活动都必须更改的颜色。和其他动态如果我的主题是“Theme.AppCompat.Light.DarkActionBar”,那么它找不到“holo actionbar”主题。所以想想吧。
    • 主题基于从 API 获取的颜色字符串。同样,您的回答并没有回答实际问题。
    • 没事没问题。因为它在我的应用程序中完美运行。谢谢。
    【解决方案3】:

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

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

    维奥拉!

    【讨论】:

    • 当然有,但是这个问题已经存在三年了,在 KitKat 之前 :-)。我相信当时我们还没有使用工具栏。
    • 我希望我能给你 10 个赞。我已经研究这个问题几个小时了。有人说要创建自己的三点图标,一组带有一种颜色,一组带有另一种颜色,并在它们之间进行交换。绝对矫枉过正。答案原来如此简单。
    【解决方案4】:

    各位大佬,我做的很简单,请看这个sn-p如下:

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

    如果这里有任何进一步的说明,请告诉我。

    【讨论】:

    • 这是用于更改菜单项颜色,而不是溢出图标
    【解决方案5】:

    更改溢出图标的方法更简单。有一个示例如何更改溢出图标的颜色,但您可以对其进行调整以更改图像:

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

    【讨论】:

    【解决方案6】:

    Adneal 的回答很棒,直到最近我还在使用它。但后来我希望我的应用程序能够使用材料设计,从而使用 Theme.AppCompat.* 样式和 android.support.v7.widget.Toolbar

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

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

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

    那么为什么不使用它呢?同样通过 Hannes 的想法(在 cmets 中),我实现了侦听器,以消除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);
            }
        });
    }
    

    根据another StackOverflow answer:

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

    编辑: 添加了对使用 AppCompatImageView 的最新 AppCompat 库 (23) 的支持 对于 AppCompat 22,您应该将溢出按钮投射到 TintImageView

    【讨论】:

    • 太棒了!谢谢:)
    【解决方案7】:

    从支持 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);
        }
    }
    

    【讨论】:

    • 我更新到 23.1 并尝试了您的代码,但它对我不起作用:S。有什么额外的建议或帮助来实现它吗?
    • 嗯...我已经在运行 6.0、5.0.1 和 4.4.4 的多个设备上对其进行了测试。我将 targetSdkVersion 设置为 23。
    • 试过你的建议(在我的回答中)和这个在 4.2.2 设备上没有成功......我错过了什么 O_O'
    • @0mahc0 您已更新到 23.1,但您是否也将编译器选项/targetSdkVersion 设置为 23?实际上,我将 appcompat 和其他库构建为 23 个而不是 19 个(通过更改其 project.properties、构建设置等中的目标)。除此之外,您确定您实际上是在布局中使用工具栏,而不是支持操作栏吗? (如果您使用旧代码工作,则活动可能仍在使用操作栏,工具栏已定义但未使用。您在运行时实际看到的工具栏将取决于您在代码中为活动分配的内容。)
    • 猜你是对的,我会更新所有这些东西。
    【解决方案8】:

    使用 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() 和瞧!它有效!

    【讨论】:

    • 是有道理的,因为它是一个更新的支持库,可以像原始问题一样做其他事情。但这对许多人来说可能是有价值的。
    • 从 23.1 开始,这不起作用(又一次!),您需要转换为 ImageView 并改用 overflow.setColorFilter(colorFilter)。 (该按钮现在实现为 ActionMenuPresenter.OverflowMenuButton,但具有私有访问权限。)
    • 天啊!!他们改变了很多:S,我会尝试更新答案(或者如果你想修改它)
    • 实际上,从 23.1 开始,有一种更简单的方法可以做到这一点,因为他们已经添加了 getOverflowIcon() 和 setOverflowIcon() - 但即便如此,我还是赞成你的答案,因为我发现它很有帮助持续了。 :)
    • 谢谢@Lorne Laliberte,我正在尝试toolbar.getOverflowIcon().setColorFilter(),但它不起作用,你能给我一些建议吗?顺便说一句,我更新到 23.1,我的代码仍在处理有关私有字符串资源的警告.... NVM 我刚刚意识到你的答案哈哈哈 ;)
    【解决方案9】:

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

    【讨论】:

      【解决方案10】:

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

      为溢​​出菜单创建样式并传入内容描述

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

      结果

      【讨论】:

      • @MathijsSegers 我编辑了我的答案,最近意识到你可以动态改变它。
      • 有趣,我会研究一下,但我不知道我是否会需要它,但我有机会这样做。谢谢!
      • 你也可以使用ViewTreeObserver.OnPreDrawListener:stackoverflow.com/questions/4393612/…来代替超时
      • 这么糟糕的编程调用延迟方法(1秒内)
      猜你喜欢
      • 1970-01-01
      • 2023-03-05
      • 1970-01-01
      • 2011-10-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多