Menus(菜单)

菜单

在许多不同类型的应用中,菜单是一种常用的用户界面组件。为了提供熟悉且一致的用户体验,你应该在你的activity中使用 Menu API来展示用户的操作和其他选项。

从Android 3.0(API等级11)开始, Android设备就不需要再提供专用的菜单按钮了。基于这种变化,Android应用应该远离原来所以来的传统6选项菜单面板,取而代之是使用操作栏来展示常用的用户操作。

尽管一些菜单选项的设计和用户体验已经发生变化,但是定义一套操作和选项的语义依旧是基于 Menu API的。这篇指南会向你展示如何在全Android版本的平台上创建三个基本类型的菜单或操作:

选项菜单和操作栏

options menu 对于activity来说是主要的菜单项目集合。你应该在其中放置一些能影响整个应用的操作,例如“搜索”、“编写邮件”和“设置”。

如果你在为Android 2.3或更低版本的平台开发应用,那么用户可以通过按下Menu按钮来显示选项菜单面板。

在Android 3.0或更高的平台上,选项菜单中的项目是通过屏幕上操作项目和更多选项组合成的 action bar 来显示的。从 Android 3.0开始,Menu按键被弃用了(有些设备甚至都没有),所以你应该使用操作栏来提供操作和其他选项的入口。

请查阅观月 Creating an Options Menu 的章节。

上下文菜单和上下文操作模式

上下文菜单是一种  floating menu,是当用户在一个元件上执行长按动作时才显示的。它可以提供能影响被选中内容或内容框架的操作。

若为Android 3.0或更高版本的平台开发,你应该使用 contextual action mode 为被选内容开启操作。这种模式把影响内容的操作选项放置在屏幕顶部的操作栏中,并允许用户选择多项。

请查阅 Creating Contextual Menus 章节了解更多信息。

弹出菜单

弹出菜单会在调用它的视图处锚定并以垂直列表的形式显示选项列表。它很好的为一些特定内容提供了相关的更多操作,或为命令提供了更多选择。在弹出菜单中的操作 不能直接影响到相应的内容—因为这是上下文操作的用处。相当于,弹出菜单为activity里相关区域的内容提供了延伸的操作。

请查阅 Creating a Popup Menu 章节了解更多信息。

在XML中定义菜单


针对所有的菜单类型,Android都提供了定义菜单项的标准XML格式。你应该在 menu resource 中定义菜单和菜单项,而不是在activity代码中构建菜单。然后你就能在activity或fragment中扩展这些菜单资源(按照 Menu 对象载入资源)。

使用菜单资源是一个很好的做法,有以下几个原因:

  • 在XML中可以更容易的可视化展现菜单结构。
  • 把菜单的内容从应用的行为代码中分离出来。
  • 可以利用 app resources 框架来创建备选的菜单配置以适应不同版本的平台,不同尺寸的屏幕或其他不同的配置。

为了创建菜单,你需要在项目的res/menu/目录下创建XML文件,然后使用下面的元素构建菜单:

<menu>
定义菜单项的容器  Menu<menu>元素必须作为文件的根节点,它可以容纳一个或多个 <item><group>元素。
<item>
创建  MenuItem,它代表菜单中的一个单独的选项。创建子菜单时,这个元素会包含嵌套的 <menu>元素。
<group>
可选项, <item> 元素的不可见容器。它可以让你把菜单项进行归类,这样它们就可以共享某些属性,例如活动状态和可见性。请查阅  Creating Menu Groups 章节获取更多信息。

下面是名为game_menu.xml的菜单事例:     

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/new_game"
          android:icon="@drawable/ic_new_game"
          android:title="@string/new_game"
          android:showAsAction="ifRoom"/>
    <item android:id="@+id/help"
          android:icon="@drawable/ic_help"
          android:title="@string/help" />
</menu>

<item> 元素支持多种属性,你可以使用它们来定义菜单项的外观和行为。在上面菜单里的菜单项包含下面这些属性:

