Android学习笔记之菜单详解

菜单概述

每个Activity都可以指定它自己的菜单,按下硬件菜单键即可显示选项菜单。。但是从 Android 3.0(API 级别 11)开始,硬件菜单就变成了可选择的。Activity推荐使用应用栏和溢出菜单来代替传统的6键菜单。尽管某些菜单项的设计和用户体验已发生改变,但定义一系列操作和选项所使用的语义仍是以Menu API为基础。

代表了一个菜单容器,负责盛放其他菜单内容。

方法摘要:

  • abstract SubMenu addSubMenu(int titleRes):添加一个新的子菜单;
  • abstract SubMenu addSubMenu(CharSequence title):添加一个新的子菜单;
  • abstract SubMenu addSubMenu(int groupId, int itemId, int order, CharSequence title):添加一个新的处于groupId菜单组的子菜单;
  • abstract SubMenu addSubMenu(int groupId, int itemId, int order, int titleRes):添加一个新的处于groupId菜单组的子菜单。是addSubMenu(int, int, int, CharSequence) 的变种;
  • abstract MenuItem add(int titleRes):添加一个新的菜单项;
  • abstract MenuItem add(CharSequence title):添加一个新的菜单项;
  • abstract MenuItem add(int groupId, int itemId, int order, CharSequence title):添加一个新的处于groupId菜单组的菜单项;
  • abstract MenuItem add(int groupId, int itemId, int order, int titleRes):添加一个新的处于groupId菜单组的菜单项。是add(int, int, int, CharSequence) 方法的变种。
  • abstract MenuItem findItem(int id):返回指定id的菜单项;
  • abstract MenuItem getItem(int index):返回指定索引处的菜单项;
  • abstract void removeGroup(int groupId):删除指定id的菜单组;
  • abstract void removeItem(int id):删除指定id的菜单项;
  • abstract void setGroupCheckable(int group, boolean checkable, boolean exclusive):设置指定组的菜单项是否可勾选;
  • abstract void setGroupEnabled(int group, boolean enabled):设置指定组的菜单项的可用性;
  • abstract void setGroupVisible(int group, boolean visible):设置指定组的菜单项的可见性;
  • abstract int addIntentOptions(int groupId, int itemId, int order, ComponentName caller, Intent[] specifics, Intent intent, int flags, MenuItem[] outSpecificItems):添加一组菜单项,这些菜单项可以用于一个特定的Intent。

继承了Menu接口,代表一个子菜单。可以包含至少一个MenuItem。子菜单既不支持图标菜单,也不支持嵌入的子菜单。

方法摘要:

  • abstract SubMenu setHeaderIcon(int iconRes):设置该子菜单的菜单头图标为指定ID的图标;
  • abstract SubMenu setHeaderIcon(Drawable icon):设置该子菜单的菜单头图标为指定ID的Drawable资源;
  • abstract SubMenu setHeaderTitle(int titleRes):设置该子菜单的菜单头标题为指定ID的标题;
  • abstract SubMenu setHeaderTitle(CharSequence title):设置该子菜单的菜单头标题为指定的字符序列;
  • abstract SubMenu setHeaderView(View view):设置该子菜单的菜单头为指定的view;
  • abstract void clearHeader():清除菜单头;
  • abstract MenuItem getItem():获取在父菜单中代表该子菜单的MenuItem对象;
  • abstract SubMenu setIcon(Drawable icon):设置该子菜单的菜单项在父菜单中的图标;
  • abstract SubMenu setIcon(int iconRes):设置该子菜单的菜单项在父菜单中的图标。
ContextMenu接口

也继承了Menu接口,代表一个上下文菜单。可以包含至少一个MenuItem。上下文菜单不支持快捷键菜单和图表菜单。通过长按可显示上下文菜单。

