Android Menu用法全面講解


說明:本文只介紹Android3.0及以上的Menu知識點。

菜單的分類

菜單是Android應用中非常重要且常見的組成部分,主要可以分為三類:選項菜單、上下文菜單/上下文操作模式以及彈出菜單。它們的主要區別如下:

  • 選項菜單是一個應用的主菜單項,用於放置對應用產生全局影響的操作,如搜索/設置

  • 上下文菜單是用戶長按某一元素時出現的浮動菜單。它提供的操作將影響所選內容,主要應用於列表中的每一項元素(如長按列表項彈出刪除對話框)。上下文操作模式將在屏幕頂部欄(菜單欄)顯示影響所選內容的操作選項,並允許用戶選擇多項,一般用於對列表類型的數據進行批量操作。

  • 彈出菜單以垂直列表形式顯示一系列操作選項,一般由某一控件觸發,彈出菜單將顯示在對應控件的上方或下方。它適用於提供與特定內容相關的大量操作。

使用XML定義Menu

理論上而言,使用XML和Java代碼都可以創建Menu。但是在實際開發中,往往通過XML文件定義Menu,這樣做有以下幾個好處:

  • 使用XML可以獲得更清晰的菜單結構
  • 將菜單內容與應用的邏輯代碼分離
  • 可以使用應用資源框架,為不同的平台版本、屏幕尺寸創建最合適的菜單(如對drawable、string等系統資源的使用)

要定義Menu,我們首先需要在res文件夾下新建menu文件夾,它將用於存儲與Menu相關的所有XML文件。

我們可以使用<menu><item><group>三種XML元素定義Menu,下面簡單介紹一下它們:

  • <menu>是菜單項的容器。<menu>元素必須是該文件的根節點,並且能夠包含一個或多個<item><group>元素。
  • <item>是菜單項,用於定義MenuItem,可以嵌套<menu>元素,以便創建子菜單。
  • <group><item>元素的不可見容器(可選)。可以使用它對菜單項進行分組,使一組菜單項共享可用性和可見性等屬性。

其中,<item>是我們主要需要關注的元素,它的常見屬性如下:

  • android:id:菜單項(MenuItem)的唯一標識
  • android:icon:菜單項的圖標(可選)
  • android:title:菜單項的標題(必選)
  • android:showAsAction:指定菜單項的顯示方式。常用的有ifRoom、never、always、withText,多個屬性值之間可以使用|隔開。

選項菜單

普通選項菜單

要創建選項菜單,首先需要在XML文件中定義各個菜單項,具體代碼如下:

XML代碼:

<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/option_normal_1" android:icon="@mipmap/ic_vpn_key_white_24dp" android:title="普通菜單1" app:showAsAction="ifRoom"/> <item android:id="@+id/option_normal_2" android:icon="@mipmap/ic_email_white_24dp" android:title="普通菜單2" app:showAsAction="always"/> <item android:id="@+id/option_normal_3" android:icon="@mipmap/ic_vpn_key_white_24dp" android:title="普通菜單3" app:showAsAction="withText|always"/> <item android:id="@+id/option_normal_4" android:title="普通菜單4" app:showAsAction="never"/> </menu>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

可以看到,我們在XML文件中定義了四個普通的菜單項。同時,每一個<item>都有一個獨特的showAsAction屬性。

我們需要知道,菜單欄中的菜單項會分為兩個部分。一部分可以直接在菜單欄中看見,我們可以稱之為常駐菜單;另一部分會被集中收納到溢出菜單中(就是菜單欄右側的小點狀圖標)。一般情況下,常駐菜單項以圖標形式顯示(需要定義icon屬性),而溢出菜單項則以文字形式顯示(通過title屬性定義)。showAsAction的差異如下所示:

  • always:菜單項永遠不會被收納到溢出菜單中,因此在菜單項過多的情況下可能超出菜單欄的顯示范圍。
  • ifRoom:在空間足夠時,菜單項會顯示在菜單欄中,否則收納入溢出菜單中。
  • withText:無論菜單項是否定義了icon屬性,都只會顯示它的標題,而不會顯示圖標。使用這種方式的菜單項默認會被收納入溢出菜單中。
  • never:菜單項永遠只會出現在溢出菜單中。