android:id
资源ID,作为菜单项的唯一标识,在用户选中这个选择项时可以让应用通过它来识别菜单项。
android:icon
一个图片资源的引用,用作菜单项的图标。
android:title
一个字符串资源的引用,用作菜单项的标题。
android:showAsAction
指明这个菜单项什么时候和如何作为操作项显示在  action bar 中。

这些是你将要使用到的最重要的属性,除此之外还有更多可用的属性。请查阅 Menu Resource 文档,获取更多关于所有可用属性的信息。

你可以通过在<item>中添加一个<menu>元素的子项来给任意的菜单项(子菜单除外)添加子菜单。当你的应用里有许多可以按主题分组的功能时,使用子菜单就非常有用了,例如PC应用里菜单栏的选项(文件,编辑,视图等等)。例如:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/file"
          android:title="@string/file" >
        <!-- “文件”子菜单 -->
        <menu>
            <item android:id="@+id/create_new"
                  android:title="@string/create_new" />
            <item android:id="@+id/open"
                  android:title="@string/open" />
        </menu>
    </item>
</menu>

如果要在你的activity中使用菜单,那么你需要使用 MenuInflater.inflate() 来扩展菜单资源(把XML资源转换成可编程的对象)。在接下来的章节中,你将会看到如何为每种不同的菜单类型扩展菜单。

创建选项菜单


图1.在Android 2.3中,浏览器的选项菜单。

选项菜单应该包含操作和其他与当前activity的上下文相关的选项,例如“搜索”、“编写邮件”和“设置”。

选项菜单中的菜单项在屏幕上显示的位置取决于你的应用基于的开发平台的版本:

  • 如果你在为 Android 2.3.x(API等级10)或更低 的平台开发应用,那么你的选项菜单的内容会在用户按下菜单按钮后出现在屏幕的底部位置,就如同图1展示的那样。当菜单面板打开时,首先可见的部分是图标菜单,最多可以容纳六个菜单项。如果你的菜单包含了多于六个的菜单项,Android只会放置六个菜单项并且把其他多余的选项放置到更多菜单中,用户可以通过选中“更多”来打开。
  • 如果你在为 Android 3.0(API等级11)或更高 的平台开发应用,那么选项菜单中的菜单项都会被添加到 action bar 中使用。默认情况下,系统会把所有的选项放置到更多操作中,用户可以点击操作栏右边的更多操作图标来显示它们(或者如果设备的菜单按钮可用的话,按下即可)。为了能够快速访问重要的操作,你可以在相应的<item>元素中添加android:showAsAction="ifRoom"使一些选项出现在操作栏上(如图2)。

    请查阅 Action Bar 指南获取更多关于操作选项和其他操作栏行为的信息。

    注解:即便你没有为Android 3.0或更高版本的平台开发应用,为了相似的效果你仍然可以构建自己的操作栏布局。请查阅 Action Bar Compatibility 获取如何在老版本的Android平台上支持操作栏的事例。

图2.Honeycomb Gallery 应用的操作栏,显示了导航标签和相机操作项(加上更多操作按钮)。

你可以在 Activity 子类或 Fragment 子类中声明选项菜单的菜单项。如果在activity和fragment(s)中都声明了选项菜单的菜单项,那么它们将在UI中合并。Activity里的选项先显示,然后才按照每个fragment添加到activity中的顺序依次显示frament中的选项。如果需要的话,你还可以在你需要移动的每个<item>元素中添加android:orderInCategory属性重新排列菜单项。

可以通过重写 onCreateOptionsMenu() 来为activity指定选项菜单(fragment里提供他们自己的 onCreateOptionsMenu() 回调)。在这个方法里,你可以把菜单资源(defined in XML)扩展到这个回调提供的 Menu 中。例如:

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.game_menu, menu);
    return true;
}

你也可以使用 add() 添加菜单项以及使用 findItem() 获取菜单项后通过 MenuItem API修改它们的属性。

