添加菜单

试用 Compose 方式
Jetpack Compose 是推荐用于 Android 的界面工具包。了解如何在 Compose 中添加组件。

菜单是许多类型应用中常见的界面组件。为了提供熟悉且一致的用户体验,请使用 Menu API 呈现 activity 中的用户操作和其他选项。

显示溢出菜单示例的图片
图 1.通过点按图标触发的菜单,显示在溢出菜单图标下方。

本文档介绍了如何在所有 Android 版本上创建三种基本类型的菜单或操作呈现方式:

选项菜单和应用栏
选项菜单是 activity 的主菜单项集合。您可以在其中放置对应用产生全局影响的操作,例如“搜索”“撰写电子邮件”和“设置”。

请参阅创建选项菜单部分。

上下文菜单和关联操作模式
上下文菜单是用户轻触并按住某个元素时显示的浮动菜单。它提供的操作会影响所选内容或上下文框架。

关联操作模式可在屏幕顶部的栏中显示影响所选内容的操作项,并允许用户选择多项。

请参阅创建上下文菜单部分。

弹出式菜单
弹出式菜单显示固定在调用该菜单的视图中的垂直列表项。它非常适用于提供与特定内容相关的大量操作,或者为命令的第二部分提供选项。弹出式菜单中的操作不会直接影响对应的内容,而上下文操作的作用正是如此。相反,弹出菜单适用于与 activity 中的内容区域相关的扩展操作。

请参阅创建弹出式菜单部分。

使用 XML 定义菜单

对于所有菜单类型,Android 提供标准的 XML 格式来定义菜单项。您可以在 XML 菜单资源中定义菜单及其所有项,而不是在 activity 的代码中构建菜单。然后,您可以在 activity 或 fragment 中膨胀菜单资源,即将其作为 Menu 对象加载。

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

  • 利用 XML 更易于直观掌握菜单结构。
  • 它将菜单的内容与应用的行为代码分离开来。
  • 让您利用应用资源框架,为不同的平台版本、屏幕尺寸和其他配置创建备用菜单配置。

如需定义菜单,请在项目的 res/menu/ 目录内创建一个 XML 文件,并使用以下元素构建菜单:

<menu>
定义 Menu,即菜单项的容器。<menu> 元素必须是该文件的根节点,并且可以包含一个或多个 <item><group> 元素。
<item>
创建 MenuItem,它表示菜单中的单个项。此元素可以包含嵌套的 <menu> 元素来创建子菜单。
<group>
<item> 元素的不可见容器(可选)。它可让您对菜单项进行分类,使其共享活动状态和可见性等属性。如需了解详情,请参阅创建菜单组部分。

以下是名为 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"
          app: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
关于此项在应用栏中作为操作项显示的时间和方式的规范。

这些是您使用的最重要属性,但还有许多其他属性。如需了解所有支持的属性,请参阅菜单资源文档。