方法摘要:

  • abstract void clearHeader():清除上下文菜单的菜单头;
  • abstract ContextMenu setHeaderIcon(int iconRes):设置该上下文菜单的菜单头图标为指定ID的图标;
  • abstract ContextMenu setHeaderIcon(Drawable icon):设置该上下文菜单的菜单头图标为指定ID的Drawable资源;
  • abstract ContextMenu setHeaderTitle(int titleRes):设置该上下文菜单的菜单头标题为指定ID的标题;
  • abstract ContextMenu setHeaderTitle(CharSequence title):设置该上下文菜单的菜单头标题为指定的字符序列;
  • abstract ContextMenu setHeaderView(View view):设置该上下文菜单的菜单头为指定的view。

代表一个已经存在的菜单项。

方法摘要:

  • abstract MenuItem setTitle(CharSequence title):设置该菜单项的标题;
  • abstract MenuItem setTitle(int title):设置该菜单项的标题;
  • abstract MenuItem setTitleCondensed(CharSequence title):设置该菜单项的压缩标题;
  • abstract CharSequence getTitle():获取该菜单项的标题;
  • abstract CharSequence getTitleCondensed():获取该菜单项的压缩标题;
  • abstract MenuItem setIcon(Drawable icon):设置该菜单项相关的图标;
  • abstract MenuItem setIcon(int iconRes):设置该菜单项相关的图标;
  • abstract Drawable getIcon():获取该菜单项的图标的Drawable对象(如果之前没有加载就从从资源中获取);
  • abstract MenuItem setCheckable(boolean checkable):设置该菜单项是否可勾选;
  • abstract MenuItem setChecked(boolean checked):设置该菜单项是否已勾选;
  • abstract MenuItem setEnabled(boolean enabled):设置该菜单项是否可用;
  • abstract MenuItem setVisible(boolean visible):设置该菜单项的可见性;
  • abstract boolean isCheckable():判断该菜单项是否可勾选;
  • abstract boolean isChecked(): 判断该菜单项是否已勾选;
  • abstract boolean isEnabled():判断该菜单项是否可用;
  • abstract boolean isVisible():判断该菜单项是否可见;
  • abstract int getItemId():获取该菜单项的资源ID;
  • abstract int getGroupId():获取该菜单项所在组的ID;
  • abstract ContextMenu.ContextMenuInfo getMenuInfo():获取链接到该菜单项的额外信息;
  • abstract int getOrder():获取该菜单项在组中的次序;
  • abstract void setShowAsAction(int actionEnum):设置该菜单项是否如何显示在活动条中。通过 SHOW_AS_ACTION_ALWAYS, SHOW_AS_ACTION_IF_ROOM, 或SHOW_AS_ACTION_NEVER来制定显示的样式;
  • abstract MenuItem setShowAsActionFlags(int actionEnum):同上,只是返回了设置后的菜单项;
  • abstract MenuItem setActionView(int resId):设置该菜单项的action view;
  • abstract MenuItem setActionView(View view):设置该菜单项的action view;
  • abstract View getActionView():获取该菜单项当前的action view;
  • abstract boolean collapseActionView():收缩该菜单项相关的的action view;
  • abstract boolean expandActionView():展开该菜单项相关的action view;
  • abstract boolean isActionViewExpanded():判断该菜单项的action view是否已被展开;
  • abstract MenuItem setOnActionExpandListener(MenuItem.OnActionExpandListener listener):设置该菜单项上的MenuItem.OnActionExpandListener监听器获取相关action view被展开或收缩后的通知;
  • abstract MenuItem setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener menuItemClickListener):设置该菜单项自定义的单击事件监听器;
  • abstract MenuItem setIntent(Intent intent):设置该菜单项相关的Intent;
  • abstract Intent getIntent():获取该菜单项相关的Intent;
  • abstract MenuItem setAlphabeticShortcut(char alphaChar):设置该菜单项的字母快捷键;
  • abstract MenuItem setNumericShortcut(char numericChar):设置该菜单项的数字快捷键;
  • abstract MenuItem setShortcut(char numericChar, char alphaChar):设置该菜单项的字母和数字快捷键;
  • abstract char getAlphabeticShortcut():获取该菜单项的字母快捷键;
  • abstract char getNumericShortcut():获取该菜单项的数字快捷键;
  • abstract MenuItem setActionProvider(ActionProvider actionProvider):如果该菜单项位于活动条上,设置用于创建action view的ActionProvider;
  • abstract ActionProvider getActionProvider():获取创建action view的ActionProvider;
  • abstract boolean hasSubMenu():判断该菜单项是否有相关的子菜单;
  • abstract SubMenu getSubMenu():获取该菜单项被选中时被调用的子菜单,如果有的话;

