OptionsMenu:选项菜单

QuibblerQuibbler 2021-02-20 约 27 分钟 2041 次阅读

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

        

相关推荐

精选
ViewPager和PagerAdapter、FragmentPagerAdapter、FragmentStatePager
Android

ViewPager和PagerAdapter、FragmentPagerAdapter、FragmentStatePager

ViewPager1、ViewPagerandroidx.viewpager.widget.ViewPager,Android中使用非常广泛的控件,可以说是APP必备:首次打开引导页、页面Banner广告等。常用方法:setAdapter() 设置适配器setOffscreenPageLimit() 设置缓存的页面个数,默认是 1setCurrentItem() 跳转到特定的页面setOnPage

1.1k
获取Android内置WebView内核版本
Android

获取Android内置WebView内核版本

获取Android内置WebView内核版本竟然能遇到这样奇葩的事情,网页用的技术过于新颖,以至于只支持高版本Chromium内核的,低版本安卓系统中内置的内核版本较低,无法加载前端页面。1、设置查看在系统设置里 > 应用 > 应用管理 > 显示系统应用,查看WebView组件:2、页面查看通过WebView发起的网络请求,都会带上浏览器的UA,通常页面都可以通过UA判断浏览器的内核版本。这里有两

1.1w
Android 14适配总结
Android

Android 14适配总结

Android 14适配总结毕业工作至今已经适配了三个Android大版本,从Android 11到Android 12、再到Android 13。2023年,Google即将推出的Android 14,上半年已经开始第一批适配。现在,第四个Android版本已经适配完,总结记录一下。1、Android 14计划Google一般会在2月份对外发布预告,同时放出开发者预览版。“拉通”各大平台、厂商以

1.1w