您可以向任何菜单中的菜单项添加子菜单,只需添加 <menu> 元素作为 <item> 的子级即可。当应用具有大量可按主题进行整理的功能(例如 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" >
        <!-- "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() 将 XML 资源转换为可编程对象。以下几个部分展示了如何扩充每种菜单类型的菜单。

创建选项菜单

选项菜单(如图 1 所示)包含与当前 activity 上下文相关的操作和其他选项,如“搜索”“撰写电子邮件”和“设置”。

显示 Google 表格应用应用栏的图片
图 2. Google 表格应用,其中显示了多个按钮,包括操作溢出按钮。

您可以通过 Activity 子类或 Fragment 子类为选项菜单声明项。如果您的 activity 和 fragment 都为选项菜单声明了项,则这些项会合并到界面中。系统会先显示 activity 的项,然后按 fragment 添加到 activity 中的顺序,接着显示每个 fragment 的项。如有必要,您可以使用需要移动的每个 <item> 中的 android:orderInCategory 属性对菜单项重新排序。

如需为 activity 指定选项菜单,请替换 onCreateOptionsMenu()。fragment 提供自己的 onCreateOptionsMenu() 回调。在此方法中,您可以将使用 XML 定义的菜单资源膨胀到回调中提供的 Menu 中。具体可见以下示例:

Kotlin

override fun onCreateOptionsMenu(menu: Menu): Boolean {
    val inflater: MenuInflater = menuInflater
    inflater.inflate(R.menu.game_menu, menu)
    return true
}

Java

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

您还可以使用 add() 添加菜单项,并使用 findItem() 检索菜单项,从而使用 MenuItem API 修改其属性。

处理点击事件

当用户从选项菜单中选择某项(包括应用栏中的操作项)时,系统会调用您的 activity 的 onOptionsItemSelected() 方法。此方法会传递所选的 MenuItem。您可以通过调用 getItemId() 来识别菜单项,该方法会返回菜单项的唯一 ID,该 ID 由菜单资源中的 android:id 属性定义,或通过提供给 add() 方法的整数定义。您可以将此 ID 与已知的菜单项进行匹配,以执行适当的操作。

Kotlin

override fun onOptionsItemSelected(item: MenuItem): Boolean {
    // Handle item selection.
    return when (item.itemId) {
        R.id.new_game -> {
            newGame()
            true
        }
        R.id.help -> {
            showHelp()
            true
        }
        else -> super.onOptionsItemSelected(item)
    }
}

Java

@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 调用 onOptionsItemSelected(),然后按照 fragment 的添加顺序对每个 fragment 调用,直到有一个返回 true 或调用所有 fragment。

在运行时更改菜单项

系统调用 onCreateOptionsMenu() 后,会保留您填充的 Menu 的实例;除非菜单失效,否则不会再次调用 onCreateOptionsMenu()。不过,请仅使用 onCreateOptionsMenu() 来创建初始菜单状态,而不要在 activity 生命周期内进行更改。

如果要根据在 activity 生命周期中发生的事件修改选项菜单,可以在 onPrepareOptionsMenu() 方法中执行此操作。此方法会向您传递 Menu 对象(因为它当前存在),以便您对其进行修改,例如添加、移除或停用项。此外,Fragment 还提供 onPrepareOptionsMenu() 回调。

当菜单项显示在应用栏中时,选项菜单会被视为始终处于打开状态。当有事件发生且您希望执行菜单更新时,请调用 invalidateOptionsMenu() 来请求系统调用 onPrepareOptionsMenu()

创建上下文菜单

显示浮动上下文菜单的图片
图 3. 悬浮上下文菜单。

上下文菜单提供了许多操作,这些操作会影响界面中的特定项或上下文框架。您可以为任何视图提供上下文菜单,但这些菜单通常用于 RecylerView 或其他视图集合中的菜单项,用户可以在这些集合中直接对菜单项执行操作。

提供关联操作的方法有两种:

  • 使用悬浮上下文菜单。当用户轻触并按住声明支持上下文菜单的视图时,菜单会显示为菜单项的浮动列表(类似于对话框)。用户一次可对一个项目执行上下文操作。
  • 使用关联操作模式。此模式是 ActionMode 的系统实现,它会在屏幕顶部显示一个关联操作栏(简称 CAB),其中包含影响所选项的操作项。当此模式处于活动状态时,用户可以同时对多项执行操作(如果您的应用支持)。

创建悬浮上下文菜单

如需提供悬浮上下文菜单,请执行以下操作:

  1. 通过调用 registerForContextMenu() 并向其传递 View,注册与上下文菜单关联的 View

    如果您的 activity 使用 RecyclerView,并且您希望每个项都提供相同的上下文菜单,请通过将 RecyclerView 传递给 registerForContextMenu() 来注册上下文菜单的所有项。

  2. ActivityFragment 中实现 onCreateContextMenu() 方法。

    当注册的视图收到轻触并按住事件时,系统会调用您的 onCreateContextMenu() 方法。您可以在此处定义菜单项,通常通过膨胀菜单资源来实现,如以下示例所示:

    Kotlin

        override fun onCreateContextMenu(menu: ContextMenu, v: View,
                                menuInfo: ContextMenu.ContextMenuInfo) {
            super.onCreateContextMenu(menu, v, menuInfo)
            val inflater: MenuInflater = menuInflater
            inflater.inflate(R.menu.context_menu, menu)
        }
        

    Java

        @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 有多个视图,每个视图提供不同的上下文菜单,您可以使用这些参数来确定要膨胀的上下文菜单。

  3. 实现 onContextItemSelected(),如以下示例所示。当用户选择菜单项时,系统会调用此方法,以便您执行适当的操作。

    Kotlin

        override fun onContextItemSelected(item: MenuItem): Boolean {
            val info = item.menuInfo as AdapterView.AdapterContextMenuInfo
            return when (item.itemId) {
                R.id.edit -> {
                    editNote(info.id)
                    true
                }
                R.id.delete -> {
                    deleteNote(info.id)
                    true
                }
                else -> super.onContextItemSelected(item)
            }
        }
        

    Java

        @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 属性将其分配给 XML 中的每个菜单项,如在 XML 中定义菜单中所示。

    成功处理菜单项后,系统会返回 true。如果未处理菜单项,请将菜单项传递给父类实现。如果 Activity 包含 Fragment,则 Activity 会先收到此回调。通过在未处理的情况下调用父类,系统会按照每个 Fragment 的添加顺序将事件逐一传递给每个 Fragment 中的相应回调方法,直到返回 truefalseActivityandroid.app.Fragment 的默认实现会返回 false,因此请始终在未处理的情况下调用父类。

使用关联操作模式

关联操作模式是 ActionMode 的一种系统实现,它将用户互动的重点放在执行关联操作上。当用户通过选择菜单项启用此模式时,屏幕顶部会显示一个关联操作栏,显示用户可以对选定项执行的操作。启用此模式后,用户可以选择多个项(如果您的应用支持),并且可以取消选择多个项并继续在 activity 中导航。当用户取消选择所有菜单项、点按“返回”按钮或点按操作栏左侧的完成操作时,该操作模式会停用,且上下文操作栏会消失。

对于提供关联操作的视图,当发生以下两个事件或同时发生两个事件时,您通常会调用关联操作模式:

  • 用户轻触并按住视图。
  • 用户选中复选框或视图内类似的界面组件。

应用如何调用关联操作模式以及如何定义每项操作的行为,具体取决于您的设计。有两种设计:

  • 针对单个任意视图的关联操作。
  • 针对 RecyclerView 中项目组的批量上下文操作,允许用户选择多个项目并针对所有项目执行操作。

下面几部分介绍了每种场景所需的设置。

为各个视图启用关联操作模式

如果您希望仅在用户选择特定视图时调用关联操作模式,请执行以下操作:

  1. 实现 ActionMode.Callback 接口,如以下示例所示。在其回调方法中,您可以为上下文操作栏指定操作、响应操作项上的点击事件,以及处理操作模式的其他生命周期事件。

    Kotlin

        private val actionModeCallback = object : ActionMode.Callback {
            // Called when the action mode is created. startActionMode() is called.
            override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
                // Inflate a menu resource providing context menu items.
                val inflater: MenuInflater = mode.menuInflater
                inflater.inflate(R.menu.context_menu, menu)
                return true
            }
    
            // Called each time the action mode is shown. Always called after
            // onCreateActionMode, and might be called multiple times if the mode
            // is invalidated.
            override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
                return false // Return false if nothing is done
            }
    
            // Called when the user selects a contextual menu item.
            override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
                return when (item.itemId) {
                    R.id.menu_share -> {
                        shareCurrentItem()
                        mode.finish() // Action picked, so close the CAB.
                        true
                    }
                    else -> false
                }
            }
    
            // Called when the user exits the action mode.
            override fun onDestroyActionMode(mode: ActionMode) {
                actionMode = null
            }
        }
        

    Java

        private ActionMode.Callback actionModeCallback = new ActionMode.Callback() {
    
            // Called when the action mode is created. startActionMode() is 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, and might 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) {
                actionMode = null;
            }
        };
        

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

    上述示例会在操作模式被销毁时将 actionMode 变量设置为 null。在下一步中,请查看如何初始化该变量,以及将成员变量保存在 Activity 或 Fragment 中有何作用。

  2. 当您想要显示该栏时(例如当用户轻触并按住视图时),请调用 startActionMode()

    Kotlin

        someView.setOnLongClickListener { view ->
            // Called when the user performs a touch & hold on someView.
            when (actionMode) {
                null -> {
                    // Start the CAB using the ActionMode.Callback defined earlier.
                    actionMode = activity?.startActionMode(actionModeCallback)
                    view.isSelected = true
                    true
                }
                else -> false
            }
        }
        

    Java

        someView.setOnLongClickListener(new View.OnLongClickListener() {
            // Called when the user performs a touch & hold on someView.
            public boolean onLongClick(View view) {
                if (actionMode != null) {
                    return false;
                }
    
                // Start the CAB using the ActionMode.Callback defined earlier.
                actionMode = getActivity().startActionMode(actionModeCallback);
                view.setSelected(true);
                return true;
            }
        });
        

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