如果你是为Android 2.3.x或更低的平台开发的应用,那么用户第一次打开菜单时系统会调用 onCreateOptionsMenu() 来创建选项菜单。如果你是为Android 3.0或更高版本的平台开发的应用,为了在操作栏上显示选项系统会在启动activity时就调用 onCreateOptionsMenu()

处理点击事件

当用户从选项菜单中选中选项时(包括操作栏中的操作项),系统将会调用activity中的 onOptionsItemSelected() 方法。这个方法会传递被选中的 MenuItem。你可以通过调用 getItemId() 来识别该选项,它会返回菜单项的唯一标识(在菜单资源中通过android:id定义或通过 add() 方法指定一个整型值)。你可以通过这个ID与已知的菜单项对比后来执行合适的操作。例如:

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // 处理菜单项选中操作
    switch (item.getItemId()) {
        case R.id.new_game:
            newGame();
            return true;
        case R.id.help:
            showHelp();
            return true;
        default:
            return super.onOptionsItemSelected(item);
    }
}

如果你成功处理了菜单项,那么请返回true。如果你没有处理菜单项,那么你应该调用父类的 onOptionsItemSelected() 实现(默认实现返回false)。

如果你的activity包含fragment,那么系统首先会为activity调用 onOptionsItemSelected(),然后再为每个fragment调用(按照每个fragment被添加的顺序),直到其中有个返回值为true或所有的fragment都被调用过。

小贴士:Android 3.0在XML中为菜单项添加了定义点击行为的方式,使用android:onClick属性即可。这个属性的值必须是在使用这个菜单的activity中定义的方法的名字。这个方法还必须是公共的并且只接受一个 MenuItem 参数,当系统调用这个方法时,它会传递被选中的菜单项。请查阅 Menu Resource 文档获取更多信息和事例。

小贴士:如果你的应用包含多个activity并且其中有一些有着相同的选项菜单,那么你可以考虑创建一个只实现 onCreateOptionsMenu() 和 onOptionsItemSelected() 方法的activity。然后每个需要相同选项菜单的activity都扩展这个类。这样的话,你可以只用管理处理菜单操作的那部分代码,然后每个子类都会继承这些菜单行为。如果你想要为某个子activity添加菜单项,你只需在那个activity中重写 onCreateOptionsMenu() 即可。调用 super.onCreateOptionsMenu(menu) 以便创建最初的菜单项,然后调用 menu.add() 来添加新的菜单项。当然你也可以为个别的菜单项重写基类的行为。

运行时改变菜单项

在系统调用 onCreateOptionsMenu() 后,它留下一个供你填充的 Menu 实例,并且除非菜单因为某种原因失效了,系统不会再调用 onCreateOptionsMenu() 了。无论如何,你只能使用 onCreateOptionsMenu() 来初始化按钮状态,不能在activity的生命周期内改变它。

如果你想要基于activity生命周期内发生的事件来修改选项菜单,你可以在 onPrepareOptionsMenu() 方法内实现这种效果。这个方法会传递给你一个当前存在的 Menu 对象,这样你就能修改它了,例如添加、移除或使选项失效(Fragment同样也提供了一个 onPrepareOptionsMenu() 回调)。

在Android 2.3.x或更低版本的平台上时,每次用户打开选项菜单(按下菜单按钮)系统都会调用 onPrepareOptionsMenu()

在Android 3.0或更高版本的平台上时,当菜单项是在操作栏中显示的时候,可以认为选项菜单始终是打开状态。当事件触发并且你想要执行菜单更新的操作,你必须调用 invalidateOptionsMenu() 来请求系统调用 onPrepareOptionsMenu()

注解:你永远不能根据 View 是否获得焦点来改变选项菜单中的选项。当在触摸模式下(也就是用户当前没有使用轨迹球或方向键),视图不可能获取焦点,所以你永远不能使用焦点作为修改选项菜单中选项的依据。如果你想为 View 提供上下文相关的菜单项,那么你可以使用 Context Menu