创建选项菜单

在选项菜单中,应当包括与当前 Activity 上下文相关的操作和其他选项,如“搜索”、“撰写电子邮件”和“设置”。
选项菜单中的项目在屏幕上的显示位置取决于您开发的应用所适用的 Android 版本:

  • 如果您开发的应用适用于 Android 2.3.x(API 级别 10)或更低版本,则当用户按“菜单”按钮时,选项菜单的内容会出现在屏幕底部,如图1 所示。打开时,第一个可见部分是图标菜单,其中包含多达 6 个菜单项。 如果菜单包括 6 个以上项目,则 Android 会将第六项和其余项目放入溢出菜单。用户可以通过选择“更多”打开该菜单;
  • 如果您开发的应用适用于 Android 3.0(API 级别 11)及更高版本,则选项菜单中的项目将出现在应用栏中。 默认情况下,系统会将所有项目均放入操作溢出菜单中。用户可以使用应用栏右侧的操作溢出菜单图标(或者,通过按设备“菜单”按钮(如有))显示操作溢出菜单。 要支持快速访问重要操作,您可以将 android:showAsAction=”ifRoom” 添加到对应的 元素,从而将几个项目提升到应用栏中(如图2所示)。

选项菜单不支持勾选,只显示菜单项的“浓缩”(condensed)标题。

图1 Android 2.3 系统上浏览器中的选项菜单

