如何在带有菜单按钮的设备上强制使用溢出菜单

161

我希望所有无法适应ActionBar的菜单项都进入溢出菜单(从ActionBar而不是菜单按钮中访问),即使在菜单按钮的设备上也是如此。对用户来说,这似乎比将它们抛到需要用户从触摸(屏幕)交互跳转到基于按钮的交互的单独菜单列表中要更直观。

在模拟器中,我可以将“硬件返回/主页键”值设置为“否”,以获得此效果。 我已经搜索了一种在具有菜单按钮但找不到的实际设备上执行此操作的方法。 有人能帮我吗?

12个回答

325

你也可以使用这个小技巧:

try {
    ViewConfiguration config = ViewConfiguration.get(this);
    Field menuKeyField = ViewConfiguration.class.getDeclaredField("sHasPermanentMenuKey");
    if (menuKeyField != null) {
        menuKeyField.setAccessible(true);
        menuKeyField.setBoolean(config, false);
    }
} catch (Exception ignored) {
}

建议将这段代码放在你的应用程序类的onCreate方法中。

这将强制应用程序显示溢出菜单。菜单按钮仍然可以工作,但它会在右上角打开菜单。

[编辑] 因为它已经多次出现:这个技巧只适用于Android 3.0引入的本机ActionBar,而不是ActionBarSherlock。后者使用自己的内部逻辑来决定是否显示溢出菜单。如果您使用ABS,则所有平台<4.0都由ABS处理,因此受到其逻辑的影响。该技巧仍将适用于所有Android 4.0或更高版本的设备(您可以安全地忽略Android 3.x,因为几乎没有带有菜单按钮的平板电脑)。

存在一个特殊的ForceOverflow主题,可以强制ABS中的菜单,但显然它会因为一些问题在将来的版本中被删除


4
我查看了源代码。里面有很多未记录的功能 :) 只要确保在出现这种情况时有可行的备选方案即可。 - Timo Ohr
4
非常感谢!这真的太好了。我想将评论、错误报告和分享操作放入溢出菜单中,但在Nexus S上没有显示出来。用户甚至不会点击菜单按钮。通过溢出菜单,用户可以看到额外的操作。 - Yuriy Kulikov
4
我晚了评论,但是我刚刚尝试了最新版本的ActionBarCompat,它确实起作用了。 - jacobhyphenated
3
这适用于三星Galaxy S3和S4,但不适用于LG G2(怀疑他们在硬编码检查以显示溢出菜单按钮)。 - dvd
19
好的!如果Eclipse给你提供了6个不同的导入选项来使用Field,请选择这个java.lang.reflect.Field ;) - Eugene van der Merwe
显示剩余15条评论

54

编辑:修改以回答物理菜单按钮的情况。

这实际上是由设计防止的。根据Android 设计指南中的兼容性部分

"...操作溢出可从菜单硬件键中获得。 所显示的操作弹出窗口...将显示在屏幕底部。"

您会注意到,在截图中,具有物理菜单按钮的手机在ActionBar中没有溢出菜单。这避免了用户的歧义,本质上有两个可用于打开完全相同菜单的按钮。

为了解决设备间一致性问题:对于用户体验来说,最重要的是您的应用与同一设备上的所有其他应用程序的行为一致,而不是在所有设备上都表现一致。


