選單

選單是一種適用於多種應用程式的常見使用者介面元件。為了提供熟悉且一致的使用者體驗,您應在活動中使用 Menu API 為使用者呈現動作和其他選項。

從 Android 3.0 (API 級別 11) 開始,搭載 Android 的裝置不再需要提供專用的「Munu」(選單) 按鈕。在這項改變下,Android 應用程式不應再使用傳統的 6 項式選單面板,應改為使用一個應用程式列來顯示常見的使用者操作。

儘管部分選單項目的設計和使用者體驗有所改變,但用來定義一組操作和選項的語意仍是以 Menu API 為基礎。本指南說明如何在所有版本的 Android 上建立三種基本選單類型或操作的呈現:

選項選單和應用程式列
選項選單是一項活動的選單項目的主集合。在這裡,您應該放置會對應用程式造成全域影響的操作,例如「搜尋」、「撰寫電子郵件」和「設定」。

請參閱建立選項選單一節。

內容選單及關聯動作模式
內容選單是一種浮動式選單,會在使用者長按元素時顯示。這提供的一些操作會影響所選的內容或內容畫面。

關聯動作模式會在螢幕上方長條中顯示會影響所選內容的操作項目,並允許使用者選取多個選項。

請參閱「建立關聯選單」一節。

彈出式選單
彈出式選單會以垂直清單的方式顯示一列項目,該清單會固定在呼叫出選單的檢視畫面中。這有助於提供與特定內容相關的操作溢位,或為一項指令提供第二部分的選項。在彈出式選單中的操作不應直接影響相對應的內容,這就是關聯動作的目的。相反地,彈出式選單適合在您的活動中,用來擴充與內容區域相關的操作。

請參閱建立彈出式選單一節。

以 XML 定義選單

