代码之家  ›  专栏  ›  技术社区  ›  Nicolas

上溢菜单和子菜单中的着色菜单图标

  •  3
  • Nicolas  · 技术社区  · 6 年前

    我设法在工具栏的溢出菜单和子菜单中显示图标,但找不到如何根据图标的位置对其着色。这里是我使用的代码:

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.toolbar_main, menu);
    
        // Show icons in overflow menu
        if (menu instanceof MenuBuilder) {
            MenuBuilder m = (MenuBuilder) menu;
            m.setOptionalIconsVisible(true);
        }
    
        // Change icons color
        changeIconsColor(menu, colorNormal, colorInMenu, false);
    
        return super.onCreateOptionsMenu(menu);
    }
    
    public static void changeIconsColor(Menu menu, int colorNormal, int colorInMenu, boolean isInSubMenu) {
        // Change icons color
        for (int i = 0; i < menu.size(); i++) {
            MenuItem item = menu.getItem(i);
            Drawable icon = item.getIcon();
            if (icon != null) {
                int color = (((MenuItemImpl) item).requiresActionButton() ? colorNormal : colorInMenu);
                icon.setColorFilter(color, PorterDuff.Mode.SRC_IN);
                icon.setAlpha(item.isEnabled() ? 255 : 128);
            }
    
            if (item.hasSubMenu()) {
                changeIconsColor(item.getSubMenu(), colorNormal, colorInMenu, true);
            }
        }
    }
    

    使用 MenuItem.requiresActionButton() 允许知道项目是否具有 never always showAsAction 属性,但如果它具有 ifRoom 价值。因此,我不能使用 伊弗洛姆 物品的价值如果我想要合适的颜色,这是非常限制性的。

    • 有没有办法在所有情况下正确地给菜单项着色?

    • 更重要的是,有没有一种内置的方法可以用主题或样式给项目着色,从而避免我使用这段复杂的代码?即使解决方案不包括溢出菜单中的图标,我想知道它。

    如果没有其他方法,我完全可以使用反射。

    2 回复  |  直到 6 年前
        1
  •  2
  •   Jared Rummler mehmoodnisar125    6 年前

    很遗憾,无法使用主题或样式设置菜单项图标颜色的色调。你需要一种方法来检查 MenuItem ActionBar 或者在溢出菜单中。本地人和支持者 MenuItemImpl 类对此有一个方法,但它们要么被限制在库中,要么被隐藏。这需要反思。可以使用以下方法检查菜单项是否可见,然后设置颜色过滤器:

    public static boolean isActionButton(@NonNull MenuItem item) {
      if (item instanceof MenuItemImpl) {
        return ((MenuItemImpl) item).isActionButton();
      } else {
        // Not using the support library. This is a native MenuItem. Reflection is needed.
        try {
          Method m = item.getClass().getDeclaredMethod("isActionButton");
          if (!m.isAccessible()) m.setAccessible(true);
          return (boolean) m.invoke(item);
        } catch (Exception e) {
          return false;
        }
      }
    }
    

    您还需要等到菜单充气后再给项目上色。要做到这一点,你可以得到一个参考 动作栏 染红 菜单项 动作栏 已经画好了。

    例子:

    @Override public boolean onCreateOptionsMenu(Menu menu) {
      getMenuInflater().inflate(R.menu.menu_main, menu);
    
      int id = getResources().getIdentifier("action_bar", "id", "android");
      ViewGroup actionBar;
      if (id != 0) {
        actionBar = (ViewGroup) findViewById(id);
      } else {
        // You must be using a custom Toolbar. Use the toolbar view instead.
        // actionBar = yourToolbar
      }
    
      actionBar.post(new Runnable() {
        @Override public void run() {
          // Add code to tint menu items here 
        }
      });
    
      return super.onCreateOptionsMenu(menu);
    }
    

    下面是我为帮助给菜单项图标着色而编写的类: https://gist.github.com/jaredrummler/7816b13fcd5fe1ac61cb0173a1878d4f

        2
  •  1
  •   Nicolas    6 年前

    感谢@jaredrummler,我找到了一种方法来确定图标是否在溢出菜单中。我在这里发布了完整的代码,收集了他的答案。我还添加了一个助手方法来获得正确的图标颜色。以下是我目前使用的:

    主题词

    public final class ThemeUtils {
    
        /**
         * Obtain colors of a context's theme from attributes
         * @param context    themed context
         * @param colorAttrs varargs of color attributes
         * @return array of colors in the same order as the array of attributes
         */
        public static int[] getColors(Context context, int... colorAttrs) {
            TypedArray ta = context.getTheme().obtainStyledAttributes(colorAttrs);
    
            int[] colors = new int[colorAttrs.length];
            for (int i = 0; i < colorAttrs.length; i++) {
                colors[i] = ta.getColor(i, 0);
            }
    
            ta.recycle();
    
            return colors;
        }
    
        /**
         * Get the two colors needed for tinting toolbar icons
         * The colors are obtained from the toolbar's theme and popup theme
         * These themes are obtained from {@link R.attr#toolbarTheme} and {@link R.attr#toolbarPopupTheme}
         * The two color attributes used are:
         * - {@link android.R.attr#textColorPrimary} for the normal color
         * - {@link android.R.attr#textColorSecondary} for the color in a menu
         * @param context activity context
         * @return int[2]{normal color, color in menu}
         */
        public static int[] getToolbarColors(Context context) {
            // Get the theme and popup theme of a toolbar
            TypedArray ta = context.getTheme().obtainStyledAttributes(
                    new int[]{R.attr.toolbarTheme, R.attr.toolbarPopupTheme});
            Context overlayTheme = new ContextThemeWrapper(context, ta.getResourceId(0, 0));
            Context popupTheme = new ContextThemeWrapper(context, ta.getResourceId(1, 0));
            ta.recycle();
    
            // Get toolbar colors from these themes
            int colorNormal = ThemeUtils.getColors(overlayTheme, android.R.attr.textColorPrimary)[0];
            int colorInMenu = ThemeUtils.getColors(popupTheme, android.R.attr.textColorSecondary)[0];
    
            return new int[]{colorNormal, colorInMenu};
        }
    
        /**
         * Change the color of the icons of a menu
         * Disabled items are set to 50% alpha
         * @param menu        targeted menu
         * @param colorNormal normal icon color
         * @param colorInMenu icon color for popup menu
         * @param isInSubMenu whether menu is a sub menu
         */
        private static void changeIconsColor(View toolbar, Menu menu, int colorNormal, int colorInMenu, boolean isInSubMenu) {
            toolbar.post(() -> {
                // Change icons color
                for (int i = 0; i < menu.size(); i++) {
                    MenuItem item = menu.getItem(i);
                    changeMenuIconColor(item, colorNormal, colorInMenu, isInSubMenu);
    
                    if (item.hasSubMenu()) {
                        changeIconsColor(toolbar, item.getSubMenu(), colorNormal, colorInMenu, true);
                    }
                }
            });
        }
    
        public static void changeIconsColor(View toolbar, Menu menu, int colorNormal, int colorInMenu) {
            changeIconsColor(toolbar, menu, colorNormal, colorInMenu, false);
        }
    
        /**
         * Change the color of a single menu item icon
         * @param item        targeted menu item
         * @param colorNormal normal icon color
         * @param colorInMenu icon color for popup menu
         * @param isInSubMenu whether item is in a sub menu
         */
        @SuppressLint("RestrictedApi")
        public static void changeMenuIconColor(MenuItem item, int colorNormal, int colorInMenu, boolean isInSubMenu) {
            if (item.getIcon() != null) {
                Drawable icon = item.getIcon().mutate();
                int color = (((MenuItemImpl) item).isActionButton() && !isInSubMenu ? colorNormal : colorInMenu);
                icon.setColorFilter(color, PorterDuff.Mode.SRC_IN);
                icon.setAlpha(item.isEnabled() ? 255 : 128);
                item.setIcon(icon);
            }
        }
    
    }
    

    活性物质

    public final class ActivityUtils {
    
        /**
         * Force show the icons in the overflow menu and submenus
         * @param menu target menu
         */
        public static void forceShowMenuIcons(Menu menu) {
            if (menu instanceof MenuBuilder) {
                MenuBuilder m = (MenuBuilder) menu;
                m.setOptionalIconsVisible(true);
            }
        }
    
        /**
         * Get the action bar or toolbar view in activity
         * @param activity activity to get from
         * @return the toolbar view
         */
        public static ViewGroup findActionBar(Activity activity) {
            int id = activity.getResources().getIdentifier("action_bar", "id", "android");
            ViewGroup actionBar = null;
            if (id != 0) {
                actionBar = activity.findViewById(id);
            }
            if (actionBar == null) {
                return findToolbar((ViewGroup) activity.findViewById(android.R.id.content).getRootView());
            }
            return actionBar;
        }
    
        private static ViewGroup findToolbar(ViewGroup viewGroup) {
            ViewGroup toolbar = null;
            for (int i = 0; i < viewGroup.getChildCount(); i++) {
                View view = viewGroup.getChildAt(i);
                if (view.getClass() == android.support.v7.widget.Toolbar.class ||
                        view.getClass() == android.widget.Toolbar.class) {
                    toolbar = (ViewGroup) view;
                } else if (view instanceof ViewGroup) {
                    toolbar = findToolbar((ViewGroup) view);
                }
                if (toolbar != null) {
                    break;
                }
            }
            return toolbar;
        }
    
    }
    

    我还定义了两个属性 attrs.xml : toolbarTheme toolbarPopupTheme 我在工具栏上设置的XML布局。它们的值在中的“我的应用”主题中定义 themes.xml . 这些属性由 ThemeUtils.getToolbarColors(Context) 获取用于给图标着色的颜色,因为工具栏通常使用主题覆盖。通过这样做,我可以通过改变这2个属性的值来改变每个工具栏的主题。

    剩下的就是在活动的 onCreateOptionsMenu(Menu menu) :

    ActivityUtils.forceShowMenuIcons(menu);  // Optional, show icons in overflow and submenus
    
    View toolbar = ActivityUtils.findActionBar(this);  // Get the action bar view
    int[] toolbarColors = ThemeUtils.getToolbarColors(this);  // Get the icons colors
    ThemeUtils.changeIconsColor(toolbar, menu, toolbarColors[0], toolbarColors[1]);
    

    在一个片段中可以通过替换来完成同样的操作。 this 具有 getActivity() .

    更新MeNuItI图图标时,可以调用另一种方法, ThemeUtils.changeMenuIconColor() . 在这种情况下,工具栏颜色可以在 onCreate 并全局存储以重用它们。