创建上下文菜单


图3.浮动上下文菜单(左)和上下文操作栏(右)的截图。

上下文菜单可以支持影响UI中指定选项或上下文框架的操作。你可以为任何视图应用上下文菜单,但是它们通常被应用在 ListViewGridView或其他用户可以直接操作选项的视图集合里的选项。

有两种方式可以提供上下文操作:

  • 在 floating context menu 中。当用户在声明支持上下文菜单的视图上长按时(按下并保持),一个浮动菜单项列表就会显示出来(类似对话框)。用户每次只能在一个选项上执行上下文操作。
  • 在 contextual action mode 中。这种模式是 ActionMode 的一种系统实现,它会在屏幕顶部显示一个上下文相关的操作栏,操作栏里是可以影响被选项目的操作选项。当这个模式被激活时,用户甚至可以同时在多个项目上执行操作。

注解:上下文操作模式只有在Android 3.0(API等级11)或更高版本的平台上才可用,如果可用的话它肯定是显示上下文操作的首选技术。如果你的应用支持3.0以下的版本,那么你应该回过来在这些设备上使用浮动上下文菜单。

创建浮动上下文菜单

为了准备一个浮动上下文菜单:

  1. 通过调用 registerForContextMenu() 来注册上下文菜单应该关联的 View,需要将这个 View 传递给该方法。

    如果你的activity使用了 ListView 或 GridView 并且你希望为每个项目都提供相同的上下文菜单,那么你可以把这个 ListView 或 GridView 传递给 registerForContextMenu() 来给所有的项目都实现上下文菜单。

  2. 在你的 Activity 或 Fragment 中实现 onCreateContextMenu() 方法。

    当被注册的视图接收到长按事件,系统就会调用你的 onCreateContextMenu() 方法。在这里你可以定义菜单项,通常是扩展菜单资源来实现。例如:

    @Override
    public void onCreateContextMenu(ContextMenu menu, View v,
                                    ContextMenuInfo menuInfo) {
        super.onCreateContextMenu(menu, v, menuInfo);
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.context_menu, menu);
    }

    MenuInflater 可以让你从 menu resource 中扩展上下文菜单。这个回调方法的参数包含用户选中的 View 和提供关于被选项目附加信息的 ContextMenu.ContextMenuInfo 对象。如果你的 activity 有许多提供各不相同上下文菜单的视图,那么你可以使用这些参数决定扩展哪种上下文菜单。

  3. 实现 onContextItemSelected().

    当用户选中菜单项,系统会调用这个方法为你执行合适的操作。例如:

    @Override
    public boolean onContextItemSelected(MenuItem item) {
        AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
        switch (item.getItemId()) {
            case R.id.edit:
                editNote(info.id);
                return true;
            case R.id.delete:
                deleteNote(info.id);
                return true;
            default:
                return super.onContextItemSelected(item);
        }
    }

    getItemId() 方法可以查询被选中菜单项的ID,它们是你在XML中使用android:id属性赋予每个菜单项的,如同在 Defining a Menu in XML 章节中讲的那样。

    如果你成功处理了菜单项的操作,那么返回true。如果你没有处理菜单项的操作,那么你应该把这个菜单项传递给基类的实现。如果你的 activity 包含 fragment,那么 activity 将首先接收到这个回调。在没有处理时通过调用基类,系统会把这个事件分别传递给每个fragment中的回调方法,一次一个(按照每个fragment添加进来的顺序)直到返回true 或 false。(Activity 和android.app.Fragment 的默认实现是返回false ,所以在不处理时你总是应该调用父类)

使用上下文操作模式

上下文操作模式是 ActionMode 的一种系统实现,它关注执行上下文操作的用户交互。当用户通过选中项目开启这个模式时,一个上下文操作栏会出现在屏幕的顶部,它呈现了用户可以对当前选中的选项执行的操作。当这个模式被开启时,用户可以选中多个项目(如果你允许这样),取消多个项目以及继续在activity中导航(只要你乐意这样)。当用户取消了所有项目、按下回退键或选中栏目左边的执行操作时,操作模式将变得不可用并且上下文操作栏会消失掉。

