如何在ActionBar的溢出菜单中显示图标

80

我知道使用本地API是不可能实现的。有没有其他方法来实现这种视图?


27
没关系,我想为我的应用程序实现它。 - Mihir
好的,但如果你有另一个应用程序可以做到这一点,那么可能可以尝试一下并弄清楚它们的实现方式。一个很好的起点可能是ActionBarSherlock库,因为它为旧版(4.0之前)设备创建了类似的视图。调整那个实现方式应该不会太难。 - Alex MDC
它还在内部使用本地类,因此它不会起作用。 - Mihir
18个回答

91

一般来说,之前发布的答案是可以的。但它基本上移除了溢出菜单的默认行为,比如在不同屏幕尺寸上可以显示多少个图标,以及当它们无法显示时如何将它们放入溢出菜单中。通过以上操作,您将删除许多重要的功能。

更好的方法是告诉溢出菜单直接显示图标。您可以通过向Activity添加以下代码来实现此目的。

@Override
public boolean onMenuOpened(int featureId, Menu menu)
{
    if(featureId == Window.FEATURE_ACTION_BAR && menu != null){
        if(menu.getClass().getSimpleName().equals("MenuBuilder")){
            try{
                Method m = menu.getClass().getDeclaredMethod(
                    "setOptionalIconsVisible", Boolean.TYPE);
                m.setAccessible(true);
                m.invoke(menu, true);
            }
            catch(NoSuchMethodException e){
                Log.e(TAG, "onMenuOpened", e);
            }
            catch(Exception e){
                throw new RuntimeException(e);
            }
        }
    }
    return super.onMenuOpened(featureId, menu);
}

6
这应该是被接受的答案,另一个更像是一个快速窍门。 - Yoann Hercouet
4
这是因为您正在使用AppCompat/Support Library。您需要将菜单的完整类名添加到ProGuard脚本中。在这种情况下,它是"android.support.v7.internal.view.menu.MenuBuilder"。 - Simon
14
警告:在appcompat-v7:22.x中不再调用onMenuOpened(FEATURE_ACTION_BAR),这可能是有意为之,也可能不是,请参见http://b.android.com/171440。将代码移至`onPrepareOptionsMenu`应该是可以的。 - TWiStErRob
4
这种方法在最新版本的支持库上不再起作用...取而代之,应该覆盖onPrepareOptionsPanel()方法..请参考这个答案:https://dev59.com/S10a5IYBdhLWcg3wqaNS#30337653 - Alécio Carvalho
5
"featureId == Window.FEATURE_ACTION_BAR" 不起作用,我使用 "(featureId & Window.FEATURE_ACTION_BAR) == Window.FEATURE_ACTION_BAR" 进行修复,它有效了!" - Ninja
显示剩余13条评论

68

根据之前的答案,我尝试了这个方法,在较新版本的支持库(25.1)中可以正常工作:

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.menu_main, menu);

    if(menu instanceof MenuBuilder){
        MenuBuilder m = (MenuBuilder) menu;
        //noinspection RestrictedApi
        m.setOptionalIconsVisible(true);
    }

    return true;
}

2
谢谢,反射让我感到害怕。 :) 如果这能成为被接受的答案就太好了。 - William T. Mallard
1
以美丽简洁的方式运作。谢谢。 - dazed
9
只能从相同的库组(groupId=com.android.support)中调用MenuBuilder.setOptionalIconsVisible,请使用Android Support 25.3.1。请注意不要改变原意,同时保证语言通俗易懂。 - Francesco Vadicamo
3
@FrancescoVadicamo @SuppressLint("RestrictedApi") - tim4dev
这里不起作用,可能是因为我在 ToolBar 上定义了另一个图标(这时溢出图标消失了)。此外,对 m.setOptionalIconsVisible 的调用仅限于从中调用它的包,另请参阅 tim4dev 评论。 - carl

64
在您的菜单xml中,使用以下语法嵌套菜单,您将开始获得带有图标的菜单。

<item
    android:id="@+id/empty"
    android:icon="@drawable/ic_action_overflow"
    android:orderInCategory="101"
    android:showAsAction="always">
    <menu>
        <item
            android:id="@+id/action_show_ir_list"
            android:icon="@drawable/ic_menu_friendslist"
            android:showAsAction="always|withText"
            android:title="List"/>
    </menu>