41
让我们加入这个对话并讨论:我知道这是设计上防止的(我看过设计指南)。但我认为这有点烦人。例如:用户从Galaxy Nexus(->带Overflow)切换到Nexus One(带4.0/ ->没有Overflow)。我敢打赌用户不会再找到菜单项了。因此,我想在所有设备上使用相同的功能。因此,我和paulp面临着同样的问题。难道没有任何干净的解决方法吗? - Sprigg
10
我也先阅读了设计指南。对我来说,这个支持包中的设计选择并不好。一个更好的策略是让按钮变得多余,并在屏幕上和按钮中都提供功能,以便让用户远离按钮(这是目标,对吧?)。目前情况是,按钮并未列出所有菜单选项,而只列出未在操作栏中的选项,因此这种设计既不能支持平稳过渡到操作栏,也不能像添加操作栏之前那样做到显示所有菜单选项。我怀疑你是对的,没有简单的解决方法。 - PaulP
18
谷歌在他们的新Google+应用中违反了这一点。它具有溢出菜单项,无论使用何种设备...但是据我所知,他们仍然阻止开发者做相同的事情并建议不要这样做。 - Karl
10
说实话,我见过太多用户不去尝试使用菜单硬键,以至于我不再期望他们会这样做。如果设计规定任何手机上的任何用户都不应该有屏幕指示菜单选项的存在,那么这个设计就是一个信息不足的设计。 - Lance Nanek
1
从KitKat(4.4)开始,溢出按钮已经存在,无论硬件菜单按钮是否可用。因此,至少在将来的某个时候,当所有人都使用4.4或更高版本时,这不应该成为问题。 - Carl
显示剩余8条评论

35

我曾经通过定义我的菜单来解决这个问题(在我的示例中也使用了ActionBarSherlock图标):

<menu xmlns:android="http://schemas.android.com/apk/res/android" >

    <item
        android:id="@+id/menu_overflow"
        android:icon="@drawable/abs__ic_menu_moreoverflow_normal_holo_light"
        android:orderInCategory="11111"
        android:showAsAction="always">
        <menu>
            <item
                android:id="@+id/menu_overflow_item1"
                android:showAsAction="never"
                android:title="@string/overflow_item1_title"/>
            <item
                android:id="@+id/menu_overflow_item2"
                android:showAsAction="never"
                android:title="@string/overflow_item2_title"/>
        </menu>
    </item>

</menu>

我承认这可能需要在您的xml中手动进行"溢出管理",但我发现这个解决方案很有用。

您还可以在您的活动中强制设备使用硬件按钮来打开溢出菜单:

private Menu mainMenu;

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // TODO: init menu here...
    // then:
    mainMenu=menu;
    return true;
}

@Override
public boolean onKeyUp(int keycode, KeyEvent e) {
    switch(keycode) {
        case KeyEvent.KEYCODE_MENU:
            if (mainMenu !=null) {
                mainMenu.performIdentifierAction(R.id.menu_overflow, 0);
            }
    }

    return super.onKeyUp(keycode, e);
}

:-)


2
如何使用硬件按钮打开溢出菜单? - bladefury
1
请查看我更新后的答案,了解如何通过硬件按钮打开溢出菜单。 :) - Berťák

11

如果您正在使用支持库中的操作栏(android.support.v7.app.ActionBar),请使用以下内容:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:yorapp="http://schemas.android.com/apk/res-auto" >

    <item
        android:id="@+id/menu_overflow"
        android:icon="@drawable/icon"
        yourapp:showAsAction="always"
        android:title="">
        <menu>
            <item
                android:id="@+id/item1"
                android:title="item1"/>
            <item
                android:id="@+id/item2"
                android:title="item2"/>
        </menu>
    </item>

</menu>

使用支持库时,我也实现了这个功能,并且在早期和3.0后的设备上都能正常运行。 - leafcutter

7
这种方法被Android开发者设计系统所防止,但我找到了一个通过的方法:
将以下内容添加到您的XML菜单文件中:
<item android:id="@+id/pick_action_provider"
    android:showAsAction="always"
    android:title="More"
    android:icon="@drawable/ic_action_overflow"
    android:actionProviderClass="com.example.AppPickActionProvider" />

接下来,创建一个名为“AppPickActionProvider”的类,并将以下代码复制到其中:
    package com.example;

import android.content.Context;
import android.util.Log;
import android.view.ActionProvider;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.SubMenu;
import android.view.View;