图2 Honeycomb Gallery 应用中的应用栏,其中显示了导航标签和相机操作项目(以及操作溢出菜单按钮

可以通过Activity子类或Fragment子类为选项菜单声明菜单项。如果Activity和Fragment均为选项菜单声明菜单项,则这些菜单项将合并到UI中。系统将首先显示Activity的菜单项,随后按每个Fragment添加到Activity中的顺序显示各Fragment的菜单项。如有必要,可以使用android:orderInCategory属性,对需要移动的每个<item>中的菜单项重新排序。

为Activity指定选项菜单,请重写Acitvity的onCreateOptionsMenu()(Fragment会提供自己的onCreateOptionsMenu())方法。通过此方法,可以将菜单资源(使用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() 方法来识别项目,该方法将返回菜单项的唯一 ID(由菜单资源中的android:id属性定义,或通过提供给add()方法的整型数定义)。您可以将此ID与已知的菜单项匹配,以执行适当的操作。例如:

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Handle item selection
    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和每个Fragment(按照每个Fragment的添加顺序)调用 onOptionsItemSelected(),直到有一个返回结果为true或所有Fragment均调用完毕为止。

另外,Android 3.0新增了一项功能,支持您在XML中使用android:onClick属性为菜单项定义点击行为。该属性的值必须是Activity使用菜单定义的方法的名称。 该方法必须是公用的,且接受单个MenuItem参数。

如果应用包含多个Activity,且其中某些Activity提供相同的选项菜单,则可考虑创建一个仅实现onCreateOptionsMenu()和onOptionsItemSelected()方法的Activity。然后,为每个应共享相同选项菜单的Activity扩展此类。若要将菜单项添加到一个子级Activity,请重写该Activity中的onCreateOptionsMenu()。调用super.onCreateOptionsMenu(menu),以便创建原始菜单项,然后使用menu.add()添加新菜单项。此外,您还可以替代各个菜单项的超类行为。

在运行时更改菜单项

系统调用onCreateOptionsMenu()后,将保留您填充的Menu实例。除非菜单由于某些原因而失效,否则不会再次调用onCreateOptionsMenu()。但是,您仅应使用onCreateOptionsMenu()来创建初始菜单状态,而不应使用它在Activity生命周期中执行任何更改。

如需根据在Activity生命周期中发生的事件修改选项菜单,则可通过Activity的onPrepareOptionsMenu()方法执行此操作。此方法向您传递Menu对象(因为该对象目前存在),以便您能够对其进行修改,如添加、移除或禁用项目。(此外,Fragment还提供它自己的onPrepareOptionsMenu() 回调。)

在Android 2.3.x及更低版本中,每当用户打开选项菜单时(按“菜单”按钮),系统均会调用onPrepareOptionsMenu()。

在Android 3.0及更高版本中,当菜单项显示在应用栏中时,选项菜单被视为始终处于打开状态。发生事件时,如果您要执行菜单更新,则必须调用Activity的invalidateOptionsMenu()来请求系统调用onPrepareOptionsMenu()。

切勿根据目前处于焦点的View更改选项菜单中的菜单项,因为处于触摸模式(用户未使用轨迹球或方向键)时,view无法形成焦点。 若要为View提供上下文相关的菜单项,请使用上下文菜单。

创建上下文菜单

上下文菜单提供了许多操作,这些操作影响UI中的特定项目或上下文框架。您可以为任何视图提供上下文菜单,但这些菜单通常用于ListView、GridView或用户可直接操作每个项目的其他view集合中的项目。

提供上下文操作的方法有两种:

  • 使用浮动上下文菜单。用户长按(按住)一个声明支持上下文菜单的view时,菜单显示为菜单项的浮动列表(类似于对话框)。用户一次可对一个项目执行上下文操作。
  • 使用上下文操作模式。此模式是ActionMode的系统实现,它将在屏幕顶部显示上下文操作栏,其中包括影响所选项目的操作项目。当此模式处于活动状态时,用户可以同时对多项执行操作(如果应用允许)。该模式可用于Android 3.0(API级别11)及更高版本,是显示上下文操作(如果可用)的首选方法。如果应用支持低于3.0版本的系统,则应在这些设备上回退到浮动上下文菜单。

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

创建浮动上下文菜单

要提供浮动上下文菜单,请执行以下操作:

  1. 通过调用Activity的registerForContextMenu(),注册与上下文菜单关联的View并将其传递给View。如果Activity使用ListView或GridView且您希望每个项目均提供相同的上下文菜单,请通过将ListView或GridView传递给registerForContextMenu(),为上下文菜单注册所有项目。
  2. 在Activity或Fragment中实现View的onCreateContextMenu()方法。当注册后的view收到长按事件时,系统将调用您的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允许您通过菜单资源扩充上下文菜单。回调方法参数包括用户所选的要添加上下文菜单的View,以及一个提供有关所选项的附加信息的ContextMenu.ContextMenuInfo对象。如果Activity有多个view,每个view均提供不同的上下文菜单,则可使用这些参数确定要扩充的上下文菜单。
3. 实现Activity的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,您应使用android:id属性将此ID分配给XML中的每个菜单项,如使用XML定义菜单部分所示。成功处理菜单项后,系统将返回true。如果未处理菜单项,则应将菜单项传递给超类实现。如果Activity包括fragment,则Activity将先收到此回调。通过在未处理的情况下调用超类,系统会将事件逐一传递给每个fragment中相应的回调方法(按照每个片段的添加顺序),直到返回true或false为止。(Activity和android.app.Fragment的默认实现返回false,因此您始终应在未处理的情况下调用超类。)

使用上下文操作模式

上下文操作模式是ActionMode的一种系统实现,它将用户交互的重点转到执行上下文操作上。用户通过选择项目启用此模式时,屏幕顶部将出现一个“上下文操作栏”,显示用户可对当前所选项执行的操作。启用此模式后,用户可以选择多个项目(若您允许)、取消选择项目以及继续在Activity内导航(在您允许的最大范围内)。当用户取消选择所有项目、按“返回”按钮或选择操作栏左侧的“完成”操作时,该操作模式将会停用,且上下文操作栏将会消失。

上下文操作栏不一定与应用栏相关联。尽管表面上看来上下文操作栏取代了应用栏的位置,但事实上二者独立运行。

对于提供上下文操作的view,当出现以下两个事件(或之一)时,您通常应调用上下文操作模式:

  • 用户长按视图。
  • 用户选中复选框或视图内的类似 UI 组件。

应用如何调用上下文操作模式以及如何定义每个操作的行为,具体取决于您的设计。设计基本上分为两种:

  • 针对单个任意视图的上下文操作。
  • 针对ListView或GridView中项目组的批处理上下文操作(允许用户选择多个项目并针对所有项目执行操作)。
为单个视图启用上下文操作模式

如果希望仅当用户选择特定视图时才调用上下文操作模式,则应:

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

例如:

  1. 实现 ActionMode.Callback 接口:
private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {

    // Called when the action mode is created; startActionMode() was called
    @Override
    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
        // Inflate a menu resource providing context menu items
        MenuInflater inflater = mode.getMenuInflater();
        inflater.inflate(R.menu.context_menu, menu);
        return true;
    }

    // Called each time the action mode is shown. Always called after onCreateActionMode, but
    // may be called multiple times if the mode is invalidated.
    @Override
    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
        return false; // Return false if nothing is done
    }

    // Called when the user selects a contextual menu item
    @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;
        }
    }

    // Called when the user exits the action mode
    @Override
    public void onDestroyActionMode(ActionMode mode) {
        mActionMode = null;
    }
};

