OptionsMenu:选项菜单
Android中菜单有三种:OptionsMenu、ContextMenu和PopupMenu。后两种见之前写过两篇博客:ContextMenu:上下文菜单和PopupMenu:弹出菜单。
1、OptionsMenu简介
OptionsMenu是应用的主菜单,另外两种作为菜单的补充。很早之前的旧Android手机中按物理菜单键会弹出OptionsMenu。一般在状态栏上面,可以收起状态或者展开带icon。

Android提供了完善的菜单机制,先看看有哪些相关的源码。
1.1、Activity中的相关方法
框架Activity中和OptionsMenu相关的部分代码如下,通过下面这些方法,开发者可以实现应用自己的菜单。
public void invalidateOptionsMenu() {
if (mWindow.hasFeature(Window.FEATURE_OPTIONS_PANEL) &&
(mActionBar == null || !mActionBar.invalidateOptionsMenu())) {
mWindow.invalidatePanelMenu(Window.FEATURE_OPTIONS_PANEL);
}
}
public boolean onCreateOptionsMenu(Menu menu) {
if (mParent != null) {
return mParent.onCreateOptionsMenu(menu);
}
return true;
}
public boolean onPrepareOptionsMenu(Menu menu) {
if (mParent != null) {
return mParent.onPrepareOptionsMenu(menu);
}
return true;
}
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (mParent != null) {
return mParent.onOptionsItemSelected(item);
}
return false;
}
public void onOptionsMenuClosed(Menu menu) {
if (mParent != null) {
mParent.onOptionsMenuClosed(menu);
}
}
public void openOptionsMenu() {
if (mWindow.hasFeature(Window.FEATURE_OPTIONS_PANEL) &&
(mActionBar == null || !mActionBar.openOptionsMenu())) {
mWindow.openPanel(Window.FEATURE_OPTIONS_PANEL, null);
}
}
public void closeOptionsMenu() {
if (mWindow.hasFeature(Window.FEATURE_OPTIONS_PANEL) &&
(mActionBar == null || !mActionBar.closeOptionsMenu())) {
mWindow.closePanel(Window.FEATURE_OPTIONS_PANEL);
}
}1.2、Fragment中的相关方法
Fragment也提供了相同签名的OptionsMenu相关的方法,通过perform回调的方式实现和Activity一样的功能。
public void setHasOptionsMenu(boolean hasMenu) {
if (mHasMenu != hasMenu) {
mHasMenu = hasMenu;
if (isAdded() && !isHidden()) {
mFragmentManager.invalidateOptionsMenu();
}
}
}
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
}
public void onPrepareOptionsMenu(Menu menu) {
}
public void onDestroyOptionsMenu() {
}
public boolean onOptionsItemSelected(MenuItem item) {
return false;
}
public void onOptionsMenuClosed(Menu menu) {
}
boolean performCreateOptionsMenu(Menu menu, MenuInflater inflater) {
boolean show = false;
if (!mHidden) {
if (mHasMenu && mMenuVisible) {
show = true;
onCreateOptionsMenu(menu, inflater);
}
if (mChildFragmentManager != null) {
show |= mChildFragmentManager.dispatchCreateOptionsMenu(menu, inflater);
}
}
return show;
}
boolean performPrepareOptionsMenu(Menu menu) {
boolean show = false;
if (!mHidden) {
if (mHasMenu && mMenuVisible) {
show = true;
onPrepareOptionsMenu(menu);
}
if (mChildFragmentManager != null) {
show |= mChildFragmentManager.dispatchPrepareOptionsMenu(menu);
}
}
return show;
}
void performOptionsMenuClosed(Menu menu) {
if (!mHidden) {
if (mHasMenu && mMenuVisible) {
onOptionsMenuClosed(menu);
}
if (mChildFragmentManager != null) {
mChildFragmentManager.dispatchOptionsMenuClosed(menu);
}
}
}2、创建OptionsMenu
给我们的Activity添加OptionsMenu。推荐使用菜单xml的方式创建菜单,重写onCreateOptionsMenu(Menu menu)方法。
官方对于该方法调用时机的说明:Android 2.3.x 及更低版本,当用户首次打开选项菜单时,系统会调用 onCreateOptionsMenu() 来创建该菜单。Android 3.0 及更高版本,系统会在启动 Activity时调用 onCreateOptionsMenu(),以便向应用栏显示菜单项。
2.1、定义菜单xml
在项目res/目录下新建menu文件夹,里面放置菜单相关的xml文件。
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/settingsFragment"
android:icon="@mipmap/ic_launcher_round"
android:title="设置界面"/>
</menu>关于<menu />标签的语法详见 Guides > Menu resource">Android Developers > Guides > Menu resource。
<?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:alphabeticModifiers=["META" | "CTRL" | "ALT" | "SHIFT" | "SYM" | "FUNCTION"]
android:numericShortcut="string"
android:numericModifiers=["META" | "CTRL" | "ALT" | "SHIFT" | "SYM" | "FUNCTION"]
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>2.2、重写onCreateOptionsMenu(Menu menu)
重写Activity中的onCreateOptionsMenu(Menu menu)方法,通过MenuInflater加载定义在res/menu中的xml菜单。
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_settings, menu);
return true;
}当然可以通过代码的方式添加菜单选项,相关方法和源码可以查看源码Menu。
@Override
public boolean onCreateOptionsMenu(Menu menu) {
menu.addSubMenu(0, 1, 0, "文件");
menu.addSubMenu(0, 2, 1, "打开");
menu.addSubMenu(0, 3, 2, "关闭");
menu.addSubMenu(1, 4, 3, "复制");
menu.addSubMenu(1, 5, 4, "粘贴");
return true;
}不建议用代码创建菜单,方式不灵活。虽然比起加载xml效率高一些,但是毫厘之间(<1ms)的性能损失无需纠结,使用xml最简单、方便维护的方式开发菜单。
2.3、调用super.onCreateOptionsMenu(Menu menu)?
Android对是否调用父方法有一个说明,先来看看Activity中onCreateOptionsMenu(menu)的原生方法实现:
public boolean onCreateOptionsMenu(Menu menu) {
if (mParent != null) {
return mParent.onCreateOptionsMenu(menu);
}
return true;
}会递归的添加父Activity的菜单,默认应当调用 super.onCreateOptionsMenu(menu)方法。它的妙用在于将通用重复的菜单抽取出来,放到父Activity中,子Activity只需要通过调用super方法,将父Activity的菜单添加复用。不过基本上用不到了,因为绝大多数情况下mParent都是null,感兴趣的可以看看mParent是什么玩意!
3、响应点击:onOptionsItemSelected()
当用户从选项菜单中选择菜单项(包括应用栏中的操作项)时,系统会调用Activity的 onOptionsItemSelected()方法。
开发者只需重写Activity或Fragment的onOptionsItemSelected()方法,此方法会传递所选的MenuItem菜单项实例。通过调用MenuItem的getItemId()方法来识别菜单项,该方法会返回菜单项的唯一 ID(由菜单资源中的 android:id 属性定义,或通过提供给 add() 方法的整型数定义)。将此 ID 与已知的菜单项进行匹配,执行菜单对应的操作。
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
// Handle item selection
switch (item.getItemId()) {
case R.id.settingsFragment:
//
return true;
case 1:
//
return true;
default:
return super.onOptionsItemSelected(item);
}
}4、动态调整菜单项:onPrepareOptionsMenu(Menu menu)
前面通过onCreateOptionsMenu(Menu menu)方法创建菜单后,是不是不能增减菜单项了呢?如果应用运行过程中出现某些条件:增加新的菜单、旧的菜单不可选等情况该如何实现呢?
作为一个成熟的操作系统,Android这些都考虑到了。onCreateOptionsMenu(Menu menu)先会创建OptionsMenu的初始状态,对菜单的修改放到onPrepareOptionsMenu(Menu menu)回调方法中,运行开发者对菜单进行编辑:增加菜单、移除菜单、禁用某些菜单等等。
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
if (condition) {
menu.addSubMenu("close");
}
return super.onPrepareOptionsMenu(menu);
}5、其它关于OptionsMenu的方法
查看源码是最可靠的方式,开发中遇到一些难点。别急着百度、CSDN找答案。网上的经验肯定有,同样的Android也会考虑到很多场景。先看看源码,找找里面的方法、注释或许会有很大帮助。
5.1、打开/关闭OptionsMenu
在Activity的源码中查看到右两个关于OPtionsMenu的方法,通过这两个方法应用可以自主弹出或者关闭OptionsMenu,不建议应用这样操作,这应该交给用户自行打开或关闭菜单。
openOptionsMenu()打开OptionsMenu:
/**
* Programmatically opens the options menu. If the options menu is already
* open, this method does nothing.
*/
public void openOptionsMenu() {
if (mWindow.hasFeature(Window.FEATURE_OPTIONS_PANEL) &&
(mActionBar == null || !mActionBar.openOptionsMenu())) {
mWindow.openPanel(Window.FEATURE_OPTIONS_PANEL, null);
}
}closeOptionsMenu()关闭OptionsMenu:
/**
* Progammatically closes the options menu. If the options menu is already
* closed, this method does nothing.
*/
public void closeOptionsMenu() {
if (mWindow.hasFeature(Window.FEATURE_OPTIONS_PANEL) &&
(mActionBar == null || !mActionBar.closeOptionsMenu())) {
mWindow.closePanel(Window.FEATURE_OPTIONS_PANEL);
}
}5.2、关闭OptionsMenu回调
有对应的OptionsMenu关闭的回调onOptionsMenuClosed(Menu menu),当OptionsMenu被关闭时回调该方法,不常用。
/**
* This hook is called whenever the options menu is being closed .
* (either by the user canceling the menu with the back/menu button, or when an item is selected).
*
* @param menu The options menu as last shown or first initialized by onCreateOptionsMenu().
*/
public void onOptionsMenuClosed(Menu menu) {
if (mParent != null) {
mParent.onOptionsMenuClosed(menu);
}
}但是并没有对应的OptionsMenu打开时的回调onOptionsMenuOpened(Menu menu)方法,可能不需要吧。毕竟前面已经有了onCreateOptionsMenu(Menu menu)和onPrepareOptionsMenu(Menu menu)。
参考资料:
Guides > Menu resource">Android Developers > Guides > Menu resource
指南 > 菜单">Android 开发者 > 指南 > 菜单
Reference > MenuInflater">Android > Reference > MenuInflater