注解:上下文操作栏未必与 action bar 有关联。尽管上下文操作栏从外观上看占用了操作栏的位置,它们依旧是独立运行的。

如果你正在基于Android 3.0(API等级11)或更高版本的平台开发应用,通常你应该使用上下文操作模式替代 floating context menu 来呈现上下文操作。

对于提供上下文操作的视图来说,通常你应该根据这两个事件中的一个(或两者兼具)调用上下文操作模式:

  • 用户在视图上执行长按操作。
  • 用户在这个视图内选择复选框或相似UI组件。

你的应用如何调用上下文操作模式?如何根据你的设计为每个操作定义行为?有两种基本的设计方法:

  • 在单个的、任意的视图上的上下文操作。
  • 在 ListView 或 GridView 中项目小组的批上下文操作(用户可以选择多个项目然后在它们上执行相同的操作)。、

下面的章节描述每种方案所需的设置步骤。

为单个视图开启上下文操作模式

如果你想在用户选择特定的视图才调用上下文模式,那么你应该:

  1. 实现 ActionMode.Callback 接口。在它的回调方法中,你可以为上下文操作栏指定操作,响应操作项的点击事件,以及为操作模式处理其他生命周期事件。
  2. 在你想要显示操作栏时调用 startActionMode()(比如用户长按视图时)。

例如:

  1. 实现 ActionMode.Callback 接口:
    private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
    
        // startActionMode()被调用后,操作模式被创建时调用该方法
        @Override
        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
            // 扩展菜单资源来提供上下文菜单项
            MenuInflater inflater = mode.getMenuInflater();
            inflater.inflate(R.menu.context_menu, menu);
            return true;
        }
    
        // 操作模式每次显示时调用。总是在onCreateActionMode之后调用,但是如果操作模式不可用时可能会被调用多次。
        @Override
        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
            return false; // 如果不执行任何操作返回false
        }
    
        // 当用户选中一个上下文菜单项时调用
        @Override
        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
            switch (item.getItemId()) {
                case R.id.menu_share:
                    shareCurrentItem();
                    mode.finish(); // Action picked, so close the CAB
                    return true;
                default:
                    return false;
            }
        }
    
        // 用户离开操作模式时调用
        @Override
        public void onDestroyActionMode(ActionMode mode) {
            mActionMode = null;
        }
    };

    注意这些事件的回调除了他们每个都传递与事件关联的 ActionMode 对象之外几乎与 options menu 的回调一模一样。你可以使用 ActionMode API对上下文操作栏做出不同的改变,例如使用 setTitle() 和 setSubtitle() 来修改标题和副标题(用来指明有多少项目被选中是非常有用的)。

    同样要注意上面的事例在操作模式被摧毁时把 mActionMode 变量置为null。下一步,你会明白它是如何初始化的,还有在你的activity或fragment里保存的成员变量有什么用。

  2. 适当的时候调用 startActionMode() 来启用上下文操作模式,例如在 View 上响应长按操作:

    someView.setOnLongClickListener(new View.OnLongClickListener() {
        // 用户长按某个视图时调用
        public boolean onLongClick(View view) {
            if (mActionMode != null) {
                return false;
            }
    
            // 使用上面定义的ActionMode.Callback来启动上下文操作栏
            mActionMode = getActivity().startActionMode(mActionModeCallback);
            view.setSelected(true);
            return true;
        }
    });

    当你调用 startActionMode(),系统就会返回一个创建好的 ActionMode。通过把它存储在成员变量里,在响应其他事件时你可以对上下文操作栏做出改变。在上面的事例中,ActionMode 被用来在启动操作模式前通过检查成员变量是否为null来确保操作模式已激活的情况下 ActionMode 实例不会被重新创建。

在ListView或GridView里启用批上下文操作