创建弹出式菜单

图片:显示 Gmail 应用中的弹出式菜单,并锚定在右上角的溢出按钮。
图 4. Gmail 应用中的弹出式菜单,锚定到右上角的溢出按钮。

PopupMenu 是锚定在 View 中的模态菜单。如果有足够的空间,它会显示在锚点视图下方,否则会显示在视图上方。它适用于以下情况:

  • 为与特定内容(例如 Gmail 的电子邮件标头)相关的操作提供溢出样式菜单,如图 4 所示。
  • 提供命令语句的另一部分,例如标记为 Add 且使用不同的 Add 选项生成弹出式菜单的按钮。
  • 提供类似于 Spinner 且不会保留永久选择的菜单。

如果您使用 XML 定义菜单,可按以下方法显示弹出式菜单:

  1. 使用构造函数实例化 PopupMenu,该函数接受当前应用的 Context 以及菜单锚定到的 View
  2. 使用 MenuInflater 将菜单资源膨胀到 PopupMenu.getMenu() 返回的 Menu 对象中。
  3. 调用 PopupMenu.show()

例如,以下按钮会显示弹出式菜单:

<ImageButton
    android:id="@+id/dropdown_menu"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:contentDescription="@string/descr_overflow_button"
    android:src="@drawable/arrow_drop_down" />