對於所有類型的選單,Android 都提供了標準的 XML 格式來定義選單項目。 您不應在活動的程式碼中建立選單,而是應於 XML 選單資源中定義選單及其所有項目。接著即可在活動或片段中加載選單資源 (以 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"
          android: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> 的子項,即可在任何選單中為項目新增子選單。如果應用程式有許多功能可以依主題分類,就如同電腦應用程式選單列中的各個項目 (「檔案」、「編輯」、「檢視」等),就可以用到子選單。例如:

<?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>

若要在活動中使用選單,必須使用 MenuInflater.inflate() 加載選單資源 (將 XML 資源轉換為可編寫程式的物件)。以下章節將為您介紹如何為每種選單類型加載選單。

建立選項選單

圖 1. 瀏覽器中的選項選單。

選項選單應包含與目前活動情境相關的操作和其他選項,例如「搜尋」、「撰寫電子郵件」及「設定」。

畫面中顯示選項選單的項目的位置,取決於您開發應用程式的版本:

  • 如果您是在 Android 2.3.x (API 級別 10) 或以下版本開發應用程式,則當使用者按下「選單」按鈕時,選項選單中的內容會出現在螢幕頂端 (如圖 1 所示)。開啟時,首先顯示的部分是圖示選單,最多可顯示六個選單項目。如果選單的項目多於六個,Android 會將第六個與其餘的項目放置在溢位選單中,使用者只需選擇「更多項目」即可開啟該選單。
  • 如果您是在 Android 3.0 (API 級別 11) 以上版本開發應用程式,選項選單中的項目會顯示在應用程式列。在預設的情況下,系統會將所有項目都放在動作溢位中,使用者可以透過應用程式列右側的動作溢位圖示查看 (或是如果有的話,按下裝置的「選單」按鈕)。若要能夠快速存取重要動作,可以在對應的 <item> 元素中加入 android:showAsAction="ifRoom",讓一些項目升級顯示在應用程式列中 (見圖 2)。

    若要進一步瞭解操作項目及其他應用程式列的行為,請參閱新增應用程式列訓練課程。

圖 2. Google 試算表應用程式,顯示了多個按鈕,包括動作溢位按鈕在內。

您可以從 Activity 子類別或 Fragment 子類別中宣告選項選單的項目。如果您的活動和片段都宣告了選項選單的項目,使用者介面會將這些項目合併。活動的項目會先顯示,接著依照每個片段被加入活動的順序顯示各個片段的項目。如有需要,您可以在每個需要移動的 <item> 中使用 android:orderInCategory 屬性重新排序選單項目。

若要指定活動的選項選單,請覆寫 onCreateOptionsMenu() (由片段自行提供其 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 關聯的屬性。

如果您是針對 Android 2.3.x 及更低版本開發應用程式,系統會在使用者首次打開選單時呼叫 onCreateOptionsMenu() 以建立選項選單。如果您是為 Android 3.0 及以上版本開發,系統會在啟動活動時呼叫 onCreateOptionsMenu(),讓項目顯示在應用程式列中。

處理點擊事件

當使用者從選項選單中選取項目 (包括應用程式列中的動作項目) 時,系統會呼叫活動的 onOptionsItemSelected() 方法。此方法會傳遞所選的 MenuItem。您可以藉由呼叫 getItemId() 確認該項目,這會回傳選單項目的專屬 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() 父類別執行 (預設執行結果會回傳否)。

如果活動中含有片段,系統會先針對活動呼叫 onOptionsItemSelected(),接著針對每個片段呼叫 (依每個片段加入的順序),直到一個呼叫回傳 true 或所有片段均被呼叫為止。

提示:Android 3.0 新增了一項功能,可以使用 android:onClick 屬性以 XML 定義選單項目的點擊行為。屬性值必須是方法的名稱,該名稱由使用選單的活動定義。此方法必須公開且接受單一的 MenuItem 參數 — 當系統呼叫此方法時,它會傳遞已選取的選單項目。如需詳細資訊和範例,請參閱選單資源文件。

提示:如果您的應用程式具有多項活動,而其中部分使用的是相同的選項選單,請考慮建立一個僅執行 onCreateOptionsMenu()onOptionsItemSelected() 方法的活動。接著將這個類別擴充給每個應共用相同選項選單的活動使用。這麼一來,您就可以管理一組用來處理選單操作的程式碼,而每個子類別都可繼承這個選單行為。如果您想為其中一個子活動新增選單項目,請在該活動中覆寫 onCreateOptionsMenu()。呼叫 super.onCreateOptionsMenu(menu) 即可建立原始選單項目,接著以 menu.add() 新增選單項目。您也可以為個別的選單項目覆寫父類別的行為。

在執行階段變更選單項目

系統呼叫 onCreateOptionsMenu() 後,會保留您填入的 Menu 執行個體而不會再次呼叫 onCreateOptionsMenu(),除非該選單因特定原因變為無效。不過,建議您只將 onCreateOptionsMenu() 用於建立初始選單狀態,不要在活動生命週期內進行變更。

若想要依據活動生命週期中發生的事件修改選項選單,可以使用 onPrepareOptionsMenu() 方法。此方法會向您傳遞目前存在的 Menu 物件,因此您可以加以修改,例如新增、移除或停用項目。(片段也會提供 onPrepareOptionsMenu() 回呼)。

在 Android 2.3.x 及更低的版本中,使用者每次開啟選項選單 (按下「選單」按鈕) 時,系統便會呼叫 onPrepareOptionsMenu()

在 Android 3.0 以上的版本中,當應用程式列顯示選單項目時,選項選單會一律開啟。當事件發生且您想要更新選單時,您必須呼叫 invalidateOptionsMenu() 以要求系統呼叫 onPrepareOptionsMenu()

注意:請勿根據目前焦點所在的 View 變更選項選單中的項目。在輕觸模式中 (當使用者未使用軌跡球或 D-pad 時) 檢視畫面無法取得焦點,因此,因此您不應以焦點做為修改選項選單中項目的基礎。若要提供依 View 內容而定的選單項目,請使用內容選單

建立內容選單

圖 3. 浮動內容選單 (左) 和關聯動作列 (右) 的螢幕截圖。

內容選單可提供會影響使用者介面中特定項目或內容畫面的操作。您可以為任何檢視畫面提供內容選單,但它們通常用於 ListViewGridView 中的項目,或其他使用者可以直接對各個項目進行操作的檢視畫面集合。

提供內容關聯操作的方法有兩種:

  • 浮動內容選單中。當使用者在檢視畫面上長按 (按住),且該檢視畫面宣告支援內容選單時,選單就會以浮動式清單的方式顯示選單項目清單 (類似於一個對話方塊)。使用者一次可以對一個項目執行內容關聯操作。
  • 關聯動作模式中。此模式是系統執行 ActionMode 的作法,會在畫面頂端顯示關聯動作列,其中包含會影響所選項目的操作項目。啟用此模式時,使用者一次可對多個項目執行一項操作 (如果應用程式允許)。

注意:關聯動作模式適用於 Android 3.0 (API 級別 11) 及以上版本,且可以的話,建議您盡可能使用此技術來顯示關聯動作。如果您的應用程式支援 3.0 以下版本,針對這些裝置您應該回到使用浮動內容選單。

建立浮動內容選單

如何提供浮動內容選單:

  1. 呼叫 registerForContextMenu() 並將其傳遞到 View,以註冊與內容選單相關的 View

    如果您的活動使用 ListViewGridView 且您希望每個項目都提供相同的內容選單,那麼請將 ListViewGridView 傳遞到 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 物件。如果活動有多個檢視畫面,且每個檢視畫面提供不同的內容選單,您可以利用這些參數來判斷要加載哪些內容選單。

  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 傳回。如果您不處理選單項目,則應將選單項目傳至父類別進行實作。如果您的活動含有片段,活動會先收到此回呼。在未處理的情況下呼叫父類別時,系統會把此事件傳遞到每個片段中相應的回呼方法,每次傳遞一個 (依每個片段的新增順序),直至傳回 truefalse 為止。(Activityandroid.app.Fragment 的預設作法會傳回 false,因此您必須在未處理時呼叫父類別。)

使用關聯動作模式

關聯動作模式是 ActionMode 的系統實作,著重於將使用者互動轉為執行關聯操作。當使用者選取某個項目啟用此模式時,螢幕頂端會出現關聯動作列,以顯示使用者可以對目前選取項目執行的操作。啟用此模式後,使用者就可以選取多個項目 (如果您允許的話)、取消選取項目,以及繼續在活動中瀏覽 (視您允許的情況而定)。當使用者取消選取所有項目、按下「BACK」(返回) 按鈕或選取列左側的「Done」(完成) 動作時,操作模式即會停用,關聯動作列會消失。

注意:關聯動作列不需與應用程式列有所關聯。雖然關聯動作列看起來會出現在應用程式列的位置,但它們是獨立作業的。

針對提供內容關聯操作的檢視畫面,通常應在兩個事件之一 (或兩者) 上叫用關聯動作模式:

  • 使用者長按檢視畫面。
  • 使用者在檢視畫面中選取核取方塊或類似的 UI 元件。

您的設計決定了應用程式如何叫用關聯動作模式,並定義了每個操作的行為。基本上有兩種設計:

  • 用於個別任意檢視畫面的內容關聯操作。
  • 用於 ListViewGridView 中對項目群組進行批次關聯操作 (允許使用者選取多個項目並對其執行操作)。

以下章節介紹了各個情境所需的設定。

為個別檢視畫面啟用關聯動作模式

如果只想在使用者選取特定檢視畫面時叫用關聯動作模式,您應採取下列做法:

  1. 執行 ActionMode.Callback 介面。在回呼方法中,您可以指定關聯動作列的操作、回應操作項目的點擊事件,以及為操作模式處理其他生命週期事件。
  2. 若要顯示列 (例如在使用者長按檢視畫面時),請呼叫 startActionMode()

例如:

  1. 執行 ActionMode.Callback 介面:

    Kotlin

    private val actionModeCallback = object : ActionMode.Callback {
        // Called when the action mode is created; startActionMode() was 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, but
        // may 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() 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) {
            actionMode = null;
        }
    };
    

    請注意,這些事件回呼與選項選單中的回呼幾乎相同,唯每個回呼也會傳送與事件相關的 ActionMode 物件。您可以使用 ActionMode API 對 CAB 進行多項變更,例如利用 setTitle()setSubtitle() 修改標題及子標題 (可用來指出已選取的項目數量)。

    另外也請注意,上述範例在刪除操作模式時,會將 actionMode 變數設為 null。在下一步中,您將瞭解其初始化的方式,以及在活動或片段中儲存成員變數的好處。

  2. 視情況呼叫 startActionMode() 以啟用關聯動作模式,例如對長按 View 做出回應:

    Kotlin

    someView.setOnLongClickListener { view ->
        // Called when the user long-clicks on someView
        when (actionMode) {
            null -> {
                // Start the CAB using the ActionMode.Callback defined above
                actionMode = activity?.startActionMode(actionModeCallback)
                view.isSelected = true
                true
            }
            else -> false
        }
    }
    

    Java

    someView.setOnLongClickListener(new View.OnLongClickListener() {
        // Called when the user long-clicks on someView
        public boolean onLongClick(View view) {
            if (actionMode != null) {
                return false;
            }
    
            // Start the CAB using the ActionMode.Callback defined above
            actionMode = getActivity().startActionMode(actionModeCallback);
            view.setSelected(true);
            return true;
        }
    });
    

    當您呼叫 startActionMode() 時,系統會傳回建立的 ActionMode。將此項目儲存至成員變數中,即可變更關聯動作列以回應其他事件。在上述的範例中,ActionMode 是用來確保在活動期間不會重新建立 ActionMode 執行個體,方法是先檢查成員的值是否為空值,接著再啟用操作模式。

在 ListView 或 GridView 中啟用批次關聯動作

如果您在 ListViewGridView (或其他 AbsListView 的擴充) 中有某項目集合,而且想讓使用者執行批次操作,則應採取以下做法:

例如:

Kotlin

val listView: ListView = getListView()
with(listView) {
    choiceMode = ListView.CHOICE_MODE_MULTIPLE_MODAL
    setMultiChoiceModeListener(object : AbsListView.MultiChoiceModeListener {
        override fun onItemCheckedStateChanged(mode: ActionMode, position: Int,
                                               id: Long, checked: Boolean) {
            // Here you can do something when items are selected/de-selected,
            // such as update the title in the CAB
        }

        override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
            // Respond to clicks on the actions in the CAB
            return when (item.itemId) {
                R.id.menu_delete -> {
                    deleteSelectedItems()
                    mode.finish() // Action picked, so close the CAB
                    true
                }
                else -> false
            }
        }

        override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
            // Inflate the menu for the CAB
            val menuInflater: MenuInflater = mode.menuInflater
            menuInflater.inflate(R.menu.context, menu)
            return true
        }

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

        override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
            // Here you can perform updates to the CAB due to
            // an <code><a href="/reference/android/view/ActionMode.html#invalidate()">invalidate()</a></code> request
            return false
        }
    })
}