</item>


2
我没想到会这么容易! - Mohammad Ersan
尝试了很多次后,我也在考虑同样的事情。我对它进行了一些其他任务的更改,但它也开始显示图标。 - iBabur
@drawable/ic_action_overflow 图像在安卓中默认存在,还是需要将其放置在drawable文件夹中? - jyomin
它是私有的,因此无法直接访问,您需要将其放置在资源中。您可以从以下链接获取这些资源 http://developer.android.com/design/downloads/index.html - iBabur
1
@techtinkerer 你有什么方法实现了吗? - sanevys
显示剩余3条评论

25
你可以使用SpannableString。
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.menu_tab, menu);

    MenuItem item = menu.findItem(R.id.action_login);
    SpannableStringBuilder builder = new SpannableStringBuilder("* Login");
    // replace "*" with icon
    builder.setSpan(new ImageSpan(this, R.drawable.login_icon), 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    item.setTitle(builder);
}

1
这个解决方案很棒,不需要依赖内部字段! - nicopico
缺少返回语句 - King of Masses

18

Simon的回答对我非常有用,因此我想分享一下我如何按照建议将它实现到onCreateOptionsMenu方法中:

Simon的答案对我很有帮助,所以我想分享一下我如何像他建议的那样把它实现到onCreateOptionsMenu方法中:

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu items for use in the action bar
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.main_action_bar, menu);

    // To show icons in the actionbar's overflow menu:
    // https://dev59.com/fWMl5IYBdhLWcg3we243
    //if(featureId == Window.FEATURE_ACTION_BAR && menu != null){
        if(menu.getClass().getSimpleName().equals("MenuBuilder")){
            try{
                Method m = menu.getClass().getDeclaredMethod(
                        "setOptionalIconsVisible", Boolean.TYPE);
                m.setAccessible(true);
                m.invoke(menu, true);
            }
            catch(NoSuchMethodException e){
                Log.e(TAG, "onMenuOpened", e);
            }
            catch(Exception e){
                throw new RuntimeException(e);
            }
        }
    //}

    return super.onCreateOptionsMenu(menu);
}

2
由于“setOptionalIconsVisible”方法是包局部的,所以我最终在我的项目中创建了一个同名的包,并创建了一个助手类,不必使用反射。`package android.support.v7.view.menu;import android.view.Menu;public class Menus {public static void setOptionalIconsVisible(Menu menu) { if (menu instanceof MenuBuilder) { MenuBuilder menuBuilder = (MenuBuilder) menu; menuBuilder.setOptionalIconsVisible(true); } }}` - Makotosan
1
@Makotosan,这个方法不再是包私有的了! ;) - Louis CAD
@LouisCAD 现在该方法被限制为库组,因此他们正在更加努力地使其听起来难以访问。 - Abandoned Cart

8

在@Desmond Lua的上面的答案基础上,我创建了一个静态方法,用于在下拉列表中使用在XML中声明的可绘制对象,并确保其着色不影响常量可绘制对象状态。

    /**
 * Updates a menu item in the dropdown to show it's icon that was declared in XML.
 *
 * @param item
 *         the item to update
 * @param color
 *         the color to tint with
 */
private static void updateMenuWithIcon(@NonNull final MenuItem item, final int color) {
    SpannableStringBuilder builder = new SpannableStringBuilder()
            .append("*") // the * will be replaced with the icon via ImageSpan
            .append("    ") // This extra space acts as padding. Adjust as you wish
            .append(item.getTitle());

    // Retrieve the icon that was declared in XML and assigned during inflation
    if (item.getIcon() != null && item.getIcon().getConstantState() != null) {
        Drawable drawable = item.getIcon().getConstantState().newDrawable();

        // Mutate this drawable so the tint only applies here
        drawable.mutate().setTint(color);

        // Needs bounds, or else it won't show up (doesn't know how big to be)
        drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
        ImageSpan imageSpan = new ImageSpan(drawable);
        builder.setSpan(imageSpan, 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        item.setTitle(builder);
    }
}

当在活动中使用时,它的使用看起来会像这样。根据您的个人需求,这可能会更加优雅。

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.menu_activity_provider_connect, menu);
    int color = ContextCompat.getColor(this, R.color.accent_dark_grey);
    updateMenuWithIcon(menu.findItem(R.id.email), color);
    updateMenuWithIcon(menu.findItem(R.id.sms), color);
    updateMenuWithIcon(menu.findItem(R.id.call), color);
    return true;
}