如果你在 ListView 或 GridView (或另外一种 AbsListView 的扩展)中有一些项目,并且你想要用户可以执行批处理操作,那么你应该:

例如:

ListView listView = getListView();
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
listView.setMultiChoiceModeListener(new MultiChoiceModeListener() {

    @Override
    public void onItemCheckedStateChanged(ActionMode mode, int position,
                                          long id, boolean checked) {
        // 当项目被选中/取消选中时你可以在这里执行某些操作,
        // 比如更新上下文操作栏中的标题
    }

    @Override
    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
        // 响应上下文操作栏中的点击操作事件
        switch (item.getItemId()) {
            case R.id.menu_delete:
                deleteSelectedItems();
                mode.finish(); // 选择完操作后关闭上下文操作栏
                return true;
            default:
                return false;
        }
    }

    @Override
    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
        // 初始化上下文操作栏中的菜单
        MenuInflater inflater = mode.getMenuInflater();
        inflater.inflate(R.menu.context, menu);
        return true;
    }

    @Override
    public void onDestroyActionMode(ActionMode mode) {
        // 当上下文操作栏被移除时,你可以对activity执行任何必要的更新,
        // 默认情况下,被选中的项目会被取消选中。
    }

    @Override
    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
        // 这里根据 invalidate() 请求对上下文操作栏执行更新
        return false;
    }
});

就是这样。现在当用户长按选择项目时,系统会调用 onCreateActionMode() 方法并且使用指定的操作显示上下文操作栏。让上下文操作栏可见时,用户就能选择更多项目了。

在上下文操作栏提供公共操作选项的情况下,因为用户可能不清楚长按操作,所以你可能想要添加一个复选框或一种相似的UI组件来让用户选择多个项目。当用户选中复选框时,你可以通过使用 setItemChecked() 把单独的列表选项设置为被选中状态来调用上下文操作模式。

创建弹出菜单


图4.Gmail应用中的探底出菜单,锚定在顶部右侧的更多选项按钮下。

PopupMenu 是一种锚定在 View 上的类型的菜单。如果空间足够的话它会出现在锚定视图的下方,否则在视图的上方。对这些情景来说是很有用的:

  • 为关联特殊内容的操作提供一种类更多操作样式的菜单(例如如图4所示的Gmail的邮件头部)。

    注解:这不同于上下文菜单,上下文菜单通常是提供会影响被选中内容的操作。要想获得影响内容的操作,请使用 contextual action mode 或 floating context menu

  • 提供子命令(例如一个被标记为“Add”的按钮,可以使用弹出菜单提供不同的“Add”选项)。
  • 提供一个类似 Spinner 但是又不会保留一个长久存在的选择的下拉列表。

注解:PopupMenu 在API等级11或更高版本的平台上可用。

如果你已经 define your menu in XML,下面就讲解如何显示弹出菜单:

  1. 使用其构造方法实例化一个 PopupMenu,需要使用当前应用的 Context 和菜单想要锚定的 View
  2. 使用 MenuInflater 把菜单资源扩展到 PopupMenu.getMenu() 返回的 Menu 对象中。在API 等级14或更高版本的平台上,你可以直接使用 PopupMenu.inflate()
  3. 调用 PopupMenu.show()

例如,下面是一个使用了 android:onClick 属性的可以显示弹出菜单的按钮:

<ImageButton
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content" 
    android:src="@drawable/ic_overflow_holo_dark"
    android:contentDescription="@string/descr_overflow_button"
    android:onClick="showPopup" />

然后 activity 可以像这样显示弹出菜单:

public void showPopup(View v) {
    PopupMenu popup = new PopupMenu(this, v);
    MenuInflater inflater = popup.getMenuInflater();
    inflater.inflate(R.menu.actions, popup.getMenu());
    popup.show();
}

在API 等级14或更高版本的平台上,你可以使用 PopupMenu.inflate() 来合并上面扩展菜单的两行。

