1. 程式人生 > >Android學習筆記之選單詳解

Android學習筆記之選單詳解

選單概述

每個Activity都可以指定它自己的選單,按下硬體選單鍵即可顯示選項選單。。但是從 Android 3.0(API 級別 11)開始,硬體選單就變成了可選擇的。Activity推薦使用應用欄和溢位選單來代替傳統的6鍵選單。儘管某些選單項的設計和使用者體驗已發生改變,但定義一系列操作和選項所使用的語義仍是以Menu API為基礎。

代表了一個選單容器,負責盛放其他選單內容。

方法摘要:

  • abstract SubMenu addSubMenu(int titleRes):新增一個新的子選單;
  • abstract SubMenu addSubMenu(CharSequence title):新增一個新的子選單;
  • abstract SubMenu addSubMenu(int groupId, int itemId, int order, CharSequence title):新增一個新的處於groupId選單組的子選單;
  • abstract SubMenu addSubMenu(int groupId, int itemId, int order, int titleRes):新增一個新的處於groupId選單組的子選單。是addSubMenu(int, int, int, CharSequence) 的變種;
  • abstract MenuItem add(int titleRes):新增一個新的選單項;
  • abstract MenuItem add(CharSequence title):新增一個新的選單項;
  • abstract MenuItem add(int groupId, int itemId, int order, CharSequence title):新增一個新的處於groupId選單組的選單項;
  • abstract MenuItem add(int groupId, int itemId, int order, int titleRes):新增一個新的處於groupId選單組的選單項。是add(int, int, int, CharSequence) 方法的變種。
  • abstract MenuItem findItem
    (int id):返回指定id的選單項;
  • abstract MenuItem getItem(int index):返回指定索引處的選單項;
  • abstract void removeGroup(int groupId):刪除指定id的選單組;
  • abstract void removeItem(int id):刪除指定id的選單項;
  • abstract void setGroupCheckable(int group, boolean checkable, boolean exclusive):設定指定組的選單項是否可勾選;
  • abstract void setGroupEnabled(int group, boolean enabled):設定指定組的選單項的可用性;
  • abstract void setGroupVisible(int group, boolean visible):設定指定組的選單項的可見性;
  • abstract int addIntentOptions(int groupId, int itemId, int order, ComponentName caller, Intent[] specifics, Intent intent, int flags, MenuItem[] outSpecificItems):新增一組選單項,這些選單項可以用於一個特定的Intent。

繼承了Menu介面,代表一個子選單。可以包含至少一個MenuItem。子選單既不支援圖示選單,也不支援嵌入的子選單。

方法摘要:

  • abstract SubMenu setHeaderIcon(int iconRes):設定該子選單的選單頭圖示為指定ID的圖示;
  • abstract SubMenu setHeaderIcon(Drawable icon):設定該子選單的選單頭圖示為指定ID的Drawable資源;
  • abstract SubMenu setHeaderTitle(int titleRes):設定該子選單的選單頭標題為指定ID的標題;
  • abstract SubMenu setHeaderTitle(CharSequence title):設定該子選單的選單頭標題為指定的字元序列;
  • abstract SubMenu setHeaderView(View view):設定該子選單的選單頭為指定的view;
  • abstract void clearHeader():清除選單頭;
  • abstract MenuItem getItem():獲取在父選單中代表該子選單的MenuItem物件;
  • abstract SubMenu setIcon(Drawable icon):設定該子選單的選單項在父選單中的圖示;
  • abstract SubMenu setIcon(int iconRes):設定該子選單的選單項在父選單中的圖示。

ContextMenu介面

也繼承了Menu介面,代表一個上下文選單。可以包含至少一個MenuItem。上下文選單不支援快捷鍵選單和圖表選單。通過長按可顯示上下文選單。