Java

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 <code><a href="/reference/android/view/ActionMode.html#invalidate()">invalidate()</a></code> request
        return false;
    }
});

大功告成!當使用者以長按的方式選取一個項目時,系統會呼叫 onCreateActionMode() 方法,並顯示含有指定操作的關聯動作列。顯示出關聯動作列後,使用者即可以選取其他項目。

在某些情況下,關聯操作會提供常用的操作項目,您可能會想要新增核取方塊或類似的 UI 元素,讓使用者可以選取項目,因為他們可能不會發現長按的行為。當使用者選取核取方塊時,您可以叫用關聯動作模式,方法是透過 setItemChecked() 將對應的清單項目設定為勾選狀態。

建立彈出式選單

圖 4. Gmail 應用程式的彈出式選單,其固定在右上方的溢位按鈕上。

PopupMenu 是固定在 View 上的模式選單。如果有空間,它或顯示在錨定檢視畫面下方,或是在檢視畫面上方。這適用於:

  • 為與特定內容 (例如圖 4 中顯示的 Gmail 電子郵件標頭) 相關的操作提供溢位樣式選單。

    注意:這與內容選單不同,通常是用以影響所選內容的操作。針對會影響所選內容的操作,請使用關聯動作模式浮動內容選單

  • 提供指令句的第二部分 (例如標示「新增」的按鈕,可產生包含不同「新增」選項的彈出式選單)。
  • 提供類似 Spinner 而不會保留永久選項的下拉式選單。