当用户选择一个选项或触碰菜单区域以外的地方时菜单将会消失。你可以使用 PopupMenu.OnDismissListener 来监听菜单消失事件。

处理点击事件

你必须实现 PopupMenu.OnMenuItemClickListener 接口并通过调用 setOnMenuItemclickListener() 把它注册到你的 PopupMenu 中,这样在用户选中菜单项时才能执行某些具体的操作。当用户选中某个菜单项时,系统会在你的接口里调用 onMenuItemClick() 回调。

例如:

public void showMenu(View v) {
    PopupMenu popup = new PopupMenu(this, v);

    // 该activity实现了OnMenuItemClickListener
    popup.setOnMenuItemClickListener(this);
    popup.inflate(R.menu.actions);
    popup.show();
}

@Override
public boolean onMenuItemClick(MenuItem item) {
    switch (item.getItemId()) {
        case R.id.archive:
            archive(item);
            return true;
        case R.id.delete:
            delete(item);
            return true;
        default:
            return false;
    }
}

创建菜单组


菜单组是一种共享某些特征的菜单项的集合。使用菜单组,你可以:

你可以通过在菜单资源中的<group>元素中嵌套<item>元素或通过 add() 方法指定菜单组ID来创建菜单组。

下面是一个包含菜单组的菜单资源事例:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/menu_save"
          android:icon="@drawable/menu_save"
          android:title="@string/menu_save" />
    <!-- 菜单组 -->
    <group android:id="@+id/group_delete">
        <item android:id="@+id/menu_archive"
              android:title="@string/menu_archive" />
        <item android:id="@+id/menu_delete"
              android:title="@string/menu_delete" />
    </group>
</menu>

这些在菜单组里的菜单项和第一个菜单项是同一级别的,在这个菜单中的三个菜单项都是兄弟节点。然而,你可以通过引用组ID的和使用上面列出的方法来修改菜单组里那两个菜单项的特征。系统绝不会分离菜单组内菜单项。例如,如果你为每个菜单项都声明了android:showAsAction="ifRoom",那么它们会同时出现在操作栏中或同时出现在更多选项中。

使用可选的菜单项

图5.带可选菜单项的子菜单截图。

菜单可以被用来作为开关选项的接口,对互相独立的选项使用复选框,或对组内互斥的选项使用单选按钮。图5显示了一个使用单选按钮菜单项的子菜单。

注解:在图标菜单里的菜单项(来自选项菜单)不能显示复选框或单选按钮。如果你想要使图标菜单中的菜单项可选的话,你必须在每次状态改变时通过改变图标和/或文本来手动的指定选择状态。

你可以在<item>元素里使用android:checkable属性来为单独的菜单项定义可选行为,或者在<group>元素里使用android:checkableBehavior属性为整个组定义。例如,在菜单组里的所有菜单项都使用单选按钮且是可选的:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <group android:checkableBehavior="single">
        <item android:id="@+id/red"
              android:title="@string/red" />
        <item android:id="@+id/blue"
              android:title="@string/blue" />
    </group>
</menu>

android:checkableBehavior 属性可以接受:

single
在菜单组里只有一个菜单项能被选中(单选按钮)
all
所有的菜单项都能被选中(复选框)
none
所有菜单项都不可选

你可以在<item>元素里使用android:checked属性对菜单项应用默认的选择状态并且可以使用 setChecked() 方法在代码里改变它。

当一个可选菜单项被选中时,系统会调用菜单项各自的菜单项被选中回调方法(例如 onOptionsItemSelected()因为复选框或单选按钮不会自动改变它们的状态,所以在这里你必须设置复选框的状态。你可以使用 isChecked() 查询菜单项当前的状态(在用户选中它之前时),以及使用 setChecked() 设置选中状态。例如:

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case R.id.vibrate:
        case R.id.dont_vibrate:
            if (item.isChecked()) item.setChecked(false);
            else item.setChecked(true);
            return true;
        default:
            return super.onOptionsItemSelected(item);
    }
}