現在我們已經在XML文件中將Menu定義完畢了,接下來還需要在Java代碼中進行加載,具體代碼如下:

Java代碼:

@Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater=getMenuInflater(); inflater.inflate(R.menu.option_menu_normal,menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()){ case R.id.option_normal_1: return true; case R.id.option_normal_2: return true; case R.id.option_normal_3: return true; case R.id.option_normal_4: return true; default: return super.onOptionsItemSelected(item); } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

可以看見,我們在Activity中重寫了onCreateOptionsMenu方法,在這個方法中完成加載Menu資源的操作,關鍵代碼如下:

//獲取MenuInflater MenuInflater inflater=getMenuInflater(); //加載Menu資源 inflater.inflate(R.menu.option_menu_normal,menu);
  • 1
  • 2
  • 3
  • 4

需要注意的是,這個方法必須返回true,否則Menu將不會顯示。

onOptionsItemSelected方法中,我們實現了菜單項的點擊監聽。可以看見,這里是通過MenuItemid進行區分的,對應着XML文件中<item>id屬性。每次處理完點擊事件后,記得要返回true,對系統而言這次點擊事情才算是真正結束了。此外,在default分支下,推薦調用父類的默認實現,即super.onOptionsItemSelected(item),避免在多個Activity使用公有父類的情況下菜單項點擊事件無法觸發(下文會詳細解釋)。

效果截圖: 

包含多級子菜單的選項菜單

我們在前面提到過,<item>是可以嵌套<menu>的,而<menu>又是<item>的容器。因此,我們可以在應用中實現具有層級結構的子菜單。下面給出一個實際的例子:

XML代碼:

<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/option_sub_file" android:title="文件" app:showAsAction="ifRoom"> <menu> <item android:id="@+id/file_new" android:title="新建"/> <item android:id="@+id/file_save" android:title="保存"/> <item android:id="@+id/file_more" android:title="更多"> <menu> <item android:id="@+id/file_more_1" android:title="更多1"/> <item android:id="@+id/file_more_2" android:title="更多2"/> <item android:id="@+id/file_more_more" android:title="更多更多"> <menu> <item android:id="@+id/file_more_more_1" android:title="更多更多1"/> <item android:id="@+id/file_more_more_2" android:title="更多更多2"/> </menu> </item> </menu> </item> </menu> </item> </menu>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

上面的代碼實現了一個三級子菜單結構。理論上來說,子菜單的層級是沒有限制的。但是在實際應用中,由於移動設備的顯示特點,建議菜單層級不要超過兩層,否則會給用戶的操作帶來諸多不便。

效果截圖: 
 

Activity+Fragment構建的選項菜單

在前面,我們都是在Activity中加載Menu資源,實際上在Fragment中同樣也可以做到這一點。如果Activity和Fragment都加載了Menu資源,那么這些菜單項將合並到一起。系統將首先顯示Activity加載的菜單項,隨后按每個Fragment添加到Activity中的順序顯示各Fragment的菜單項。如果有必要,可以使用<item>orderInCategory屬性,對菜單項重新排序。

實際上,在Fragment中加載Menu的方式和Activity幾乎一致,同樣需要重寫onCreateOptionsMenuonOptionsItemSelected方法。當然,Fragment中的onCreateOptionsMenu方法有所不同,如下所示:

@Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.option_menu_fragment_2,menu); }
  • 1
  • 2
  • 3
  • 4

還需要注意,要讓Fragment中的菜單項顯示出來,還需要在Fragment中調用setHasOptionsMenu(true)方法。傳入true作為參數表明Fragment需要加載菜單項。建議在Fragment的onCreate方法中調用這個方法,如下所示:

@Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setHasOptionsMenu(true); }
  • 1
  • 2
  • 3
  • 4
  • 5

當菜單項發生點擊事件時,如果Activity包括Fragment,則系統將依次為Activity和每個Fragment(按照每個Fragment的添加順序)調用onOptionsItemSelected方法,直到有一個返回結果為true或所有Fragment都調用完畢為止。因此,無論是Activity還是Fragment,onOptionsItemSelected方法中的switch語句塊中的default分支都不要直接返回true,而應該使用return super.onOptionsItemSelected(item),避免截斷了菜單項的點擊事件。

說明:詳細代碼可以參考下文提供的demo。

在運行時修改的選項菜單

系統調用onCreateOptionsMenu方法后,將保留創建的Menu實例。除非菜單由於某些原因而失效,否則不會再次調用onCreateOptionsMenu。因此,我們只應該使用onCreateOptionsMenu來創建初始菜單狀態,而不應使用它在Activity生命周期中對菜單執行任何更改。

如果需要根據在Activity生命周期中發生的某些事件修改選項菜單,則應該通過onPrepareOptionsMenu方法實現。這個方法的參數中有一個Menu對象(即舊的Menu對象),我們可以使用它對菜單執行修改,如添加、移除、啟用或禁用菜單項。(Fragment同樣提供onPrepareOptionsMenu方法,只是不需要提供返回值)

需要注意,在Android 3.0及更高版本中,當菜單項顯示在應用欄中時,選項菜單被視為始終處於打開狀態。發生事件時,如果要執行菜單更新,則必須調用 invalidateOptionsMenu來請求系統調用onPrepareOptionsMenu方法。

下面我們提供一個簡單的例子。在這個例子中:點擊下一步后,上一步會被啟用,下一步會被禁用;點擊上一步后,下一步會被啟用,上一步會被禁用。這是許多應用中常見的場景,代碼如下:

XML代碼:

<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/option_menu_previous" android:title="上一步" android:enabled="false" app:showAsAction="ifRoom"/> <item android:id="@+id/option_menu_next" android:title="下一步" android:enabled="true" app:showAsAction="ifRoom"/> </menu>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

我們在XML中定義了兩個菜單項,默認啟用下一步,禁用上一步

Java代碼:

private boolean isShowNext=true;//當前是否顯示[下一步] ...... @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.option_menu_change,menu); return true; } @Override public boolean onPrepareOptionsMenu(Menu menu) { if(isShowNext){//根據標識值判斷當前應該啟用哪個菜單項 menu.findItem(R.id.option_menu_next).setEnabled(true); menu.findItem(R.id.option_menu_previous).setEnabled(false); }else{ menu.findItem(R.id.option_menu_previous).setEnabled(true); menu.findItem(R.id.option_menu_next).setEnabled(false); } return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()){ case R.id.option_menu_next: isShowNext=false; invalidateOptionsMenu();//通知系統刷新Menu return true; case R.id.option_menu_previous: isShowNext=true; invalidateOptionsMenu();//通知系統刷新Menu return true; default: return super.onOptionsItemSelected(item); } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

在代碼中,我們使用isShowNext這個布爾值標識當前可用的菜單項。在onOptionsItemSelected方法中,每次菜單項被點擊后,我們會更改isShowNext,同時調用invalidateOptionsMenu通知系統刷新Menu。之后,onPrepareOptionsMenu會被調用。在這個方法中,我們根據isShowNext的值啟用、禁用菜單項。可以看到,這里使用了Menu的findItem方法,它可以根據<item>的id獲取對應的MenuItem對象,方法原型如下:

public MenuItem findItem(int id);
  • 1

此外,還可以使用Menu的add方法添加新的菜單項(有多個重載方法)。

效果截圖: 
 

使用公有父類構建選項菜單

如果應用包含多個Activity,且其中某些Activity具有相同的選項菜單,則可考慮創建一個僅實現onCreateOptionsMenu和 onOptionsItemSelected方法的Activity。然后,將這個Activity作為每個具有相同選項菜單的Activity的父類。通過這種方式,每個子類均會繼承父類的菜單行為。下面給出一個簡單的例子:

父類Activity中的XML代碼:

<menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/option_menu_parent" android:title="父類菜單項"/> </menu>
  • 1
  • 2
  • 3
  • 4

父類Activity中的Java代碼:

@Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.option_menu_parent,menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()){ case R.id.option_menu_parent: Toast.makeText(this,"父類菜單項",Toast.LENGTH_SHORT).show(); return true; default: return super.onOptionsItemSelected(item); } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

子類Activity中的XML代碼:

<menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/option_menu_child" android:title="子類菜單項"/> </menu>
  • 1
  • 2
  • 3
  • 4

子類Activity中的Java代碼:

@Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu);//調用這一句保證父類的菜單項可以正常加載 getMenuInflater().inflate(R.menu.option_menu_child,menu);//加載子類自己的菜單項 return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()){ case R.id.option_menu_child: Toast.makeText(this,"子類菜單項",Toast.LENGTH_SHORT).show(); return true; default: return super.onOptionsItemSelected(item); } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

可以看到,大部分代碼都和創建普通的選項菜單一致。需要注意,在子類Activity的onCreateOptionsMenu方法中,我們首先調用了super.onCreateOptionsMenu(menu),保證父類的菜單項可以正常加載。然后,才對子類自己的菜單項進行加載。最終的效果就是在子類Activity中,既有父類的菜單項,也有自己的菜單項。

需要注意,在子類的onOptionsItemSelected方法的default分支中,我們調用了父類的方法super.onOptionsItemSelected(item)。這是為了保證父類菜單項的點擊行為可以被正確執行。當然,如果我們想要改變父類菜單項的行為,也可以在switch語句塊中添加case進行重寫。

效果截圖: 

上下文菜單及上下文操作模式

上下文菜單

通常上下文菜單是以浮動菜單的形式呈現的,用戶長按(按住)一個支持上下文菜單的View時,菜單將以浮動列表的形式出現(類似於對話框)。 通常用戶一次可對一個項目執行上下文操作(比如一個單獨的控件或列表中的一項)。

要提供浮動上下文菜單,可以參照以下步驟:

  1. 在Activity或Fragment中調用registerForContextMenu(View v)方法,注冊需要和上下文菜單關聯的View。如果將ListView或GridView作為參數傳入,那么每個列表項將會有相同的浮動上下文菜單。
  2. 在Activity或Fragment中重寫onCreateContextMenu方法,加載Menu資源。
  3. 在Activity或Fragment中重寫onContextItemSelected方法,實現菜單項的點擊邏輯。

下面,我們演示如何為ListView設置浮動上下文菜單:

XML代碼:

<menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/context_option_add" android:title="添加"/> <item android:id="@+id/context_option_delete" android:title="刪除"/> <item android:id="@+id/context_option_save" android:title="保存"/> </menu>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

Java代碼:

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_context_menu); //初始化ListView ListView listView= (ListView) findViewById(R.id.list_context_menu); ArrayAdapter<String> adapter=new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,createDataList()); listView.setAdapter(adapter); //為ListView注冊上下文浮動菜單 registerForContextMenu(listView); } //生成測試數據List private List<String> createDataList(){ List<String> list=new ArrayList<>(); for(int i=0;i<10;i++){ list.add("測試條目"+i); } return list; } @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); MenuInflater inflater=getMenuInflater(); inflater.inflate(R.menu.context_menu,menu); } @Override public boolean onContextItemSelected(MenuItem item) { switch (item.getItemId()){ case R.id.context_option_add: Toast.makeText(this,"添加",Toast.LENGTH_SHORT).show(); return true; case R.id.context_option_save: Toast.makeText(this,"保存",Toast.LENGTH_SHORT).show(); return true; case R.id.context_option_delete: Toast.makeText(this,"刪除",Toast.LENGTH_SHORT).show(); return true; default: return super.onContextItemSelected(item); } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

