http://developer.android.youdaxue.com/guide/topics/ui/menus.html
菜單是許多應用類型中常見的用戶界面組件。要提供熟悉而一致的用戶體驗,您應使用 Menu API 呈現 Activity 中的用戶操作和其他選項。
從 Android 3.0(API 級別 11)開始,采用 Android 技術的設備不必再提供一個專用“菜單”按鈕。隨著這種改變,Android 應用需擺脫對包含 6 個項目的傳統菜單面板的依賴,取而代之的是要提供一個應用欄來呈現常見的用戶操作。
盡管某些菜單項的設計和用戶體驗已發生改變,但定義一系列操作和選項所使用的語義仍是以 Menu API 為基礎。本指南將介紹所有 Android 版本系統中三種基本菜單或操作呈現效果的創建方法:
選項菜單和應用欄
選項菜單是某個 Activity 的主菜單項, 供您放置對應用產生全局影響的操作,如“搜索”、“撰寫電子郵件”和“設置”。請參閱創建選項菜單部分。上下文菜單和上下文操作模式
上下文菜單是用戶長按某一元素時出現的浮動菜單。 它提供的操作將影響所選內容或上下文框架。上下文操作模式在屏幕頂部欄顯示影響所選內容的操作項目,并允許用戶選擇多項。
請參閱創建上下文菜單部分。彈出菜單
彈出菜單將以垂直列表形式顯示一系列項目,這些項目將錨定到調用該菜單的視圖中。 它特別適用于提供與特定內容相關的大量操作,或者為命令的另一部分提供選項。 彈出菜單中的操作不會直接影響對應的內容,而上下文操作則會影響。 相反,彈出菜單適用于與您 Activity 中的內容區域相關的擴展操作。請參閱創建彈出菜單部分。
使用 XML 定義菜單
對于所有菜單類型,Android 提供了標準的 XML 格式來定義菜單項。您應在 XML 菜單資源中定義菜單及其所有項,而不是在 Activity 的代碼中構建菜單。定義后,您可以在 Activity 或片段中擴充菜單資源(將其作為 Menu
對象加載)。
使用菜單資源是一種很好的做法,原因如下:
- 更易于使用 XML 可視化菜單結構
- 將菜單內容與應用的行為代碼分離
- 允許您利用應用資源框架,為不同的平臺版本、屏幕尺寸和其他配置創建備用菜單配置
以下是名為 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>
要在 Activity 中使用菜單,您需要使用 [MenuInflater.inflate()](http://developer.android.youdaxue.com/reference/android/view/MenuInflater.html#inflate(int, android.view.Menu)) 擴充菜單資源(將 XML 資源轉換為可編程對象)。在下文中,您將了解如何擴充每種類型的菜單。
創建選項菜單
在選項菜單中,您應當包括與當前 Activity 上下文相關的操作和其他選項,如“搜索”、“撰寫電子郵件”和“設置”。
選項菜單中的項目在屏幕上的顯示位置取決于您開發的應用所適用的 Android 版本:
- 如果您開發的應用適用于 Android 2.3.x(API 級別 10)或更低版本,則當用戶按“菜單”按鈕時,選項菜單的內容會出現在屏幕底部,如圖 1 所示。打開時,第一個可見部分是圖標菜單,其中包含多達 6 個菜單項。 如果菜單包括 6 個以上項目,則 Android 會將第六項和其余項目放入溢出菜單。用戶可以通過選擇“更多”打開該菜單。
- 如果您開發的應用適用于 Android 3.0(API 級別 11)及更高版本,則選項菜單中的項目將出現在應用欄中。 默認情況下,系統會將所有項目均放入操作溢出菜單中。用戶可以使用應用欄右側的操作溢出菜單圖標(或者,通過按設備“菜單”按鈕(如有))顯示操作溢出菜單。 要支持快速訪問重要操作,您可以將
android:showAsAction="ifRoom"
添加到對應的 <item>元素,從而將幾個項目提升到應用欄中(請參閱圖 2)。如需了解有關操作項目和其他應用欄行為的詳細信息,請參閱添加應用欄培訓課程。
您可以通過 Activity 子類或 Fragment 子類為選項菜單聲明項目。如果您的 Activity 和片段均為選項菜單聲明項目,則這些項目將合并到 UI 中。 系統將首先顯示 Activity 的項目,隨后按每個片段添加到 Activity 中的順序顯示各片段的項目。 如有必要,您可以使用 android:orderInCategory 屬性,對需要移動的每個 <item> 中的菜單項重新排序。
要為 Activity 指定選項菜單,請重寫 onCreateOptionsMenu()(片段會提供自己的 onCreateOptionsMenu() 回調)。通過此方法,您可以將菜單資源(使用 XML 定義)擴充到回調中提供的 Menu 中。 例如:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.game_menu, menu);
return true;
}
如果菜單按鈕與Fragment關聯,則確保在Fragment的 onCreate
方法中調用 setHasOptionsMenu(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 包括片段,則系統將依次為 Activity 和每個片段(按照每個片段的添加順序)調用 onOptionsItemSelected(),直到有一個返回結果為 true 或所有片段均調用完畢為止。
在運行時更改菜單項
系統調用 onCreateOptionsMenu() 后,將保留您填充的 Menu 實例。除非菜單由于某些原因而失效,否則不會再次調用 onCreateOptionsMenu()。但是,
您僅應使用 onCreateOptionsMenu() 來創建初始菜單狀態,而不應使用它在 Activity 生命周期中執行任何更改。
如需根據在 Activity 生命周期中發生的事件修改選項菜單,則可通過 onPrepareOptionsMenu() 方法執行此操作。此方法向您傳遞 Menu 對象(因為該對象目前存在),以便您能夠對其進行修改,如添加、移除或禁用項目。(此外,片段還提供 onPrepareOptionsMenu() 回調。)
在 Android 2.3.x 及更低版本中,每當用戶打開選項菜單時(按“菜單”按鈕),系統均會調用 onPrepareOptionsMenu()。
在 Android 3.0 及更高版本中,當菜單項顯示在應用欄中時,選項菜單被視為始終處于打開狀態。 發生事件時,如果您要執行菜單更新,則必須調用 invalidateOptionsMenu()
來請求系統調用 onPrepareOptionsMenu()
。
創建上下文菜單
上下文菜單提供了許多操作,這些操作影響 UI 中的特定項目或上下文框架。您可以為任何視圖提供上下文菜單,但這些菜單通常用于 ListView、GridView 或用戶可直接操作每個項目的其他視圖集合中的項目。
提供上下文操作的方法有兩種:
- 使用浮動上下文菜單。用戶長按(按住)一個聲明支持上下文菜單的視圖時,菜單顯示為菜單項的浮動列表(類似于對話框)。 用戶一次可對一個項目執行上下文操作。
- 使用上下文操作模式。此模式是 ActionMode 的系統實現,它將在屏幕頂部顯示上下文操作欄,其中包括影響所選項的操作項目。當此模式處于活動狀態時,用戶可以同時對多項執行操作(如果應用允許)。
注:上下文操作模式可用于 Android 3.0(API 級別 11)及更高版本,是顯示上下文操作(如果可用)的首選方法。如果應用支持低于 3.0 版本的系統,則應在這些設備上回退到浮動上下文菜單。
創建浮動上下文菜單
要提供浮動上下文菜單,請執行以下操作:
- 通過調用 registerForContextMenu(),注冊應與上下文菜單關聯的 View
并將其傳遞給 View。如果 Activity 使用 ListView
或 GridView 且您希望每個項目均提供相同的上下文菜單,請通過將 ListView
或 GridView傳遞給registerForContextMenu(),為上下文菜單注冊所有項目。 - 在 Activity 或 Fragment 中實現 [onCreateContextMenu()](http://developer.android.youdaxue.com/reference/android/view/View.OnCreateContextMenuListener.html#onCreateContextMenu(android.view.ContextMenu, android.view.View, android.view.ContextMenu.ContextMenuInfo)) 方法。當注冊后的視圖收到長按事件時,系統將調用您的 [onCreateContextMenu()](http://developer.android.youdaxue.com/reference/android/view/View.OnCreateContextMenuListener.html#onCreateContextMenu(android.view.ContextMenu, android.view.View, android.view.ContextMenu.ContextMenuInfo)) 方法。在此方法中,您通常可通過擴充菜單資源來定義菜單項。例如:
@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 有多個視圖,每個視圖均提供不同的上下文菜單,則可使用這些參數確定要擴充的上下文菜單。
- 實現 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 包括片段,則 Activity 將先收到此回調。 通過在未處理的情況下調用超類,系統會將事件逐一傳遞給每個片段中相應的回調方法(按照每個片段的添加順序),直到返回 true 或 false 為止。(Activity 和 android.app.Fragment 的默認實現返回 false,因此您始終應在未處理的情況下調用超類。)
使用上下文操作模式
上下文操作模式是 ActionMode 的一種系統實現,它將用戶交互的重點轉到執行上下文操作上。用戶通過選擇項目啟用此模式時,屏幕頂部將出現一個“上下文操作欄”,顯示用戶可對當前所選項執行的操作。 啟用此模式后,用戶可以選擇多個項目(若您允許)、取消選擇項目以及繼續在 Activity 內導航(在您允許的最大范圍內)。 當用戶取消選擇所有項目、按“返回”按鈕或選擇操作欄左側的“完成”操作時,該操作模式將會停用,且上下文操作欄將會消失。
注:上下文操作欄不一定與應用欄相關聯。 盡管表面上看來上下文操作欄取代了應用欄的位置,但事實上二者獨立運行。
對于提供上下文操作的視圖,當出現以下兩個事件(或之一)時,您通常應調用上下文操作模式:
- 用戶長按視圖。
- 用戶選中復選框或視圖內的類似 UI 組件。
應用如何調用上下文操作模式以及如何定義每個操作的行為,具體取決于您的設計。 設計基本上分為兩種:
下文介紹每種場景所需的設置。
為單個視圖啟用上下文操作模式
如果希望僅當用戶選擇特定視圖時才調用上下文操作模式,則應:
- 實現 ActionMode.Callback 接口。在其回調方法中,您既可以為上下文操作欄指定操作,又可以響應操作項目的點擊事件,還可以處理操作模式的其他生命周期事件。
- 當需要顯示操作欄時(例如,用戶長按視圖),請調用 startActionMode()。
- 例如:實現 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 或片段中的成員變量有何作用。
- 調用 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() 為視圖組設置該接口。在偵聽器的回調方法中,您既可以為上下文操作欄指定操作,也可以響應操作項目的點擊事件,還可以處理從 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()](http://developer.android.youdaxue.com/reference/android/view/ActionMode.Callback.html#onCreateActionMode(android.view.ActionMode, android.view.Menu)) 方法,并顯示包含指定操作的上下文操作欄。當上下文操作欄可見時,用戶可以選擇其他項目。
在某些情況下,如果上下文操作提供常用的操作項目,則您可能需要添加一個復選框或類似的 UI 元素來支持用戶選擇項目,這是因為他們可能沒有發現長按行為。用戶選中該復選框時,您可以通過使用 [setItemChecked()](http://developer.android.youdaxue.com/reference/android/widget/AbsListView.html#setItemChecked(int, boolean)) 將相應的列表項設置為選中狀態,以此調用上下文操作模式。
創建彈出菜單
PopupMenu 是錨定到 View 的模態菜單。如果空間足夠,它將顯示在定位視圖下方,否則顯示在其上方。它適用于:
- 為與特定內容確切相關的操作提供溢出樣式菜單(例如,Gmail 的電子郵件標頭,如圖所示)。
注:這與上下文菜單不同,后者通常用于影響所選內容的操作。 **對于影響所選內容的操作,請使用上下文操作模式或浮動上下文菜單。
- 提供命令語句的另一部分(例如,標記為“添加”且使用不同的“添加”選項生成彈出菜單的按鈕)。
- 提供類似于 Spinner 且不保留永久選擇的下拉菜單。
注:PopupMenu 在 API 級別 11 及更高版本中可用。
如果使用 XML 定義菜單,則顯示彈出菜單的方法如下:
- 實例化 PopupMenu 及其構造函數,該函數將提取當前應用的 Context 以及菜單應錨定到的 View。
- 使用 MenuInflater 將菜單資源擴充到 PopupMenu.getMenu() 返回的 Menu 對象中。
- 調用 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;
}
}