我只是想问一下,安卓专家,如何修复/调整图标文本的垂直对齐?这很简单吗?谢谢! - Fattie
不确定头脑中是否有,但我的直觉是在图标本身上添加视觉填充(例如在Photoshop中),以使其正好到达您想要的位置。或者也许这个LineHeightSpan有些东西,可以查看这个答案https://dev59.com/HGgu5IYBdhLWcg3wv5ca#11120208 - Kevin Grant
是的,太棒了!运行得很好!!超级棒!只要注意setTint()应该验证要使用的Android版本。 - Beppi's
如果您想要文本具有相同的颜色,可以添加以下代码: builder.setSpan(new ForegroundColorSpan(color), 1, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - Hatzegopteryx

6
我发现最简单的方式是这样的:

public boolean onCreateOptionsMenu(Menu menu){
     MenuInflater inflater = getMenuInflater();
     inflater.inflate(R.menu.toolbar_menu,menu);
     if(menu instanceof MenuBuilder) {  //To display icon on overflow menu

          MenuBuilder m = (MenuBuilder) menu; 
          m.setOptionalIconsVisible(true);

     }
   return true;
}        `

5

目前最佳但未被接受的解决方案可能适用于旧平台。无论如何,在新的AppCompat21+中,所需方法不存在,而方法getDeclaredMethod会返回异常NoSuchMethodException

因此,我使用的解决方法(在4.x、5.x设备上测试过并可行)基于直接更改背景参数。只需将此代码放入您的Activity类中即可。

@Override
public boolean onMenuOpened(int featureId, Menu menu) {
    // enable visible icons in action bar
    if (featureId == Window.FEATURE_ACTION_BAR && menu != null) {
        if (menu.getClass().getSimpleName().equals("MenuBuilder")) {
            try {
                Field field = menu.getClass().
                        getDeclaredField("mOptionalIconsVisible");
                field.setAccessible(true);
                field.setBoolean(menu, true);
            } catch (IllegalAccessException | NoSuchFieldException e) {
                Logger.w(TAG, "onMenuOpened(" + featureId + ", " + menu + ")", e);
            }
        }
    }
    return super.onMenuOpened(featureId, menu);
}

我不确定您如何破坏了Simon的解决方案,但是方法在2014年8月至2015年5月之间没有更改,它也可以在Android 5.1.1上顺利运行 - TWiStErRob
4
你是否可能忘记告诉ProGuard呢? -keepclassmembers **.MenuBuilder { void setOptionalIconsVisible(boolean); } - TWiStErRob
1
嗯,这是可能的。我通常在未混淆的代码上进行测试,但这是可能的。如果您在5.x设备上没有问题,那么将其视为另一种替代解决方案,仅此而已。 - Menion Asamm

3

@Simon的答案真的很有效...但如果你正在使用AppCompat Activity...你需要使用以下代码...因为在appcompat-v7:22.x中onMenuOpened()不再被调用。

@Override
    protected boolean onPrepareOptionsPanel(View view, Menu menu) {
        if(menu != null){
            if(menu.getClass().getSimpleName().equals("MenuBuilder")){
                try{
                    Method m = menu.getClass().getDeclaredMethod(
                            "setOptionalIconsVisible", Boolean.TYPE);
                    m.setAccessible(true);
                    m.invoke(menu, true);
                }
                catch(NoSuchMethodException e){
                    Log.e(Constants.DEBUG_LOG, "onMenuOpened", e);
                }
                catch(Exception e){
                    throw new RuntimeException(e);
                }
            }
        }
        return super.onPrepareOptionsPanel(view, menu);
    }

3

Kotlin:

@SuppressLint("RestrictedApi")
fun Menu.showOptionalIcons() {
    this as MenuBuilder
    setOptionalIconsVisible(true)
}

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