onCreateContextMenu方法中,方法參數包括用戶所選的View,以及一個提供有關所選項目的附加信息的ContextMenu.ContextMenuInfo對象。如果需要為多個View設置不同的上下文菜單,則可使用這些參數確定要加載的上下文菜單資源。

onContextItemSelected方法中,成功處理菜單項的監聽事件后,系統將返回true。需要注意在default分支中,應該調用super.onContextItemSelected(item)。如果Activity包括Fragment,則系統將依次為Activity和每個Fragment(按照每個Fragment的添加順序)調用onContextItemSelected方法,直到有一個返回結果為true或所有Fragment都調用完畢為止。

效果截圖: 

上下文操作模式

上下文操作模式是ActionMode的系統實現,它將在屏幕頂部(菜單欄區域)顯示上下文操作欄,其中包括影響所選項目的多種菜單項(通過加載Menu資源)。當啟動這個模式時,用戶可以同時對多個項目執行操作(批處理)。

當用戶取消選擇所有項目、按“返回”按鈕或選擇操作欄左側的“完成”操作時,該操作模式將會結束,同時上下文操作欄會消失。

上下文操作模式的使用很靈活,既可以為單個View配置,也可以為ListView或GridView配置(允許用戶選擇多個項目並針對所有項目執行相應操作)。下面我們給出兩個例子來說明上下文操作模式的使用。

1.為ListView設置上下文操作模式

簡單來說,為ListView設置上下文操作模式可以分為兩步:

  1. 使用CHOICE_MODE_MULTIPLE_MODAL參數調用ListView的setChoiceMode方法。
  2. 實現AbsListView.MultiChoiceModeListener接口,並調用ListView的setMultiChoiceModeListener方法為ListView設置該接口。在這個接口的回調方法中,可以為上下文操作欄加載Menu資源,也可以響應操作項目的點擊事件,還可以處理其他需要的操作。

下面給出相應的關鍵代碼:

XML代碼:

<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/context_mode_email" android:icon="@mipmap/ic_email_white_24dp" android:title="email" app:showAsAction="ifRoom"/> <item android:id="@+id/context_mode_key" android:icon="@mipmap/ic_vpn_key_white_24dp" android:title="key" app:showAsAction="ifRoom"/> </menu>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

Java代碼:

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_context_mode); //初始化ListView final ListView listView= (ListView) findViewById(R.id.list_context_menu); ArrayAdapter<String> adapter=new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,createDataList()); listView.setAdapter(adapter); //為ListView配置上下文操作模式 listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); listView.setMultiChoiceModeListener(new AbsListView.MultiChoiceModeListener() { @Override public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) { //當列表中的項目選中或取消勾選時,這個方法會被觸發 //可以在這個方法中做一些更新操作,比如更改上下文操作欄的標題 //這里顯示已選中的項目數 mode.setTitle("已選中:"+listView.getCheckedItemCount()+"項"); } @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { MenuInflater inflater=mode.getMenuInflater(); inflater.inflate(R.menu.context_mode_menu,menu); return true; } @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { switch (item.getItemId()){ case R.id.context_mode_email: Toast.makeText(ContextModeActivity.this,"email",Toast.LENGTH_SHORT).show(); mode.finish();//關閉上下文操作欄 return true; case R.id.context_mode_key: Toast.makeText(ContextModeActivity.this,"key",Toast.LENGTH_SHORT).show(); mode.finish(); return true; default: return false; } } @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { //可以對上下文操作欄做一些更新操作(會被ActionMode的invalidate方法觸發) return false; } @Override public void onDestroyActionMode(ActionMode mode) { //在上下文操作欄被移除時會觸發,可以對Activity做一些必要的更新 //默認情況下,此時所有的選中項將會被取消選中 } }); } //生成測試數據List private List<String> createDataList(){ List<String> list=new ArrayList<>(); for(int i=0;i<10;i++){ list.add("測試條目"+i); } return list; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63