请注意,这些事件回调与选项菜单的回调几乎完全相同,只是其中每个回调还会传递与事件相关联的ActionMode对象。您可以使用ActionMode API对CAB进行各种更改,例如:使用setTitle()和setSubtitle()(这对指示要选择多少个项目非常有用)修改标题和副标题。

另请注意,操作模式被销毁时,上述示例会将mActionMode变量设置为null。在下一步中,您将了解如何初始化该变量,以及保存Activity或fragment中的成员变量有何作用。
2. 调用startActionMode()以便适时启用上下文操作模式,例如:响应对View的长按操作:

someView.setOnLongClickListener(new View.OnLongClickListener() {
    // Called when the user long-clicks on someView
    public boolean onLongClick(View view) {
        if (mActionMode != null) {
            return false;
        }

        // Start the CAB using the ActionMode.Callback defined above
        mActionMode = getActivity().startActionMode(mActionModeCallback);
        view.setSelected(true);
        return true;
    }
});

当您调用startActionMode() 时,系统将返回已创建的ActionMode。通过将其保存在成员变量中,您可以更改上下文操作栏来响应其他事件。在上述示例中,ActionMode用于在启动操作模式之前检查成员是否为空,以确保当ActionMode实例已激活时不再重建该实例。

在ListView或GridView中启用批处理上下文操作

如果您在ListView或GridView中有一组项目(或 AbsListView 的其他扩展),且需要允许用户执行批处理操作,则应:

  • 实现AbsListView.MultiChoiceModeListener接口,并使用setMultiChoiceModeListener()为view组设置该接口。在侦听器的回调方法中,您既可以为上下文操作栏指定操作,也可以响应操作项目的点击事件,还可以处理从ActionMode.Callback接口继承的其他回调。
  • 使用 CHOICE_MODE_MULTIPLE_MODAL 参数调用 setChoiceMode()。

例如:

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) {
        // Here you can do something when items are selected/de-selected,
        // such as update the title in the CAB
    }

    @Override
    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
        // Respond to clicks on the actions in the CAB
        switch (item.getItemId()) {
            case R.id.menu_delete:
                deleteSelectedItems();
                mode.finish(); // Action picked, so close the CAB
                return true;
            default:
                return false;
        }
    }

    @Override
    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
        // Inflate the menu for the CAB
        MenuInflater inflater = mode.getMenuInflater();
        inflater.inflate(R.menu.context, menu);
        return true;
    }

    @Override
    public void onDestroyActionMode(ActionMode mode) {
        // Here you can make any necessary updates to the activity when
        // the CAB is removed. By default, selected items are deselected/unchecked.
    }

    @Override
    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
        // Here you can perform updates to the CAB due to
        // an invalidate() request
        return false;
    }
});