方法摘要:

  • abstract void clearHeader():清除上下文選單的選單頭;
  • abstract ContextMenu setHeaderIcon(int iconRes):設定該上下文選單的選單頭圖示為指定ID的圖示;
  • abstract ContextMenu setHeaderIcon(Drawable icon):設定該上下文選單的選單頭圖示為指定ID的Drawable資源;
  • abstract ContextMenu setHeaderTitle(int titleRes):設定該上下文選單的選單頭標題為指定ID的標題;
  • abstract ContextMenu setHeaderTitle(CharSequence title):設定該上下文選單的選單頭標題為指定的字元序列;
  • abstract ContextMenu setHeaderView(View view):設定該上下文選單的選單頭為指定的view。

代表一個已經存在的選單項。

方法摘要:

  • abstract MenuItem setTitle(CharSequence title):設定該選單項的標題;
  • abstract MenuItem setTitle(int title):設定該選單項的標題;
  • abstract MenuItem setTitleCondensed(CharSequence title):設定該選單項的壓縮標題;
  • abstract CharSequence getTitle():獲取該選單項的標題;
  • abstract CharSequence getTitleCondensed():獲取該選單項的壓縮標題;
  • abstract MenuItem setIcon(Drawable icon):設定該選單項相關的圖示;
  • abstract MenuItem setIcon(int iconRes):設定該選單項相關的圖示;
  • abstract Drawable getIcon():獲取該選單項的圖示的Drawable物件(如果之前沒有載入就從從資源中獲取);
  • abstract MenuItem setCheckable(boolean checkable):設定該選單項是否可勾選;
  • abstract MenuItem setChecked(boolean checked):設定該選單項是否已勾選;
  • abstract MenuItem setEnabled(boolean enabled):設定該選單項是否可用;
  • abstract MenuItem setVisible(boolean visible):設定該選單項的可見性;
  • abstract boolean isCheckable():判斷該選單項是否可勾選;
  • abstract boolean isChecked(): 判斷該選單項是否已勾選;
  • abstract boolean isEnabled():判斷該選單項是否可用;
  • abstract boolean isVisible():判斷該選單項是否可見;
  • abstract int getItemId():獲取該選單項的資源ID;
  • abstract int getGroupId():獲取該選單項所在組的ID;
  • abstract ContextMenu.ContextMenuInfo getMenuInfo():獲取連結到該選單項的額外資訊;
  • abstract int getOrder():獲取該選單項在組中的次序;
  • abstract void setShowAsAction(int actionEnum):設定該選單項是否如何顯示在活動條中。通過 SHOW_AS_ACTION_ALWAYS, SHOW_AS_ACTION_IF_ROOM, 或SHOW_AS_ACTION_NEVER來制定顯示的樣式;
  • abstract MenuItem setShowAsActionFlags(int actionEnum):同上,只是返回了設定後的選單項;
  • abstract MenuItem setActionView(int resId):設定該選單項的action view;
  • abstract MenuItem setActionView(View view):設定該選單項的action view;
  • abstract View getActionView():獲取該選單項當前的action view;
  • abstract boolean collapseActionView():收縮該選單項相關的的action view;
  • abstract boolean expandActionView():展開該選單項相關的action view;
  • abstract boolean isActionViewExpanded():判斷該選單項的action view是否已被展開;
  • abstract MenuItem setOnActionExpandListener(MenuItem.OnActionExpandListener listener):設定該選單項上的MenuItem.OnActionExpandListener監聽器獲取相關action view被展開或收縮後的通知;
  • abstract MenuItem setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener menuItemClickListener):設定該選單項自定義的單擊事件監聽器;
  • abstract MenuItem setIntent(Intent intent):設定該選單項相關的Intent;
  • abstract Intent getIntent():獲取該選單項相關的Intent;
  • abstract MenuItem setAlphabeticShortcut(char alphaChar):設定該選單項的字母快捷鍵;
  • abstract MenuItem setNumericShortcut(char numericChar):設定該選單項的數字快捷鍵;
  • abstract MenuItem setShortcut(char numericChar, char alphaChar):設定該選單項的字母和數字快捷鍵;
  • abstract char getAlphabeticShortcut():獲取該選單項的字母快捷鍵;
  • abstract char getNumericShortcut():獲取該選單項的數字快捷鍵;
  • abstract MenuItem setActionProvider(ActionProvider actionProvider):如果該選單項位於活動條上,設定用於建立action view的ActionProvider;
  • abstract ActionProvider getActionProvider():獲取建立action view的ActionProvider;
  • abstract boolean hasSubMenu():判斷該選單項是否有相關的子選單;
  • abstract SubMenu getSubMenu():獲取該選單項被選中時被呼叫的子選單,如果有的話;