AbsListView.MultiChoiceModeListener接口中,最重要的就是onCreateActionModeonActionItemClicked兩個方法。前者用於加載上下文操作模式的Menu資源,后者則實現菜單項的點擊邏輯。需要注意,在onActionItemClicked中處理完相應的邏輯后,應該調用mode.finish,以便關閉上下文操作欄。

效果截圖: 

2.為單個View設置上下文操作模式

為單個View設置上下文操作模式同樣可以分為兩步:

  1. 實現ActionMode.Callback接口。在這個接口的回調方法中,可以為上下文操作欄加載Menu資源,也可以響應操作項目的點擊事件,還可以處理其他需要的操作。
  2. 當需要顯示操作欄時(例如,用戶長按視圖),調用Activity的startActionMode方法,並傳入前面創建的Callback對象作為參數。

下面給出相應的關鍵代碼:

private ActionMode actionMode;//在全局范圍保存上下文操作模式實例 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_single_context_mode); //實現ActionMode.CallBack接口 final ActionMode.Callback callback=new ActionMode.Callback() { @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { MenuInflater inflater=mode.getMenuInflater(); inflater.inflate(R.menu.context_mode_menu,menu); return true; } @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { switch (item.getItemId()){ case R.id.context_mode_email: Toast.makeText(SingleContextModeActivity.this,"email",Toast.LENGTH_SHORT).show(); mode.finish();//關閉上下文操作欄 return true; case R.id.context_mode_key: Toast.makeText(SingleContextModeActivity.this,"key",Toast.LENGTH_SHORT).show(); mode.finish(); return true; default: return false; } } @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { return false; } @Override public void onDestroyActionMode(ActionMode mode) { actionMode=null;//取消保存的ActionMode實例,避免影響下一次ActionMode的創建 } }; //為按鈕配置上下文操作模式 findViewById(R.id.context_mode_view).setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { if(actionMode!=null){ return false; } actionMode=startActionMode(callback); v.setSelected(true);//設置View的狀態為選中 return true; } }); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

上面的大部分代碼都和為ListView設置上下文操作模式一致。只是在onDestroyActionMode方法中,執行了actionMode=null,這是為了避免影響下一次ActionMode的創建。此外,我們為目標View設置了OnLongClickListener,在回調方法中為全局范圍的ActionMode賦值,並調用setSelected(true)方法設置View的狀態為選中。

需要說明的是,ListView中的項目在選中后呈現的狀態(一般會使用深色強調選中項),需要在Adapter中單獨配置。在上面的例子中並沒有實現這一步,因此選中多項后ListView的外觀並不會發生變化。

效果截圖: 

彈出菜單

PopupMenu是依賴View存在的模態菜單。如果空間足夠,它將顯示在相應View的下方,否則顯示在其上方。可以將彈出菜單的使用拆分為以下四個步驟:

  1. 實例化PopupMenu,它的構造方法需要兩個參數,分別為Context以及PopupMenu依賴的View對象。
  2. 使用MenuInflater將Menu資源加載到PopupMenu.getMenu()返回的Menu對象中。
  3. 調用setOnMenuItemClickListener方法為PopupMenu設置點擊監聽器。
  4. 調用PopupMenu.show()將彈出菜單顯示出來。

下面給出一個簡單的例子:

XML代碼:

<menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/popup_add" android:title="添加"/> <item android:id="@+id/popup_delete" android:title="刪除"/> <item android:id="@+id/popup_more" android:title="更多"/> </menu>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

