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方法。

小提示:如果調用invalidateOptionsMenu方法沒能成功使菜單刷新,也可以嘗試使用supportInvalidateOptionsMenu方法。

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

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具有相同的選項菜單,則可考慮創建一個僅實現onCreateOptionsMenuonOptionsItemSelected方法的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首頁截圖:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章