建立選項選單

在選項選單中,應當包括與當前 Activity 上下文相關的操作和其他選項,如“搜尋”、“撰寫電子郵件”和“設定”。
選項選單中的專案在螢幕上的顯示位置取決於您開發的應用所適用的 Android 版本:

  • 如果您開發的應用適用於 Android 2.3.x(API 級別 10)或更低版本,則當用戶按“選單”按鈕時,選項選單的內容會出現在螢幕底部,如圖1 所示。開啟時,第一個可見部分是圖示選單,其中包含多達 6 個選單項。 如果選單包括 6 個以上專案,則 Android 會將第六項和其餘專案放入溢位選單。使用者可以通過選擇“更多”開啟該選單;
  • 如果您開發的應用適用於 Android 3.0(API 級別 11)及更高版本,則選項選單中的專案將出現在應用欄中。 預設情況下,系統會將所有專案均放入操作溢位選單中。使用者可以使用應用欄右側的操作溢位選單圖示(或者,通過按裝置“選單”按鈕(如有))顯示操作溢位選單。 要支援快速訪問重要操作,您可以將 android:showAsAction=”ifRoom” 新增到對應的 元素,從而將幾個專案提升到應用欄中(如圖2所示)。

選項選單不支援勾選,只顯示選單項的“濃縮”(condensed)標題。

圖1 Android 2.3 系統上瀏覽器中的選項選單