就这么简单。现在,当用户通过长按选择项目时,系统即会调用 onCreateActionMode() 方法,并显示包含指定操作的上下文操作栏。当上下文操作栏可见时,用户可以选择其他项目。

在某些情况下,如果上下文操作提供常用的操作项目,则您可能需要添加一个复选框或类似的UI元素来支持用户选择项目,这是因为他们可能没有发现长按行为。用户选中该复选框时,您可以通过使用setItemChecked()将相应的列表项设置为选中状态,以此调用上下文操作模式。

创建弹出菜单

从Android 3.0(API级别11)开始PopupMenu。弹出菜单是锚定到View的模态菜单。如果空间足够,它将显示在定位视图下方,否则显示在其上方。它适用于:

  • 为与特定内容确切相关的操作提供溢出样式菜单(例如,Gmail的电子邮件标头,如图4所示)。这与上下文菜单不同,后者通常用于影响所选内容的操作。对于影响所选内容的操作,请使用上下文操作模式或浮动上下文菜单;
  • 提供命令语句的另一部分(例如,标记为“添加”且使用不同的“添加”选项生成弹出菜单的按钮);
  • 提供类似于 Spinner 且不保留永久选择的下拉菜单。

图 4. Gmail 应用中的弹出菜单,锚定到右上角的溢出按钮

如果使用XML定义菜单,则显示弹出菜单的方法如下:

  1. 实例化PopupMenu及其构造函数,该函数将提取当前应用的Context以及菜单应锚定到的View;
  2. 使用MenuInflater将菜单资源扩充到PopupMenu.getMenu() 返回的Menu对象中;
  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);

    // This activity implements 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;
    }
}

创建菜单组

菜单组是指一系列具有某些共同特征的菜单项。通过菜单组,您可以:

  • 使用 setGroupVisible() 显示或隐藏所有项目
  • 使用 setGroupEnabled() 启用或禁用所有项目
  • 使用 setGroupCheckable() 指定所有项目是否可选中

通过将<item>元素嵌套在菜单资源中的<group>元素内,或者通过使用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" />
    <!-- menu group -->
    <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会保留项目的选中状态。这样一来,当用户稍后打开菜单时,您设置的选中状态将会可见。

可选中菜单项的使用往往因会话而异,且在应用销毁后不予保存。如果您想为用户保存某些应用设置,则应使用共享首选项存储数据。

添加基于Intent的菜单项

有时,您希望菜单项通过使用Intent启动Activity(无论该Activity是位于您的应用还是其他应用中)。如果您知道自己要使用的Intent,且具有启动Intent的特定菜单项,则可在相应的on-item-selected回调方法(例如,onOptionsItemSelected() 回调)期间使用startActivity() 执行 Intent。

但是,如果不确定用户的设备是否包含可处理Intent的应用,则添加调用Intent的菜单项可能会导致该菜单项无法正常工作,这是因为Intent可能无法解析为Activity。为了解决这一问题,当Android在设备上找到可处理Intent的Activity 时,则允许您向菜单动态添加菜单项。

要根据接受Intent的可用Activity添加菜单项,请执行以下操作:

  1. 使用类别CATEGORY_ALTERNATIVE和/或CATEGORY_SELECTED_ALTERNATIVE(用于处理屏幕上当前所选的元素。因此,只有在 onCreateContextMenu() 中创建菜单时,才能使用它)以及任何其他要求定义Intent。
  2. 调用Menu.addIntentOptions()。Android随后即会搜索能够执行Intent的所有应用,并将其添加到菜单中。

如果未安装可处理Intent的应用,则不会添加任何菜单项。