稍后,Activity 可按如下方式显示弹出式菜单:

Kotlin

findViewById<ImageButton>(R.id.dropdown_menu).setOnClickListener {
    val popup = PopupMenu(this, it)
    val inflater: MenuInflater = popup.menuInflater
    inflater.inflate(R.menu.actions, popup.menu)
    popup.show()
}

Java

findViewById(R.id.dropdown_menu).setOnClickListener(v -> {
    PopupMenu popup = new PopupMenu(this, v);
    popup.getMenuInflater().inflate(R.menu.actions, popup.getMenu());
    popup.show();
});

当用户选择菜单项或点按菜单以外的区域时,系统会关闭该菜单。您可以使用 PopupMenu.OnDismissListener 监听关闭事件。

处理点击事件

如需在用户选择菜单项时执行操作,请实现 PopupMenu.OnMenuItemClickListener 接口,并通过调用 setOnMenuItemclickListener() 将其注册到 PopupMenu。当用户选择某个菜单项时,系统会在您的接口中调用 onMenuItemClick() 回调。

具体可见以下示例:

Kotlin

fun showMenu(v: View) {
    PopupMenu(this, v).apply {
        // MainActivity implements OnMenuItemClickListener.
        setOnMenuItemClickListener(this@MainActivity)
        inflate(R.menu.actions)
        show()
    }
}

override fun onMenuItemClick(item: MenuItem): Boolean {
    return when (item.itemId) {
        R.id.archive -> {
            archive(item)
            true
        }
        R.id.delete -> {
            delete(item)
            true
        }
        else -> false
    }
}

Java

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;
    }
}

创建菜单组

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

如需创建组,您可以将 <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() 设置选中状态。具体可见以下示例:

Kotlin

override fun onOptionsItemSelected(item: MenuItem): Boolean {
    return when (item.itemId) {
        R.id.vibrate, R.id.dont_vibrate -> {
            item.isChecked = !item.isChecked
            true
        }
        else -> super.onOptionsItemSelected(item)
    }
}

Java

@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 时,Android 允许您向菜单动态添加菜单项。

如需根据接受 intent 的可用 activity 添加菜单项,请执行以下操作:

  1. 使用类别 CATEGORY_ALTERNATIVE 和/或 CATEGORY_SELECTED_ALTERNATIVE 以及任何其他要求定义 intent。
  2. 调用 Menu.addIntentOptions()。 然后,Android 会搜索能够执行该 intent 的所有应用,并将其添加到您的菜单中。

如果未安装任何可满足该 intent 的应用,系统不会添加任何菜单项。

具体可见以下示例:

Kotlin

override fun onCreateOptionsMenu(menu: Menu): Boolean {
    super.onCreateOptionsMenu(menu)

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

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

    return true
}

Java

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

    // Create an Intent that describes the requirements to fulfill, to be
    // included in the 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 apps.
    menu.addIntentOptions(
         R.id.intent_group,         // Menu group to which new items are 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 the 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() 方法会返回已添加的菜单项数量。

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

如需详细了解如何编写 intent 过滤器,请参阅 intent 和 intent 过滤器