圖2 Honeycomb Gallery 應用中的應用欄,其中顯示了導航標籤和相機操作專案(以及操作溢位選單按鈕

可以通過Activity子類或Fragment子類為選項選單宣告選單項。如果Activity和Fragment均為選項選單宣告選單項,則這些選單項將合併到UI中。系統將首先顯示Activity的選單項,隨後按每個Fragment新增到Activity中的順序顯示各Fragment的選單項。如有必要,可以使用android:orderInCategory屬性,對需要移動的每個<item>中的選單項重新排序。

為Activity指定選項選單,請重寫Acitvity的onCreateOptionsMenu()(Fragment會提供自己的onCreateOptionsMenu())方法。通過此方法,可以將選單資源(使用XML定義)填充到該方法中提供的Menu中。如:

@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及更高版本,則系統將在啟動Activity時呼叫onCreateOptionsMenu(),以便嚮應用欄顯示專案。

處理點選事件

使用者從選項選單中選擇專案(包括應用欄中的操作專案)時,系統將呼叫Activity的onOptionsItemSelected() 方法。 此方法將傳遞所選的MenuItem。您可以通過呼叫getItemId() 方法來識別專案,該方法將返回選單項的唯一 ID(由選單資源中的android:id屬性定義,或通過提供給add()方法的整型數定義)。您可以將此ID與已知的選單項匹配,以執行適當的操作。例如:

@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和每個Fragment(按照每個Fragment的新增順序)呼叫 onOptionsItemSelected(),直到有一個返回結果為true或所有Fragment均呼叫完畢為止。

另外,Android 3.0新增了一項功能,支援您在XML中使用android:onClick屬性為選單項定義點選行為。該屬性的值必須是Activity使用選單定義的方法的名稱。 該方法必須是公用的,且接受單個MenuItem引數。

如果應用包含多個Activity,且其中某些Activity提供相同的選項選單,則可考慮建立一個僅實現onCreateOptionsMenu()和onOptionsItemSelected()方法的Activity。然後,為每個應共享相同選項選單的Activity擴充套件此類。若要將選單項新增到一個子級Activity,請重寫該Activity中的onCreateOptionsMenu()。呼叫super.onCreateOptionsMenu(menu),以便建立原始選單項,然後使用menu.add()新增新選單項。此外,您還可以替代各個選單項的超類行為。

在執行時更改選單項

系統呼叫onCreateOptionsMenu()後,將保留您填充的Menu例項。除非選單由於某些原因而失效,否則不會再次呼叫onCreateOptionsMenu()。但是,您僅應使用onCreateOptionsMenu()來建立初始選單狀態,而不應使用它在Activity生命週期中執行任何更改。

如需根據在Activity生命週期中發生的事件修改選項選單,則可通過Activity的onPrepareOptionsMenu()方法執行此操作。此方法向您傳遞Menu物件(因為該物件目前存在),以便您能夠對其進行修改,如新增、移除或禁用專案。(此外,Fragment還提供它自己的onPrepareOptionsMenu() 回撥。)

在Android 2.3.x及更低版本中,每當使用者開啟選項選單時(按“選單”按鈕),系統均會呼叫onPrepareOptionsMenu()。

在Android 3.0及更高版本中,當選單項顯示在應用欄中時,選項選單被視為始終處於開啟狀態。發生事件時,如果您要執行選單更新,則必須呼叫Activity的invalidateOptionsMenu()來請求系統呼叫onPrepareOptionsMenu()。

切勿根據目前處於焦點的View更改選項選單中的選單項,因為處於觸控模式(使用者未使用軌跡球或方向鍵)時,view無法形成焦點。 若要為View提供上下文相關的選單項,請使用上下文選單。

建立上下文選單

上下文選單提供了許多操作,這些操作影響UI中的特定專案或上下文框架。您可以為任何檢視提供上下文選單,但這些選單通常用於ListView、GridView或使用者可直接操作每個專案的其他view集合中的專案。

提供上下文操作的方法有兩種:

  • 使用浮動上下文選單。使用者長按(按住)一個宣告支援上下文選單的view時,選單顯示為選單項的浮動列表(類似於對話方塊)。使用者一次可對一個專案執行上下文操作。
  • 使用上下文操作模式。此模式是ActionMode的系統實現,它將在螢幕頂部顯示上下文操作欄,其中包括影響所選專案的操作專案。當此模式處於活動狀態時,使用者可以同時對多項執行操作(如果應用允許)。該模式可用於Android 3.0(API級別11)及更高版本,是顯示上下文操作(如果可用)的首選方法。如果應用支援低於3.0版本的系統,則應在這些裝置上回退到浮動上下文選單。

圖3 浮動上下文選單(左)和上下文操作欄(右)的螢幕截圖

建立浮動上下文選單

要提供浮動上下文選單,請執行以下操作:

  1. 通過呼叫Activity的registerForContextMenu(),註冊與上下文選單關聯的View並將其傳遞給View。如果Activity使用ListView或GridView且您希望每個專案均提供相同的上下文選單,請通過將ListView或GridView傳遞給registerForContextMenu(),為上下文選單註冊所有專案。
  2. 在Activity或Fragment中實現View的onCreateContextMenu()方法。當註冊後的view收到長按事件時,系統將呼叫您的onCreateContextMenu()方法。在此方法中,您通常可通過擴充選單資源來定義選單項。例如:
@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有多個view,每個view均提供不同的上下文選單,則可使用這些引數確定要擴充的上下文選單。
3. 實現Activity的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);
    }
}

getItemId()方法將查詢所選選單項的ID,您應使用android:id屬性將此ID分配給XML中的每個選單項,如使用XML定義選單部分所示。成功處理選單項後,系統將返回true。如果未處理選單項,則應將選單項傳遞給超類實現。如果Activity包括fragment,則Activity將先收到此回撥。通過在未處理的情況下呼叫超類,系統會將事件逐一傳遞給每個fragment中相應的回撥方法(按照每個片段的新增順序),直到返回true或false為止。(Activity和android.app.Fragment的預設實現返回false,因此您始終應在未處理的情況下呼叫超類。)

使用上下文操作模式