如果你不通过这种方式设置选中状态,那么当用户选中它时,选项(复选框或单选按钮)的可见状态不会改变。当你设置了状态,activity将保存菜单项的选择状态,以便在用户以后打开菜单时你设置的选中状态依然是可见的。

注解:可选菜单项的仅仅被用在基础会话上,且在应用销毁后不能保存。如果你想为用户保存应用设置,你应该使用 Shared Preferences 保存数据。

基于Intent添加菜单项


有时你可能想要菜单项使用 Intent 来启动一个activity(无论这个activity是在你的应用中或其他应用中)。如果你知道你准备使用的intent和开启这个intent的具体菜单项,你可以在合适的菜单项被选中的回调方法里用 startActivity() 执行这个intent(例如 onOptionsItemSelected() 回调)。

然而,如果你不确定用户的设备上知否包含可以处理这个intent的应用,却添加了一个可以调用它的菜单项,因为这个intent可能没有接收的activity,所以这会导致生成一个无用功能的菜单项。为了解决这个问题,当Android在设备上发现可以处理你的intent的activity时,Android可以让你动态的把菜单项添加到你的菜单中。

基于接收intent的可用activity添加菜单项:

  1. 使用 CATEGORY_ALTERNATIVE 和/或 CATEGORY_SELECTED_ALTERNATIVE 定义intent,可以添加其他任意的需求。
  2. 调用 Menu.addIntentOptions()。然后Android会搜索任何可以处理这个intent的应用,并将它们添加到你的菜单中。

如果系统中没有安装可以满足这个intent的应用,那么不会添加任何菜单项。

注解:CATEGORY_SELECTED_ALTERNATIVE 别用来处理屏幕上当前选中的元素。所以,它只能在 onCreateContextMenu() 创建菜单时使用。

例如:

@Override
public boolean onCreateOptionsMenu(Menu menu){
    super.onCreateOptionsMenu(menu);

    // 创建一个声明满足需求的Intent,被包含在我们的菜单中。
    // 提供的应用笔记包含Intent.CATEGORY_ALTERNATIVE这样类别的值。
    Intent intent = new Intent(null, dataUri);
    intent.addCategory(Intent.CATEGORY_ALTERNATIVE);

    // 搜索并把符合条件的应用添加到菜单中。
    menu.addIntentOptions(
         R.id.intent_group,  // 新的菜单项将要被添加进的菜单组
         0,      // 唯一菜单项标识(无)
         0,      // 菜单项的排序(无)
         this.getComponentName(),   // 当前activity名字
         null,   // 放置在第一个位置处的菜单项(无)
         intent, // 上面创建的用来描述我们需求的intent
         0,      // 用来控制菜单项的附加标识(无)
         null);  // 与特定菜单项关联的MenuItems数组(无)

    return true;
}

对于每个被发现的activity,都提供了用来匹配已定义的intent的intent过滤器,菜单项被添加时,使用intent过滤器的android:label作为菜单项的标题,应用的图标作为菜单项的图标。addIntentOptions() 方法返回被添加的菜单项的数目。

注解:当你调用 addIntentOptions(),它将覆盖第一个参数中的菜单组指定的所有菜单项。

允许你的activity被添加到其他的菜单中

你也可以为别的activity提供你的activity中的服务,所以你的应用也能被添加到其他应用的菜单中(反之亦可)。

为了能够被添加到其他应用的菜单中,通常你需要定义一个intent过滤器,并且要确保这个intent过滤器的类别要包含CATEGORY_ALTERNATIVE 和/或 CATEGORY_SELECTED_ALTERNATIVE 值。例如:

<intent-filter label="@string/resize_image">
    ...
    <category android:name="android.intent.category.ALTERNATIVE" />
    <category android:name="android.intent.category.SELECTED_ALTERNATIVE" />
    ...
</intent-filter>

请查阅 Intents and Intent Filters 文档了解更多关于编写intent过滤器的信息。

请查阅 Note Pad 示例代码获取使用这种技术的示例应用。


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值