例如:

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

    // Create an Intent that describes the requirements to fulfill, to be included
    // in our menu. The offering app must include a category value of Intent.CATEGORY_ALTERNATIVE.
    Intent intent = new Intent(null, dataUri);
    intent.addCategory(Intent.CATEGORY_ALTERNATIVE);

    // Search and populate the menu with acceptable offering applications.
    menu.addIntentOptions(
         R.id.intent_group,  // Menu group to which new items will be added
         0,      // Unique item ID (none)
         0,      // Order for the items (none)
         this.getComponentName(),   // The current activity name
         null,   // Specific items to place first (none)
         intent, // Intent created above that describes our requirements
         0,      // Additional flags to control items (none)
         null);  // Array of MenuItems that correlate to specific items (none)

    return true;
}

如果发现Activity提供的Intent过滤器与定义的Intent匹配,则会添加菜单项,并使用Intent过滤器android:label中的值作为菜单项标题,使用应用图标作为菜单项图标。addIntentOptions()方法将返回已添加的菜单项数量。调用addIntentOptions()方法时,它将使用第一个参数中指定的菜单组替代所有菜单项。

允许将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>

使用XML定义菜单

在Android中推荐使用XML定义菜单,而不是在Activity代码中创建菜单。定义菜单的XML文件后,可以使用MenuInflater Service的inflate方法(通常位于onCreateOptionsMenu方法中)把菜单填充到应用中。

使用菜单资源是一种很好的做法,原因如下:

  • 更易于使用XML可视化菜单结构
  • 将菜单内容与应用的行为代码分离
  • 允许您利用应用资源框架,为不同的平台版本、屏幕尺寸和其他配置创建备用菜单配置

每个菜单定义都存储在res/menu文件夹下单独的一个XML文件中,每个文件只包含一个菜单。文件名就是菜单的资源ID。

语法:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@[+][package:]id/resource_name"
          android:title="string"
          android:titleCondensed="string"
          android:icon="@[package:]drawable/drawable_resource_name"
          android:onClick="method name"
          android:showAsAction=["ifRoom" | "never" | "withText" | "always" | "collapseActionView"]
          android:actionLayout="@[package:]layout/layout_resource_name"
          android:actionViewClass="class name"
          android:actionProviderClass="class name"
          android:alphabeticShortcut="string"
          android:numericShortcut="string"
          android:checkable=["true" | "false"]
          android:visible=["true" | "false"]
          android:enabled=["true" | "false"]
          android:menuCategory=["container" | "system" | "secondary" | "alternative"]
          android:orderInCategory="integer" />
    <group android:id="@[+][package:]id/resource name"
           android:checkableBehavior=["none" | "all" | "single"]
           android:visible=["true" | "false"]
           android:enabled=["true" | "false"]
           android:menuCategory=["container" | "system" | "secondary" | "alternative"]
           android:orderInCategory="integer" >
        <item />
    </group>
    <item >
        <menu>
          <item />
        </menu>
    </item>
</menu>

菜单元素详解:

<menu>:定义Menu,即菜单项的容器。xmlns:android为XML命名空间,该属性值必须为”http://schemas.android.com/apk/res/android“。<menu> 元素必须是该文件的根节点,并且能够包含一个或多个<item> 和<group> 元素。
<item>:创建 MenuItem,此元素表示菜单中的一项,必须作为<menu> 或<roup>元素的子元素。可包含嵌套的 <menu> 元素,以便创建子菜单。其属性详解:

  • android:id:ID资源类型。该属性为该菜单项的ID;
  • android:title:字符串资源类型。菜单项的标题;
  • android:titleCondensed:字符串资源类型。一个浓缩(condensed)菜单项标题。当上面的正常标题太长时显示该标题;
  • android:icon:可绘制资源类型。用作菜单项图标的图片;
  • android:onClick:指定方法名。该菜单项被单击之后将调用这里指定的方法。该方法必须在相应的Activity中声明为public并且将该MenuItem作为其唯一参数。该方法的执行优先于onOptionsItemSelected();
  • android:actionViewClass:类名,指定用作Action View的view类的全限定类名。如android.widget.SearchView可以将Search View设置为该Action View;
  • android:actionProviderClass:类名,指定替代活动项的ActionProvider类的全限定类名。如将该属性设置为”android.widget.ShareActionProvider”来使用ShareActionProvider;
  • android:alphabeticShortcut:Char类型,指定字母快捷键;
  • android:numericShortcut:Integer类型,指定数字快捷键;
  • android:checkable:布尔型,若为ture则表示该菜单项是可勾选的;
  • android:checked:布尔型,若为ture则表示该菜单项默认是勾选的;
  • android:visible:布尔型,若为ture则表示该菜单项默认是可见的;
  • android:enabled:布尔型,若为ture则表示该菜单项默认是可用的;
  • android:orderInCategory:Integer类型,表示该菜单项在菜单组中的重要性顺序。
  • android:menuCategory: 与Menu CATEGORY_*系列常量相关用于指定菜单项优先级的关键字:

    • container:是容器一部分的菜单项;
    • system:系统提供的菜单项;
    • secondary:用户提供的次要的不常用选项;
    • alternative:当前显示的数据上可选的菜单项;
  • android:showAsAction:通过指定一下关键字来何时及如何在应用栏上显示该菜单项。菜单项只有在Activity有应用栏时才能显示成活动项:

    • ifRoom:如果应用栏有空间就把该菜单项放到应用栏上,如果没有足够的空间放置所有ifRoom标记的菜单项。具有最低的orderInCategory的菜单项显示为活动项,其余的菜单项显示为溢出菜单;
    • withText:也在活动项上包括标题文本(由android:title定义) 。可以和其他值一起设置,使用|号分隔多个值;
    • never:永远不把该菜单项添加到应用栏,而是把该菜单项列入应用栏的溢出菜单中;
    • always:一直把该菜单项放入应用栏中,除非菜单项不该显示在活动条上。设置多个菜单项一直显示为活动项会导致它们与其他UI组件在应用栏上重叠;
    • collapseActionView|与该活动项相关的Action View(由Android:actionLayout和android:actionViewClass定义的)是可折叠的。从API 14开始引入;

<group>:存放若干个<item>元素的可选菜单组。可谓同一个菜单组中的菜单项指定相同的特性,如同时设置它们的可见性、可用性及可选性。必须把该元素放在<menu>元素中。属性如下:

  • android:id:资源ID,指定该菜单组的资源标识符;
  • android:checkableBehavior:通过以下关键字来指定该菜单组的选择行为:

    • none:不可选择
    • all:该菜单组中的全部菜单项都可勾选(使用checkboxes)
    • single:该菜单组中的菜单项只能选择一个(使用radio buttons)
  • android:visible:布尔型,设置该菜单组中的全部菜单项的可见性;

  • android:enabled:布尔型,设置该菜单组中的全部菜单项的可用性;
  • android:orderInCategory:Integer型,指定所有菜单项在分组中的默认顺序;
  • android:menuCategory:与Menu CATEGORY_*系列常量相关用于指定菜单组的优先级的关键字:

    • container:是容器一部分的菜单组;
    • system:系统提供的菜单组;
    • secondary:用户提供的次要的不常用选组;
    • alternative:当前显示的数据上可选的菜单组。

示例:
res/menu/example_menu.xml:

<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/item1"
          android:title="@string/item1"
          android:icon="@drawable/group_item1_icon"
          android:showAsAction="ifRoom|withText"/>
    <group android:id="@+id/group">
        <item android:id="@+id/group_item1"
              android:onClick="onGroupItemClick"
              android:title="@string/group_item1"
              android:icon="@drawable/group_item1_icon" />
        <item android:id="@+id/group_item2"
              android:onClick="onGroupItemClick"
              android:title="@string/group_item2"
              android:icon="@drawable/group_item2_icon" />
    </group>
    <item android:id="@+id/submenu"
          android:title="@string/submenu_title"
          android:showAsAction="ifRoom|withText" >
        <menu>
            <item android:id="@+id/submenu_item1"
                  android:title="@string/submenu_item1" />
        </menu>
    </item>
</menu>

在java代码中创建并相应菜单单击事件:

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

public void onGroupItemClick(MenuItem item) {
    // One of the group items (using the onClick attribute) was clicked
    // The item parameter passed here indicates which item it is
    // All other menu item clicks are handled by onOptionsItemSelected()
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值