注意: PopupMenu 適用於 API 級別 11 及以上版本。

如果您以 XML 定義選單,您可以按照下列步驟顯示彈出式選單:

  1. 利用其建構函式將 PopupMenu 執行個體化,此操作會將目前應用程式的 ContextView 帶到選單應該錨定的地方。
  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" />

這項活動隨即會顯示如下的彈出式選單:

Kotlin

fun showPopup(v: View) {
    val popup = PopupMenu(this, v)
    val inflater: MenuInflater = popup.menuInflater
    inflater.inflate(R.menu.actions, popup.menu)
    popup.show()
}

Java

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() 回呼。

例如:

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

建立選單群組

選單群組是指一組共用特定特色的選單項目集合。透過這個群組,您可以:

您可以在選單資源中的 <group> 元素內建立巢狀結構 <item> 元素,以此建立一個群組,或透過 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);
    }
}

如果您未以這種方式設定勾選狀態,則使用者選取該項目 (核取方塊或圓形按鈕) 時,視覺上的狀態不會改變。若您設定了狀態,活動會保留項目勾選的狀態,因此當使用者之後開啟選單時,就可以看到您設定的已勾選狀態。

注意:可勾選的選單項目僅適用於該工作階段使用,且在刪除應用程式後不會儲存。如果您的應用程式設定會為使用者儲存,則應使用共用偏好設定儲存資料。