上下文操作模式是ActionMode的一種系統實現,它將使用者互動的重點轉到執行上下文操作上。使用者通過選擇專案啟用此模式時,螢幕頂部將出現一個“上下文操作欄”,顯示使用者可對當前所選項執行的操作。啟用此模式後,使用者可以選擇多個專案(若您允許)、取消選擇專案以及繼續在Activity內導航(在您允許的最大範圍內)。當用戶取消選擇所有專案、按“返回”按鈕或選擇操作欄左側的“完成”操作時,該操作模式將會停用,且上下文操作欄將會消失。

上下文操作欄不一定與應用欄相關聯。儘管表面上看來上下文操作欄取代了應用欄的位置,但事實上二者獨立執行。

對於提供上下文操作的view,當出現以下兩個事件(或之一)時,您通常應呼叫上下文操作模式:

  • 使用者長按檢視。
  • 使用者選中複選框或檢視內的類似 UI 元件。

應用如何呼叫上下文操作模式以及如何定義每個操作的行為,具體取決於您的設計。設計基本上分為兩種:

  • 針對單個任意檢視的上下文操作。
  • 針對ListView或GridView中專案組的批處理上下文操作(允許使用者選擇多個專案並針對所有專案執行操作)。
為單個檢視啟用上下文操作模式

如果希望僅當用戶選擇特定檢視時才呼叫上下文操作模式,則應:

  1. 實現ActionMode.Callback介面。在其回撥方法中,您既可以為上下文操作欄指定操作,又可以響應操作專案的點選事件,還可以處理操作模式的其他生命週期事件。
  2. 當需要顯示操作欄時(例如,使用者長按檢視),請呼叫startActionMode()。

例如:

  1. 實現 ActionMode.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;
    }
};

請注意,這些事件回撥與選項選單的回撥幾乎完全相同,只是其中每個回撥還會傳遞與事件相關聯的ActionMode物件。您可以使用ActionMode API對CAB進行各種更改,例如:使用setTitle()和setSubtitle()(這對指示要選擇多少個專案非常有用)修改標題和副標題。

另請注意,操作模式被銷燬時,上述示例會將mActionMode變數設定為null。在下一步中,您將瞭解如何初始化該變數,以及儲存Activity或fragment中的成員變數有何作用。
2. 呼叫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;
    }
});

當您呼叫startActionMode() 時,系統將返回已建立的ActionMode。通過將其儲存在成員變數中,您可以更改上下文操作欄來響應其他事件。在上述示例中,ActionMode用於在啟動操作模式之前檢查成員是否為空,以確保當ActionMode例項已啟用時不再重建該例項。

在ListView或GridView中啟用批處理上下文操作

如果您在ListView或GridView中有一組專案(或 AbsListView 的其他擴充套件),且需要允許使用者執行批處理操作,則應:

  • 實現AbsListView.MultiChoiceModeListener介面,並使用setMultiChoiceModeListener()為view組設定該介面。在偵聽器的回撥方法中,您既可以為上下文操作欄指定操作,也可以響應操作專案的點選事件,還可以處理從ActionMode.Callback介面繼承的其他回撥。
  • 使用 CHOICE_MODE_MULTIPLE_MODAL 引數呼叫 setChoiceMode()。

例如:

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

就這麼簡單。現在,當用戶通過長按選擇專案時,系統即會呼叫 onCreateActionMode() 方法,並顯示包含指定操作的上下文操作欄。當上下文操作欄可見時,使用者可以選擇其他專案。

在某些情況下,如果上下文操作提供常用的操作專案,則您可能需要新增一個複選框或類似的UI元素來支援使用者選擇專案,這是因為他們可能沒有發現長按行為。使用者選中該複選框時,您可以通過使用setItemChecked()將相應的列表項設定為選中狀態,以此呼叫上下文操作模式。

建立彈出選單

從Android 3.0(API級別11)開始PopupMenu。彈出選單是錨定到View的模態選單。如果空間足夠,它將顯示在定位檢視下方,否則顯示在其上方。它適用於:

  • 為與特定內容確切相關的操作提供溢位樣式選單(例如,Gmail的電子郵件標頭,如圖4所示)。這與上下文選單不同,後者通常用於影響所選內容的操作。對於影響所選內容的操作,請使用上下文操作模式或浮動上下文選單;
  • 提供命令語句的另一部分(例如,標記為“新增”且使用不同的“新增”選項生成彈出選單的按鈕);
  • 提供類似於 Spinner 且不保留永久選擇的下拉選單。