Java代碼:

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_popup_menu); findViewById(R.id.popup_menu_view).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { PopupMenu popupMenu=new PopupMenu(PopupMenuActivity.this,view);//1.實例化PopupMenu getMenuInflater().inflate(R.menu.popup_menu,popupMenu.getMenu());//2.加載Menu資源 //3.為彈出菜單設置點擊監聽 popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { switch (item.getItemId()){ case R.id.popup_add: Toast.makeText(PopupMenuActivity.this,"添加",Toast.LENGTH_SHORT).show(); return true; case R.id.popup_delete: Toast.makeText(PopupMenuActivity.this,"刪除",Toast.LENGTH_SHORT).show(); return true; case R.id.popup_more: Toast.makeText(PopupMenuActivity.this,"更多",Toast.LENGTH_SHORT).show(); return true; default: return false; } } }); popupMenu.show();//4.顯示彈出菜單 } }); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

當用戶選擇菜單項或觸摸菜單以外的區域時,系統就會清除彈出菜單,可以使用PopupMenu.OnDismissListener監聽這一事件。

效果截圖: 

菜單組

我們在前面曾經提到過<group>這種元素,使用<group>可以對菜單項進行分組。對於同一個<group>中的<item>,可以通過menu執行以下操作:

  • 使用setGroupVisible顯示或隱藏組內的所有項目
  • 使用setGroupEnabled啟用或禁用組內的所有項目
  • 使用setGroupCheckable指定組內的所有項目是否可選中

這三個方法的原型如下:

public void setGroupVisible(int group, boolean visible); public void setGroupEnabled(int group, boolean enabled); public void setGroupCheckable(int group, boolean checkable, boolean exclusive);
  • 1
  • 2
  • 3

參數中的group指的是<group>元素的id屬性。此外,setGroupCheckable方法中的exclusive用於設置菜單項的選擇模式。如果exclusive為true,代表菜單項為單選模式,否則為多選模式。

需要注意,<group>只是一種邏輯上的分組,並不會影響<item>的外觀和級別。此外,系統也絕不會分離已分組的項目。例如,如果為同一組內的每個<item>聲明android:showAsAction="ifRoom",則它們會同時顯示在操作欄或操作溢出菜單中。

下面是一個簡單的例子:

<menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/group_menu_normal" android:title="普通項"/> <item android:id="@+id/group_menu_normal" android:title="普通項"/> <group android:id="@+id/group_menu_1" android:checkableBehavior="single"> <item android:id="@+id/group_menu_item_1" android:title="組內項1"/> <item android:id="@+id/group_menu_item_2" android:title="組內項2"/> </group> </menu>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

可選中的菜單項

如果為<group>指定checkableBehavior屬性,則可以為組內項目實現單選或多選的選擇模式。checkableBehavior有三種可選值:

  • single:組中只有一個項目可以選中(單選按鈕)
  • all:所有項目均可選中(復選框)
  • none:所有項目均無法選中

下面給出一個簡單的例子:

XML代碼:

<menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/group_menu_normal" android:title="普通項"/> <group android:id="@+id/group_menu_1" android:checkableBehavior="single"> <item android:id="@+id/group_menu_item_1" android:title="單選組內項1"/> <item android:id="@+id/group_menu_item_2" android:title="單選組內項2"/> </group> <group android:id="@+id/group_menu_2" android:checkableBehavior="all"> <item android:id="@+id/group_menu_item_3" android:title="多選組內項1" /> <item android:id="@+id/group_menu_item_4" android:title="多選組內項2" /> </group> </menu>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

Java代碼:

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_menu_group); //為按鈕注冊上下文菜單 Button button= (Button) findViewById(R.id.group_menu_view); registerForContextMenu(button); } @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); getMenuInflater().inflate(R.menu.group_menu,menu); } @Override public boolean onContextItemSelected(MenuItem item) { switch (item.getItemId()){ case R.id.group_menu_normal: case R.id.group_menu_item_1: case R.id.group_menu_item_2: case R.id.group_menu_item_3: case R.id.group_menu_item_4: if(item.isChecked()){//更改菜單項的選中狀態 item.setChecked(false); }else{ item.setChecked(true); } Toast.makeText(this,item.getTitle(),Toast.LENGTH_SHORT).show(); return true; default: return super.onContextItemSelected(item); } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

效果截圖: 

項目demo

下載地址:傳送門

demo首頁截圖: 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM