概述:
Menu是一种常见的用户操作组件. 为了提供熟悉和统一的用户体验, 我们应该使用Menu的API来提供Menu操作.
从Android3.0开始, Android不要求手机都提供一个统一的菜单按钮. 随着这一变化, Android APP应该远离依赖传统的6项菜单的控制方式, 而是应该提供一种使用APP Bar的方式. 尽管菜单的样式发生了变化, 但是定义菜单的API依然是以前那一套. 下面是几种菜单模式:
1. Option menu和app bar
Option menu是为Activity提供的主要的菜单类型. 用于为APP的全局提供统一的某种动作,比如搜索, 写邮件和设置. 如果在Android2.3或者更低版本中, 用户可以通过Menu按钮调出options menu. 在Android3.0或更高的版本中, options menu被app bar代替. 并且传统菜单按钮被弃用.
2. Context menu 和 contextualaction mode
Context menu是当用户在某个控件上长按弹出的浮动菜单. 这种菜单用于给选中的组件提供更多的操作. 在Android3.0及以上版本中, 应该使用contextual action mode代替context menu. 这种模式允许用户在屏幕顶端的动作栏中选择操作.
3. Popup menu
Popup menu会在指定的位置显示出一个菜单, 包含一个多选项的垂直列表.通常用于在空间不足的时候提供额外的选项. Popup menu不应该挡住你想要操作的内容.
在XML文件中定义一个Menu:
Android允许我们在XML文件中使用所有格式的菜单.我们应该在菜单资源文件中定义菜单(而不是在Java代码中实现它们), 然后在代码中加载这些菜单资源. 这样做有很多好处: 在XML中定义菜单更加直观; 耦合度更低; 还可以利用菜单资源来控制我们的菜单在不同配置下(比如不同的屏幕尺寸等)的显示样式.
创建菜单资源的时候, 我们首先要在/res/menu目录中创建XML文件, 然后使用以下标签创建菜单:
<menu>: 定义一个Menu, 是item的容器.<menu>必须是XML文件的根标签, 可以包含一个或者多个<item>和<group>标签.
<item>: 创建一个MenuItem.表示菜单中的一个选项. 这个标签可以包含一个嵌套的<menu>来创建一个子菜单.
<group>: 可选项, 为<item>提供一个不可见的容器.<item>会根据group被分类, 这样它们就可以共享一些属性, 比如活动状态和是否可见.
这是一个菜单资源文件的栗子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>: 一个drawable资源, 给item指定一个图标.
<android:title>: 字符串, 为item指定标题.
<android:showAsAction>: 指定什么时候和如何作为一个actionitem显示在app bar上.
这里是几个最重要的属性, 还有其它不常用属性, 详情可以点击这里.
我们可以在<item>中添加子菜单, 可以在选项过多的时候使用:
<?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" >
<!-- "file" submenu -->
<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(). 详情见下文.
创建一个OptionsMenu:
Options menu通常用于为当前的Activity添加一些额外的动作, 比如搜索, 发邮件和设置等. Menu的选项出现在啥地方由你的Android版本决定:
如果版本是Android2.3或以下, 当用户点击Menu按键的时候, menu的选项会出现在屏幕低端. 打开的时候最先被看到的部分是icon, 拥有6个item, 如果菜单包含多于6个item, 那么将会存在一个”更多”键, 点击它就可以打开其它的项了. 这种菜单长这样:
如果版本是Android3.0或者以上, 那么options menu被移至app bar中, 默认情况下这些item被折叠在app bar的右边, 我们也可以让它们不折叠, 让一些item显示出来, 可以通过android:showAsAction=”ifRoom”来指定. 这种optionsmenu长这样:
我们可以在Activity或者Fragment中为options menu添加item. 如果在Activity和Fragment都添加了item, 那么它们将会合并. 先显示Activity的item, 然后再显示Fragment中的item. 如果有需要的话可以通过<item>的android:orderInCategory属性对其进行排列.
如果想要为Activity指定option menu, 那么我们需要重写onCreateOptionsMenu()方法, 在该方法中我们可以加载菜单资源, 比如:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.game_menu, menu);
return true;
}
我们可以通过add()方法为菜单添加item, 如果想修改的话, 首先需要findItem()找到item, 然后通过相应的MenuItem API修改.
在Android2.3及以下版本中, Android将会在第一次启动菜单的时候调用onCreateOptionsMenu()来创建options menu. 但是在Android3.0及以上版本中, 该方法将在Activity创建的时候就调用, 这样它们才能显示在app bar上.
处理点击事件:
当用户点击options menu中的item的时候, Android将会调用Activity的onOptionsitemSelected()方法. 该方法会接收一个MenuItem对象, 我们可以通过getItemId()方法来获取MenuItem对象的ID, 这个ID和XML文件中指定的android:id是一样的, 这样我们就可以识别用户点击了哪个item, 栗子:
@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);
}
}
当我们成功的处理了item的点击事件, 我们就返回true. 如果没有处理菜单事件, 应该调用父类的onOptionsItemSelected()方法, 该方法会返回false.
如果Activity中包含Fragment, 那么在调用Activity的onOptionsItemSelected()方法之后会根据Fragment添加的顺序依次调用Fragment的onOptionsItemSelected(),除非中间某个返回了true或者所有Fragment的方法都被调用才停止.
在Android3.0及以上版本中, item可以响应on-click事件了, 我们可以通过<item>的android:onClick属性指定该item被点击之后的处理方法. 该方法必须在使用菜单的Activity中定义, 必须为public, 并且接收唯一的MenuItem参数. 其余的用法跟Button的android:onClick属性类似.
如果有多个Activity都需要使用同一个options menu, 那么建议创建一个Activity, 只实现onCreateOptionsMenu()方法和onOptionsItemSelected()方法, 然后每个需要使用Optionsmenu的Activity都从这个Activity继承. 如果需要在某个Activity里面增加菜单项, 那么实现它的onCreateOptionsMenu()方法, 并且记得调用super.onCreateOptionsMenu()即可.
在运行时修改菜单项:
Android只会在Activity启动的时候调用一次onCreateOptionsMenu(),我们只应该在该方法中放一些初始化options menu的操作, 而不应该在Activity的生命周期中再做修改.
如果我们需要在某些场景下修改options menu, 那么我们可以在onPrepareOptionsMenu()方法中这么做. 该方法会接收一个Menu对象, 在该方法中可以对Menu进行修改, 比如增加删除或者不可用等(Fragment也有个这个方法).
在Android2.3及以下版本中, 每次用户点击options menu(点击菜单按钮)都会调用onPrepareOptionsMenu(). 而在3.0及以上版本中, 如果在某些事件发生的时候想要修改菜单, 那么必须调用invalidateOptionsMenu()方法, 这样Android才会调用onPrepareOptionsMenu().
我们不应该使用View是否拥有焦点来作为修改item的根据, 因为在touch-mode下(用户不使用trackball或者d-pad), view不会获得焦点. 如果想要提供上下文相关的菜单, 那么我们需要使用Context Menu.
创建ContextualMenus(上下文菜单):
Contextual menu为某个特定的View提供菜单. 我们可以为任何view提供contextualmenu, 但是大多数情况下会应用于ListView, GridView等拥有多个条目的控件. 创建contextual menu有两种方式:
1. 创建一个悬浮菜单(floating context menu). 用户可以通过长按某个view的时候弹出一个悬浮的菜单.用户每次可以在一个item上弹出一个菜单.
2. 创建上下文动作模式(contextual action mode). 这种模式下会在屏幕顶部显示一个contextualaction bar, 在这个bar上显示可以操作的项. 这种模式可以一次选择多个条目指定某一动作. 比如集体删除等.
左边是悬浮菜单, 右边是上下文动作模式. 右边这种模式只有在Android3.0及以上版本中才适用, 并且是更合适的选择. 如果APP需要支持3.0以下的版本, 那么应该使用悬浮菜单.
创建一个悬浮菜单(floating context menu):
创建悬浮菜单的步骤如下:
1. 通过registerForContextMenu()方法为悬浮菜单注册一个应该响应的View.如果该Activity使用的是ListView或者GridView, 那么应该注册它们所有的子条目.
2. 实现Activity或者Fragment中的onCreateContextMenu()方法. 当注册的View收到一个long-click事件的时候, Android将会调用onCreateContextMenu()方法. 在这里可以定义menu的item. 通常是加载一个菜单资源. 比如:
@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参数, 这个就是用户长按的那个View.
ContextMenu.ContextMenuInfo对象提供了选中的item的额外信息. 如果每个View都提供了不同的Contextmenu, 那么这个参数会有用.
3. 实现onContextItemSelected():
当用户点击某个item的时候, Android会调用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); } }
这个点击事件和上面的optionsmenu的点击事件类似, 如果没有处理某个item, 记得调用super.onContextItemSelected(item).
创建一个上下文动作模式(contextualaction mode这名字好别扭):
Contextual action mode是ActionMode的系统实现. 当用户通过选择相关条目开启这种模式的时候会在屏幕顶部出现一个contextual action bar, 用户可以在这里选择相关的菜单项, 执行对应的操作. 用户可以选择多个要操作的条目(必须选中通讯录里的条目)然后选择要执行的操作, 也可以选择”Done”或者按下返回键取消操作.
Contextual action bar跟app bar是独立管理的, 有时候Contextualaction bar甚至可以超出app bar的范围. 如果我们使用的Android3.0及以上版本, 那么应该尽量使用Contextual action mode来代替floating context menu(悬浮菜单).
在使用Contextual action mode的时候, 相关的条目(比如ListView的条目)应该监听下列事件中的一个或者两个:
1. 用户在该条目上的long-click事件
2. 用户选中一个checkbox或者类似的事件
至于具体这些操作该如何跟Contextual action mode配合, 需要由我们自己指定, 下面是两种基础用法:
1. 对于单个的View, 可以应用于任何的View上
2. 针对分组的有关联的条目, 比如ListView和GridView, 可以允许用户进行批量操作.
下面是这两种用法的栗子:
用于单个View的Contextual action mode:
如果我们打算让Contextual action mode应用于单个View, 那么:
1. 实现ActionMode.callback接口. 在它的回调方法中指定contextual action bar需要执行的操作. 响应点击action items的点击事件及其它的生命周期事件.
2. 当我们想要显示这个bar的时候(比如用户长按了指定的View), 调用startActionMode().
对应第一步的代码, 实现Action.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; } };
上述代码中的回调方法基本上跟options menu类似, 除了每个方法都传入了一个ActioinMode参数, 我们可以使用ActionMode的API对其进行修改, 比如设置title和subtitle等(在统计有多少个item被选中的时候会被使用).
对应第二步的代码, 调用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; } });
当我们调用startActioinMode()方法的时候, 它会返回创建好的ActionMode, 保存这个ActionMode在一个变量里, 之后就可以通过它来操作contextual action bar了. 上面的栗子中通过mActionMode变量的判断来确保不会重复创建.
用于ListView或者GridView的contextual action mode:
ListView和GridView(以及AbsListView的其它扩展类)都是很常用的控件, 当我们想要批处理这些控件的条目的时候, 应该这样做:
1. 实现AbsListView.MultiChoiceModeListener接口, 创建一个监听器, 然后通过setMultiChoiceModeListener()关联给相关的组件. 在这个监听器的回调方法中,可以为contextual action bar指定相关的操作; 可以响应action items上的点击事件; 还可以处理来自ActionMode.Callback接口的回调方法.
2. 调用setChoiceMode(), 并传入CHOICE_MODE_MULTIPLE_MODAL参数.
栗子:
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; } });
这样, 当用户长按某个条目的时候, Android会调用onCreateActionMode()方法, 并显示一个contextual action bar, 然后用户就可以选择其中的item进行操作了.
参考: http://developer.android.com/guide/topics/ui/menus.html