public class AppPickActionProvider extends ActionProvider implements
        OnMenuItemClickListener {

    static final int LIST_LENGTH = 3;

    Context mContext;

    public AppPickActionProvider(Context context) {
        super(context);
        mContext = context;
    }

    @Override
    public View onCreateActionView() {
        Log.d(this.getClass().getSimpleName(), "onCreateActionView");

        return null;
    }

    @Override
    public boolean onPerformDefaultAction() {
        Log.d(this.getClass().getSimpleName(), "onPerformDefaultAction");

        return super.onPerformDefaultAction();
    }

    @Override
    public boolean hasSubMenu() {
        Log.d(this.getClass().getSimpleName(), "hasSubMenu");

        return true;
    }

    @Override
    public void onPrepareSubMenu(SubMenu subMenu) {
        Log.d(this.getClass().getSimpleName(), "onPrepareSubMenu");

        subMenu.clear();

        subMenu.add(0, 1, 1, "Item1")
        .setIcon(R.drawable.ic_action_home).setOnMenuItemClickListener(this);

        subMenu.add(0, 2, 1, "Item2")
            .setIcon(R.drawable.ic_action_downloads).setOnMenuItemClickListener(this);
    }

    @Override
    public boolean onMenuItemClick(MenuItem item) {
        switch(item.getItemId())
        {
            case 1:

                // What will happen when the user presses the first menu item ( 'Item1' )

                break;
            case 2:

                // What will happen when the user presses the second menu item ( 'Item2' )

                break;

        }

        return true;
    }
}

使用这种方法和仅使用子菜单有什么区别,如此处所示:https://dev59.com/cW3Xa4cB1Zd3GeqPkOxU#14634780? - android developer

5
我认为Alexander Lucas提供的答案是(不幸地)正确的,所以我将其标记为“正确”的答案。我在此添加的另一种答案只是为了将任何新读者指向Android Developers博客中这篇文章作为一个相当完整的讨论主题,并提供一些具体的建议,以便在从pre-level 11过渡到新的Action Bar时如何处理您的代码。
我仍然认为没有将菜单按钮作为菜单按钮启用设备中冗余的“操作溢出”按钮的设计错误是更好的用户体验过渡方式,但现在已经是水下的桥梁了。

我完全同意你关于设计错误的看法 - 这让我感到非常沮丧! - Paul Hunnisett
1
如果不是谷歌自己在最近的G+应用程序中开始违反这个规则,我会同意你的看法。而且这样做是正确的。菜单按钮并不是遗留问题,即使像SGS3这样的新设备也有它。它来了就不会走了,不幸的是。而且它严重影响了可用性。 - Timo Ohr

4

我不确定这是否是您正在寻找的,但我在ActionBar的菜单中构建了一个子菜单,并将其图标设置为与Overflow菜单的图标相匹配。尽管它不会自动发送项目(即您必须选择什么始终可见和什么始终溢出),但我认为这种方法可能会对您有所帮助。


2
如果您使用工具栏,则可以在所有版本和所有设备上显示溢出菜单,我已经在一些2.x设备上尝试过,它可以正常工作。

2
在预装ICS的Gmail应用程序中,当您选择多个项目时,菜单按钮被禁用。溢出菜单在这里通过使用溢出按钮而不是物理菜单按钮“强制”触发。有一个名为ActionBarSherlock的第三方库,可以让您“强制”使用溢出菜单。但这仅适用于API级别14或更低(ICS之前)。

1

如果这个问题已经过时,非常抱歉。

以下是我解决错误所做的步骤。我进入布局并创建了两个包含工具栏的布局。一个是针对sdk版本8的布局,另一个是针对sdk版本21的布局。在版本8上,我使用了android.support.v7.widget.Toolbar,而在sdk 21布局中我使用了android.widget.Toolbar。

然后我在我的活动中填充工具栏。我检查sdk版本是否为21或更高版本。然后我填充相应的布局。这将强制硬件按钮映射到您实际设计的工具栏。


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