根據意圖新增選單項目

有時候,您可能想讓選單項目使用 Intent 啟動活動 (無論是您的應用程式或其他應用程式中的活動)。如果您知道要使用的意圖為何,而且具有可啟動該意圖的特定選單項目,就可以在適當的選取項目回呼方法 (例如 onOptionsItemSelected() 回呼) 中,使用 startActivity() 執行意圖。

然而,如果您不確定使用者裝置是否具有會處理該意圖的應用程式,那麼新增會叫用該意圖的選單項目,可能會導致選單項目無法運作,因為該意圖可能無法解析活動。為解決此問題,當 Android 在處理您的意圖的裝置上找到活動時,您可透過動態的方式將選單項目加入選單。

若要根據現有可接受意圖的活動新增選單項目:

  1. 將意圖定義為類別 CATEGORY_ALTERNATIVE 及/或 CATEGORY_SELECTED_ALTERNATIVE,以及任何其他要求。
  2. 呼叫 Menu.addIntentOptions()。Android 會搜尋可執行這項意圖的應用程式,並將應用程式加入選單。

如果沒有安裝任何符合這項意圖的應用程式,系統就不會新增選單項目。

注意: CATEGORY_SELECTED_ALTERNATIVE 用於處理目前在螢幕中選取的元素。因此,只有在 onCreateContextMenu() 中建立選單時才能使用。

例如:

Kotlin

override fun onCreateOptionsMenu(menu: Menu): Boolean {
    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.
    val intent = Intent(null, dataUri).apply {
        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.componentName, // 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
}

Java

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

系統會找到提供意圖篩選器並符合所定義意圖的每個活動,並為其新增選單項目,以意圖篩選器的 android:label 值做為選單項目標題,並以應用程式圖示作為選單的項目圖示。addIntentOptions() 方法會傳回新增選單項目的數量。

注意:在呼叫 addIntentOptions() 時,系統會以第一個引數中指定的選單群組,覆寫選單中的任何及所有項目。

將活動新增至其他選單

也可以將您的活動服務提供給其他應用程式,您的應用程式就可以被納入其他應用程式的選單中 (將上述角色反轉過來)。

若要加至其他應用程式的選單中,必須一如往常定義意圖篩選器,但請務必加入意圖篩選器類別的 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>

若要進一步瞭解如何編寫意圖篩選器,請參閱意圖及意圖篩選器一文。