圖 4. Gmail 應用中的彈出選單,錨定到右上角的溢位按鈕

如果使用XML定義選單,則顯示彈出選單的方法如下:

  1. 例項化PopupMenu及其建構函式,該函式將提取當前應用的Context以及選單應錨定到的View;
  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" />

稍後,Activity可按照如下方式顯示彈出選單:

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

例如:

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

建立選單組

選單組是指一系列具有某些共同特徵的選單項。通過選單組,您可以:

  • 使用 setGroupVisible() 顯示或隱藏所有專案
  • 使用 setGroupEnabled() 啟用或禁用所有專案
  • 使用 setGroupCheckable() 指定所有專案是否可選中

通過將<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() 設定選中狀態。例如:

@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 時,則允許您向選單動態新增選單項。

要根據接受Intent的可用Activity新增選單項,請執行以下操作:

  1. 使用類別CATEGORY_ALTERNATIVE和/或CATEGORY_SELECTED_ALTERNATIVE(用於處理螢幕上當前所選的元素。因此,只有在 onCreateContextMenu() 中建立選單時,才能使用它)以及任何其他要求定義Intent。
  2. 呼叫Menu.addIntentOptions()。Android隨後即會搜尋能夠執行Intent的所有應用,並將其新增到選單中。

如果未安裝可處理Intent的應用,則不會新增任何選單項。

例如:

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

如果發現Activity提供的Intent過濾器與定義的Intent匹配,則會新增選單項,並使用Intent過濾器android:label中的值作為選單項標題,使用應用圖示作為選單項圖示。addIntentOptions()方法將返回已新增的選單項數量。呼叫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>

使用XML定義選單

在Android中推薦使用XML定義選單,而不是在Activity程式碼中建立選單。定義選單的XML檔案後,可以使用MenuInflater Service的inflate方法(通常位於onCreateOptionsMenu方法中)把選單填充到應用中。

使用選單資源是一種很好的做法,原因如下:

  • 更易於使用XML視覺化選單結構
  • 將選單內容與應用的行為程式碼分離
  • 允許您利用應用資源框架,為不同的平臺版本、螢幕尺寸和其他配置建立備用選單配置

每個選單定義都儲存在res/menu資料夾下單獨的一個XML檔案中,每個檔案只包含一個選單。檔名就是選單的資源ID。

語法:

<?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:numericShortcut="string"
          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>

選單元素詳解:

<menu>:定義Menu,即選單項的容器。xmlns:android為XML名稱空間,該屬性值必須為”http://schemas.android.com/apk/res/android“。<menu> 元素必須是該檔案的根節點,並且能夠包含一個或多個<item> 和<group> 元素。
<item>:建立 MenuItem,此元素表示選單中的一項,必須作為<menu> 或<roup>元素的子元素。可包含巢狀的 <menu> 元素,以便建立子選單。其屬性詳解:

  • android:id:ID資源型別。該屬性為該選單項的ID;
  • android:title:字串資源型別。選單項的標題;
  • android:titleCondensed:字串資源型別。一個濃縮(condensed)選單項標題。當上面的正常標題太長時顯示該標題;
  • android:icon:可繪製資源型別。用作選單項圖示的圖片;
  • android:onClick:指定方法名。該選單項被單擊之後將呼叫這裡指定的方法。該方法必須在相應的Activity中宣告為public並且將該MenuItem作為其唯一引數。該方法的執行優先於onOptionsItemSelected();
  • android:actionViewClass:類名,指定用作Action View的view類的全限定類名。如android.widget.SearchView可以將Search View設定為該Action View;
  • android:actionProviderClass:類名,指定替代活動項的ActionProvider類的全限定類名。如將該屬性設定為”android.widget.ShareActionProvider”來使用ShareActionProvider;
  • android:alphabeticShortcut:Char型別,指定字母快捷鍵;
  • android:numericShortcut:Integer型別,指定數字快捷鍵;
  • android:checkable:布林型,若為ture則表示該選單項是可勾選的;
  • android:checked:布林型,若為ture則表示該選單項預設是勾選的;
  • android:visible:布林型,若為ture則表示該選單項預設是可見的;
  • android:enabled:布林型,若為ture則表示該選單項預設是可用的;
  • android:orderInCategory:Integer型別,表示該選單項在選單組中的重要性順序。
  • android:menuCategory: 與Menu CATEGORY_*系列常量相關用於指定選單項優先順序的關鍵字:

    • container:是容器一部分的選單項;
    • system:系統提供的選單項;
    • secondary:使用者提供的次要的不常用選項;
    • alternative:當前顯示的資料上可選的選單項;
  • android:showAsAction:通過指定一下關鍵字來何時及如何在應用欄上顯示該選單項。選單項只有在Activity有應用欄時才能顯示成活動項:

    • ifRoom:如果應用欄有空間就把該選單項放到應用欄上,如果沒有足夠的空間放置所有ifRoom標記的選單項。具有最低的orderInCategory的選單項顯示為活動項,其餘的選單項顯示為溢位選單;
    • withText:也在活動項上包括標題文字(由android:title定義) 。可以和其他值一起設定,使用|號分隔多個值;
    • never:永遠不把該選單項新增到應用欄,而是把該選單項列入應用欄的溢位選單中;
    • always:一直把該選單項放入應用欄中,除非選單項不該顯示在活動條上。設定多個選單項一直顯示為活動項會導致它們與其他UI元件在應用欄上重疊;
    • collapseActionView|與該活動項相關的Action View(由Android:actionLayout和android:actionViewClass定義的)是可摺疊的。從API 14開始引入;

<group>:存放若干個<item>元素的可選選單組。可謂同一個選單組中的選單項指定相同的特性,如同時設定它們的可見性、可用性及可選性。必須把該元素放在<menu>元素中。屬性如下:

  • android:id:資源ID,指定該選單組的資源識別符號;
  • android:checkableBehavior:通過以下關鍵字來指定該選單組的選擇行為:

    • none:不可選擇
    • all:該選單組中的全部選單項都可勾選(使用checkboxes)
    • single:該選單組中的選單項只能選擇一個(使用radio buttons)
  • android:visible:布林型,設定該選單組中的全部選單項的可見性;

  • android:enabled:布林型,設定該選單組中的全部選單項的可用性;
  • android:orderInCategory:Integer型,指定所有選單項在分組中的預設順序;
  • android:menuCategory:與Menu CATEGORY_*系列常量相關用於指定選單組的優先順序的關鍵字:

    • container:是容器一部分的選單組;
    • system:系統提供的選單組;
    • secondary:使用者提供的次要的不常用選組;
    • alternative:當前顯示的資料上可選的選單組。

示例:
res/menu/example_menu.xml:

<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/item1"
          android:title="@string/item1"
          android:icon="@drawable/group_item1_icon"
          android:showAsAction="ifRoom|withText"/>
    <group android:id="@+id/group">
        <item android:id="@+id/group_item1"
              android:onClick="onGroupItemClick"
              android:title="@string/group_item1"
              android:icon="@drawable/group_item1_icon" />
        <item android:id="@+id/group_item2"
              android:onClick="onGroupItemClick"
              android:title="@string/group_item2"
              android:icon="@drawable/group_item2_icon" />
    </group>
    <item android:id="@+id/submenu"
          android:title="@string/submenu_title"
          android:showAsAction="ifRoom|withText" >
        <menu>
            <item android:id="@+id/submenu_item1"
                  android:title="@string/submenu_item1" />
        </menu>
    </item>
</menu>

在java程式碼中建立並相應選單單擊事件:

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

public void onGroupItemClick(MenuItem item) {
    // One of the group items (using the onClick attribute) was clicked
    // The item parameter passed here indicates which item it is
    // All other menu item clicks are handled by onOptionsItemSelected()
}