Android的Menu菜單夭苗,這一篇就夠了

一直想抽時(shí)間把些較為基礎(chǔ)的控件統(tǒng)一系統(tǒng)化抽取出來形成Demo豺瘤,方便記錄以及解答,以下是項(xiàng)目的效果听诸,如果有欠缺的坐求,歡迎小伙伴評論區(qū)留言,項(xiàng)目的GitHub地址:AndroidMenuDemo

效果

Menu的分類

菜單是Android應(yīng)用中非常重要且常見的組成部分晌梨,主要分為三類:選項(xiàng)菜單桥嗤、上下文菜單彈出菜單仔蝌。

使用XML定義菜單

對于所有菜單類型泛领,Android提供了標(biāo)準(zhǔn)的XML格式來定義菜單項(xiàng)。定義菜單項(xiàng)方法可以在XML菜單資源中定義菜單及其所有項(xiàng)敛惊,也可通過代碼方式進(jìn)行構(gòu)建渊鞋,推薦前者。定義后瞧挤,可以在Activity或Fragment中擴(kuò)充菜單資源(將其作為Menu對象加載)锡宋。

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

  • 更易于使用XML可視化菜單結(jié)構(gòu)
  • 將菜單內(nèi)容與應(yīng)用的行為代碼分離
  • 允許利用應(yīng)用資源框架特恬,為不同的平臺(tái)版本执俩、屏幕尺寸和其他配置創(chuàng)建備用菜單配置。

要定義菜單癌刽,需在項(xiàng)目res/menu/目錄內(nèi)創(chuàng)建一個(gè)XML文件役首,并使用以下元素構(gòu)建菜單:

<menu>標(biāo)簽

定義Menu,即菜單項(xiàng)的容器尝丐。<menu>元素必須是該文件的根節(jié)點(diǎn),并且能夠包含一個(gè)或多個(gè)<item><group>元素衡奥。

<item>標(biāo)簽

<item>是菜單項(xiàng)爹袁,用于創(chuàng)建MenuItem,可能包含嵌套的<menu>元素矮固,以便創(chuàng)建子菜單呢簸。常見屬性如下:

  • android:id:菜單項(xiàng)(MenuItem)的唯一標(biāo)識。
  • android:icon:菜單項(xiàng)的圖標(biāo)(可選)乏屯,在溢出菜單中需要顯示圖標(biāo)以及標(biāo)題根时,需要額外代碼中配置。
  • android:title:菜單項(xiàng)的標(biāo)題(必選)
  • android:titleCondensed:菜單項(xiàng)的短標(biāo)題(可選)辰晕,當(dāng)菜單項(xiàng)標(biāo)題太長時(shí)會(huì)顯示該屬性值
  • android:onClick:方法名稱蛤迎。單擊此菜單項(xiàng)時(shí)要調(diào)用的方法。該方法必須在Activity中聲明為public,并將menuItem作為唯一參數(shù)含友,該參數(shù)指示單擊的項(xiàng)替裆。此方法優(yōu)先于OnOptionsItemSelected() 的標(biāo)準(zhǔn)回調(diào)。
public void onGroupItemClick(MenuItem item) {}

警告:如果混淆代碼時(shí)窘问,請確保在混淆規(guī)則中對此屬性方法進(jìn)行排除辆童,因?yàn)榭赡軙?huì)破壞其功能。

  • android:showAsAction:指定菜單項(xiàng)的顯示方式,多個(gè)屬性值之間可以使用 | 隔開惠赫,參數(shù)值有:
有效值 描述
ifRoom 在空間足夠時(shí)把鉴,菜單項(xiàng)會(huì)顯示在菜單欄中,否則收納入溢出菜單中儿咱。
always 菜單項(xiàng)永遠(yuǎn)不會(huì)被收納到溢出菜單中庭砍,因此在菜單項(xiàng)過多的情況下可能超出菜單欄的顯示范圍。
never 菜單項(xiàng)永遠(yuǎn)只會(huì)出現(xiàn)在溢出菜單中混埠。
withText 無論菜單項(xiàng)是否定義了icon屬性怠缸,都只會(huì)顯示它的標(biāo)題,而不會(huì)顯示圖標(biāo)钳宪。使用這種方式的菜單項(xiàng)默認(rèn)會(huì)被收納入溢出菜單中揭北。
collapseActionView 此選項(xiàng)是在Api14引入的屬性,搭配android:actionLayout或者android:actionViewClass使用,可起到折疊視圖的效果
  • android:actionLayout:布局資源吏颖,動(dòng)作視圖使用的布局文件搔体。
  • android:actionViewClass:類名,所使用的動(dòng)作視圖的全類名名稱侦高。例如當(dāng)你使用SearchView只需要引入android.widget.SearchView嫉柴。在Api 11引入的。
    警告:如果項(xiàng)目混淆記得添加對應(yīng)的忽略文件奉呛。
  • android:actionProviderClass:操作提供器類名计螺,例如使用ShareActionProvider需要引入"android.widget.ShareActionProvider",當(dāng)然也可以自行自定義ActionProvider的子類,此選項(xiàng)在Api14引入瞧壮。
    警告:如果項(xiàng)目混淆記得添加對應(yīng)的忽略文件登馒。
  • android:numericShortcut:數(shù)字快捷鍵。
  • android:alphabeticShortcut:字母快捷鍵咆槽。
  • android:alphabeticModifiers:字母快捷鍵的修飾符陈轿,默認(rèn)值為Control,有效值:
描述
META 對應(yīng)Meta鍵
CTRL 對應(yīng)Control鍵
ALT 對應(yīng)Alt鍵
SHIFT 對應(yīng)Shift鍵
SYM 對應(yīng)Sym鍵
FUNTION 對應(yīng)Function鍵

注意: 可以在屬性中指定多個(gè)關(guān)鍵字秦忿。例如麦射,android:alphabeticModifiers="CTRL|SHIFT",表示要觸發(fā)相應(yīng)的菜單項(xiàng)灯谣,用戶需要同時(shí)按下兩個(gè)Control和Shift鍵以及快捷鍵潜秋。

  • android:numericModifiers:數(shù)字快捷鍵的修飾符,用法同上胎许。
  • android:checkable:是否可選中
  • android:checked:是否選中
  • android:visible:是否可見
  • android:enabled:是否啟用
  • android:menuCategory:定義組優(yōu)先級峻呛,常見值如下:
描述
container 對于屬于容器的項(xiàng)目
system 對于系統(tǒng)提供的項(xiàng)目。
secondary 對于用戶提供的輔助(不經(jīng)常使用)選項(xiàng)的項(xiàng)目辜窑。
alternative 對于對當(dāng)前顯示的數(shù)據(jù)執(zhí)行備用操作的項(xiàng)目钩述。
  • orderInCategory:組內(nèi)的重要性順序

<group>標(biāo)簽

<group><item>元素的不可見容器(可選)∧滤椋可以使用它對菜單項(xiàng)進(jìn)行分組牙勘,使一組菜單項(xiàng)共享可用性和可見性等屬性。常見屬性不過多見解所禀,可參考<item>標(biāo)簽谜悟。

  • android:checkableBehavior:為組內(nèi)項(xiàng)目實(shí)現(xiàn)單選或多選的選擇模式。有三種可選值北秽。
描述
none 所有項(xiàng)目均無法選中葡幸,默認(rèn)值
single 組中只有一個(gè)項(xiàng)目可以選中(單選按鈕)
all 所有項(xiàng)目均可選中(復(fù)選框)

XML方面需要注意點(diǎn):當(dāng)使用appcompat library時(shí),菜單資源應(yīng)引用app:namespace方式(showAsAction贺氓、actionViewClass蔚叨、actionProviderClass),而不是android:namespace方式,對應(yīng)引入的資源也需要調(diào)用兼容類辙培;而相應(yīng)如果不使用appcompat library蔑水,即需要使用android:namespace方式調(diào)用,這塊涉及到Android Menu兼容配置扬蕊。

選項(xiàng)菜單

選項(xiàng)菜單(OptionMenu):是應(yīng)用的主菜單項(xiàng)搀别,用于放置對應(yīng)用起全局影響的操作,如搜索/設(shè)置等操作按鈕尾抑。

選項(xiàng)菜單一般需要使用到以下幾個(gè)方法:

  • public boolean onCreateOptionsMenu(Menu menu): 初始化Activity選項(xiàng)菜單方法歇父,將要設(shè)置的菜單關(guān)聯(lián)到menu中蒂培。在Fragment中調(diào)用public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)

  • public boolean onOptionsItemSelected(MenuItem item): 選項(xiàng)菜單點(diǎn)擊事件的處理方法榜苫。

  • public void onOptionsMenuClosed(Menu menu): 當(dāng)選項(xiàng)菜單關(guān)閉時(shí)(用戶使用后退/菜單按鈕取消菜單)护戳,都會(huì)調(diào)用此方法。

  • public boolean onPrepareOptionsMenu(Menu menu):選項(xiàng)菜單顯示前調(diào)用的方法垂睬,一般在此方法調(diào)整菜單媳荒,在高版本顯示icon以及一些邏輯處理。

  • public boolean onMenuOpened(int featureId, Menu menu): 當(dāng)用戶打開溢出菜單選項(xiàng)時(shí)調(diào)用驹饺,當(dāng)菜單從一種類型更改為另一種類型(例如钳枕,從圖標(biāo)菜單更改為拓展菜單),也調(diào)用此方法赏壹。一般用此方法監(jiān)聽拓展菜單是否被打開鱼炒,用于重置一些菜單操作。此方法僅Activity存在卡儒√锶幔‘

    圖1

    圖2

XML實(shí)現(xiàn)方式

采用XML是實(shí)現(xiàn)菜單主要方式,官方也推薦此種方案骨望,具體如下:

  • 實(shí)現(xiàn)option_menu.xml文件
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item android:id="@+id/menu_search"
        android:orderInCategory="1"
        android:title="搜索菜單"
        android:icon="@android:drawable/ic_menu_search"
        app:showAsAction="always|collapseActionView"
        app:actionViewClass="android.support.v7.widget.SearchView"/>
    <item android:id="@+id/menu_share"
        android:title="分享菜單"
        android:icon="@android:drawable/ic_menu_share"
        android:orderInCategory="1"
        app:showAsAction="never"
        app:actionProviderClass="android.support.v7.widget.ShareActionProvider"/>
    <item android:id="@+id/menu_collect"
        android:orderInCategory="1"
        android:title="收藏菜單"
        android:icon="@android:drawable/btn_star_big_on"
        app:actionLayout="@layout/layout_collect"
        app:showAsAction="never|collapseActionView"/>
    <item android:id="@+id/menu_previous"
        android:title="這是上一步的菜單展示效果"
        android:titleCondensed="上一步"
        android:orderInCategory="1"
        app:showAsAction="always"
        android:onClick="onPreviousMenu"/>
    <item android:id="@+id/menu_next"
        android:title="這是下一步的菜單展示效果"
        android:titleCondensed="下一步"
        android:orderInCategory="1"
        app:showAsAction="always"
        android:onClick="onNextMenu"/>
    <item android:id="@+id/menu_single_check"
        android:title="單選按鈕"
        android:orderInCategory="1"
        app:showAsAction="withText">
        <menu>
            <group android:id="@+id/group_single"
                android:checkableBehavior="single"
                android:menuCategory="system">
                <item android:id="@+id/single_menu_01"
                    android:title="單選按鈕01"
                    android:checked="true"/>
                <item android:id="@+id/single_menu_02"
                    android:title="單選按鈕02"/>
                <item android:id="@+id/single_menu_03"
                    android:title="單選按鈕03"/>
            </group>
        </menu>
    </item>
    <item android:id="@+id/menu_all_check"
        android:title="多選按鈕"
        android:orderInCategory="1"
        app:showAsAction="withText">
        <menu>
            <group android:id="@+id/group_all"
                android:checkableBehavior="all"
                android:menuCategory="system"
                android:enabled="true">
                <item
                    android:id="@+id/all_menu_01"
                    android:title="多選按鈕01"
                    android:checked="true"/>
                <item
                    android:id="@+id/all_menu_02"
                    android:title="多選按鈕02"/>
                <item
                    android:id="@+id/all_menu_03"
                    android:title="多選按鈕03"/>
            </group>
        </menu>
    </item>
</menu>
  • 在Activity或Fragment的onCreateOptionsMenu關(guān)聯(lián)Option_menu.xml文件硬爆。

Activity中:

//創(chuàng)建選項(xiàng)菜單
getMenuInflater().inflate(R.menu.option_menu,menu);

Fragment中:

//創(chuàng)建選項(xiàng)菜單
inflater.inflate(R.menu.option_menu,menu);
  • 要讓Fragment中的菜單項(xiàng)顯示出來,還需要在Fragment中調(diào)用setHasOptionsMenu(true)方法擎鸠。傳入true作為參數(shù)表明Fragment需要加載菜單項(xiàng)缀磕。建議在Fragment的onCreate方法中調(diào)用這個(gè)方法
    /**
     * 要讓Fragment中的菜單項(xiàng)顯示出來,還需要在Fragment中調(diào)用setHasOptionsMenu(true)方法劣光。
     * 傳入true作為參數(shù)表明Fragment需要加載菜單項(xiàng)袜蚕。
     * 建議在Fragment的onCreate方法中調(diào)用這個(gè)方法
     * @param savedInstanceState
     */
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setHasOptionsMenu(true);
    }

代碼實(shí)現(xiàn)方式

代碼方式也是在onCreateOptionsMenu掛載添加menu子項(xiàng),主要核心代碼

//添加普通菜單
public MenuItem add(int groupId, int itemId, int order, CharSequence title);
//添加子菜單
SubMenu addSubMenu(final int groupId, final int itemId, int order, final CharSequence title);

參數(shù)說明:

  • groupId為組id绢涡,一般在主菜單中都是相同的組id牲剃,如果有subMenu的組id可以設(shè)置不同;
  • itemId為菜單項(xiàng)的唯一標(biāo)識雄可,參考xml布局方式中的id,一般用于菜單點(diǎn)擊事件的區(qū)分凿傅;
  • order為序號,主要為組內(nèi)排列順序数苫,相當(dāng)于xml中的orderInCategory屬性聪舒。
  • title菜單標(biāo)題。
//通知系統(tǒng)刷新Menu
invalidateOptionsMenu();

上下文菜單

上下文菜單

上下文菜單

上下文菜單: 是用戶長按某一元素出現(xiàn)的浮動(dòng)菜單虐急。它提供的操作將影響所選內(nèi)容箱残,主要應(yīng)用于列表中的每一項(xiàng)元素(如長按表項(xiàng)彈出刪除對話框)。
上下文菜單在view實(shí)現(xiàn)方式(這里只貼出核心代碼止吁,具體可以在底部下載gitHub下載完整案例):

  • ListView或RecycleView上下文選擇菜單(有兩種方法實(shí)現(xiàn)),單個(gè)View相同
    1. 方法一:通過registerForContextMenu方法實(shí)現(xiàn)注冊
    //在onCreate中對列表進(jìn)行注冊
    registerForContextMenu(recyclerView);
    //在onDestroy中對列表進(jìn)行解綁
    unregisterForContextMenu(recyclerView);
    //重寫方法onCreateContextMenu
    @Override
    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
        if(menuInfo instanceof RecyclerViewWithContextMenu.RecyclerViewContextMenuInfo){
            RecyclerViewWithContextMenu.RecyclerViewContextMenuInfo contextMenuInfo = (RecyclerViewWithContextMenu.RecyclerViewContextMenuInfo) menuInfo;
            if(contextMenuInfo != null && contextMenuInfo.getPostion() >= 0){
                menu.setHeaderTitle("點(diǎn)擊:"+mAdapter.getItem(contextMenuInfo.getPostion()));
                getMenuInflater().inflate(R.menu.context_menu,menu);
            }
        }
        super.onCreateContextMenu(menu, v, menuInfo);
    }
    
    1. 方法二:列表對象對onCreateContextMenu實(shí)現(xiàn)
    recyclerView.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() {
            @Override
            public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
                if(menuInfo instanceof RecyclerViewWithContextMenu.RecyclerViewContextMenuInfo){
                    RecyclerViewWithContextMenu.RecyclerViewContextMenuInfo contextMenuInfo = (RecyclerViewWithContextMenu.RecyclerViewContextMenuInfo) menuInfo;
                    if(contextMenuInfo != null && contextMenuInfo.getPostion() >= 0){
                        menu.setHeaderTitle("點(diǎn)擊:"+mAdapter.getItem(contextMenuInfo.getPostion()));
                        getMenuInflater().inflate(R.menu.context_menu,menu);
                    }
                }
            }
        });
    
    以上是列表對于上下文菜單兩種實(shí)現(xiàn)方式被辑,監(jiān)聽可用過onContextItemSelected方法進(jìn)行監(jiān)聽:
    @Override
     public boolean onContextItemSelected(MenuItem item) {
         if(item.getMenuInfo() instanceof RecyclerViewWithContextMenu.RecyclerViewContextMenuInfo){
             RecyclerViewWithContextMenu.RecyclerViewContextMenuInfo contextMenuInfo = (RecyclerViewWithContextMenu.RecyclerViewContextMenuInfo) item.getMenuInfo();
             if(contextMenuInfo != null && contextMenuInfo.getPostion() >= 0){
                 switch (item.getItemId()){
                     case R.id.context_menu_add:
                         Toast.makeText(this,mAdapter.getItem(contextMenuInfo.getPostion())+":添加菜單被點(diǎn)擊" , Toast.LENGTH_SHORT).show();
                         return true;
                     case R.id.context_menu_del:
                         Toast.makeText(this,mAdapter.getItem(contextMenuInfo.getPostion())+":刪除菜單被點(diǎn)擊" , Toast.LENGTH_SHORT).show();
                         return true;
                     case R.id.context_menu_save:
                         Toast.makeText(this,mAdapter.getItem(contextMenuInfo.getPostion())+":保存菜單被點(diǎn)擊" , Toast.LENGTH_SHORT).show();
                         return true;
                 }
             }
         }
         return super.onContextItemSelected(item);
     }
    
    針對于RecycleView列表燎悍,因?yàn)镚oogle只是更加傾向其布局的重用性,提倡視圖自定義敷待,所以這里需要自己實(shí)現(xiàn)對于ContextMenuInfo的實(shí)現(xiàn)间涵,不然RecycleView無法實(shí)現(xiàn)上下文菜單仁热。
    public class RecyclerViewWithContextMenu extends RecyclerView {
    
         private RecyclerViewContextMenuInfo mContextMenuInfo = new RecyclerViewContextMenuInfo();
    
         public RecyclerViewWithContextMenu(Context context) {
             super(context);
         }
    
         public RecyclerViewWithContextMenu(Context context, @Nullable AttributeSet attrs) {
             super(context, attrs);
         }
    
         public RecyclerViewWithContextMenu(Context context, @Nullable AttributeSet attrs, int defStyle) {
             super(context, attrs, defStyle);
         }
    
         @Override
         public boolean showContextMenuForChild(View originalView) {
             getPositionByChild(originalView);
             return super.showContextMenuForChild(originalView);
         }
    
         @Override
         public boolean showContextMenuForChild(View originalView, float x, float y) {
             getPositionByChild(originalView);
             return super.showContextMenuForChild(originalView, x, y);
         }
    
         /**
          * 重寫實(shí)現(xiàn)ContextMenuInfo返回榜揖,不然在onCreateContextMenu無法獲取到menuInfo信息
          * @return
          */
         @Override
         protected ContextMenu.ContextMenuInfo getContextMenuInfo() {
             return mContextMenuInfo;
         }
    
         /**
          * 記錄當(dāng)前RecycleView中Item上下文菜單的Postion
          * @param originalView
          */
         private void getPositionByChild(View originalView){
             LayoutManager layoutManager = getLayoutManager();
             if(layoutManager != null){
                 int position = layoutManager.getPosition(originalView);
                 mContextMenuInfo.setPostion(position);
             }
         }
    
         public class RecyclerViewContextMenuInfo implements ContextMenu.ContextMenuInfo{
             private int mPostion = -1;
    
             public int getPostion() {
                 return mPostion;
             }
    
             public void setPostion(int mPostion) {
                 this.mPostion = mPostion;
             }
         }
     }
    

上下文選擇模式

上下文操作模式: 將在屏幕頂部欄(菜單欄)顯示影響所選內(nèi)容的操作選項(xiàng),并允許用戶選擇多項(xiàng)抗蠢,一般用于對列表類型的數(shù)據(jù)進(jìn)行批量操作举哟。上下文選擇模式針對不同的列表(ListView、RecycleView)或單視圖實(shí)現(xiàn)方式有些區(qū)別迅矛,具體核心代碼如下所示:

  • ListView實(shí)現(xiàn)上下文選擇模式
    ListView上下文選擇模式
//為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) {
                //當(dāng)列表中的項(xiàng)目選中或取消勾選時(shí)妨猩,這個(gè)方法會(huì)被觸發(fā)
                //可以在這個(gè)方法中做一些更新操作,比如更改上下文操作欄的標(biāo)題
                //這里顯示已選中的項(xiàng)目數(shù)
                mode.setTitle("已選中:"+listView.getCheckedItemCount()+"項(xiàng)");
            }

            @Override
            public boolean onCreateActionMode(ActionMode mode, Menu menu) {
                mode.getMenuInflater().inflate(R.menu.context_menu,menu);
                return true;
            }

            @Override
            public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
                //可以對上下文操作欄做一些更新操作(會(huì)被ActionMode的invalidate方法觸發(fā))
                return false;
            }

            @Override
            public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
                switch (item.getItemId()){
                    case R.id.context_menu_add:
                        StringBuilder sb = new StringBuilder();
                        for (long id:listView.getCheckedItemIds()) {
                            sb.append(id);
                        }
                        Toast.makeText(ContextMenu2Activity.this, "點(diǎn)擊了添加按鈕"+sb.toString(), Toast.LENGTH_SHORT).show();
                        //關(guān)閉上下文操作欄
                        mode.finish();
                        return true;
                    case R.id.context_menu_del:
                        Toast.makeText(ContextMenu2Activity.this, "點(diǎn)擊了刪除按鈕", Toast.LENGTH_SHORT).show();
                        //關(guān)閉上下文操作欄
                        mode.finish();
                        return true;
                    case R.id.context_menu_save:
                        Toast.makeText(ContextMenu2Activity.this, "點(diǎn)擊了保存按鈕", Toast.LENGTH_SHORT).show();
                        //關(guān)閉上下文操作欄
                        mode.finish();
                        return true;
                    default:
                        return false;
                }
            }

            @Override
            public void onDestroyActionMode(ActionMode mode) {
                //在上下文操作欄被移除時(shí)會(huì)觸發(fā)秽褒,可以對Activity做一些必要的更新
                //默認(rèn)情況下壶硅,此時(shí)所有的選中項(xiàng)將會(huì)被取消選中

            }
        });
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Toast.makeText(ContextMenu2Activity.this, "點(diǎn)擊了菜單", Toast.LENGTH_SHORT).show();
            }
        });

  • RecycleView實(shí)現(xiàn)上下文選擇模式,在RecycleView中官方并未對齊支持上下文菜單以及上下文操作模式销斟,但官方針對這個(gè)情況庐椒,官方于19年3月提出RecyclerView Selection插件庫,試圖解決此問題蚂踊。
    RecycleView上下文選擇模式

要將RecyclerView Selection庫添加到Android Studio項(xiàng)目约谈,請implementation 在app 模塊的build.gradle 文件中提及以下依賴項(xiàng):

//recycleView
implementation 'com.android.support:recyclerview-v7:28.0.0'
//recyclerview-selection(如果采用androidx可采用對應(yīng)目錄下的版本)
implementation 'com.android.support:recyclerview-selection:28.0.0'

在Adapter中明確指定指出此適配器的每個(gè)項(xiàng)目將具有類型的唯一穩(wěn)定標(biāo)識符。

//在adapter構(gòu)造函數(shù)中實(shí)現(xiàn)
//明確指出此適配器的每個(gè)項(xiàng)目將具有類型的唯一穩(wěn)定標(biāo)識符非常重要Long犁钟。setHasStableIds(true);
/**
 * 為了能夠使用項(xiàng)目的位置作為其唯一標(biāo)識符,需重寫getItemId
 * @param position
 * @return
 */
@Override
public long getItemId(int position) {
    return position;
}

在ViewHolder中實(shí)現(xiàn)可以調(diào)用以唯一標(biāo)識所選列表項(xiàng)的方法棱诱。

public ItemDetailsLookup.ItemDetails getItemDetails(){
    return new LongItemDetails(getAdapterPosition(),getItemId());
}

在onBindViewHolder()方法(如果采用BaseRecyclerViewAdapterHelper需要在convert和convertPayloads)實(shí)現(xiàn)調(diào)用此代碼塊。

if(mSelectionTracker != null){
  if(mSelectionTracker.isSelected(getItemId(helper.getLayoutPosition()))){
            helper.getConvertView().setBackgroundColor(Color.parseColor("#80deea"));
            if(helper.tv instanceof CheckedTextView){
                ((CheckedTextView)helper.tv).setChecked(true);
            }
    }else {
        helper.getConvertView().setBackgroundColor(Color.WHITE);
        if(helper.tv instanceof CheckedTextView){
            ((CheckedTextView)helper.tv).setChecked(false);
        }
    }
}

并且掛載選擇跟蹤器

public void setSelectionTracker(SelectionTracker mSelectionTracker) {
    this.mSelectionTracker = mSelectionTracker;
}

實(shí)現(xiàn)ItemDetailsLookup

這個(gè)類將為選擇庫提供有關(guān)與用戶選擇關(guān)聯(lián)的項(xiàng)目的信息涝动,該選擇基于MotionEvent迈勋,所以我們必須映射到我們的ViewHolders,返回產(chǎn)生MotionEvent事件的item的信息

public class MyItemDetailsLookup extends ItemDetailsLookup<Long> {
    private RecyclerView recyclerView;

    public MyItemDetailsLookup(RecyclerView recyclerView) {
        this.recyclerView = recyclerView;
    }

    @Nullable
    @Override
    public ItemDetails<Long> getItemDetails(@NonNull MotionEvent motionEvent) {
        View childViewUnder = recyclerView.findChildViewUnder(motionEvent.getX(), motionEvent.getY());
        if(childViewUnder != null){
            RecyclerView.ViewHolder childViewHolder = recyclerView.getChildViewHolder(childViewUnder);
            if(childViewHolder instanceof SelectionQuickAdapter.SelectionQickViewHolder){
                return ((SelectionQuickAdapter.SelectionQickViewHolder)childViewHolder).getItemDetails();
            }
        }
        return null;
    }
}

在activity中創(chuàng)建選擇跟蹤器以及創(chuàng)建選擇觀察器

//創(chuàng)建選擇跟蹤器
mSelectionTracker = new SelectionTracker.Builder(
        "mySelection",
        recyclerView,
        new StableIdKeyProvider(recyclerView),      //密鑰提供者
        new MyItemDetailsLookup(recyclerView),
        StorageStrategy.createLongStorage())
        .withSelectionPredicate(SelectionPredicates.<Long>createSelectAnything())
        .build();
mAdapter.setSelectionTracker(mSelectionTracker);
//創(chuàng)建選擇觀察器
mSelectionTracker.addObserver(new SelectionTracker.SelectionObserver() {
    @Override
    public void onItemStateChanged(@NonNull Object key, boolean selected) {
        super.onItemStateChanged(key, selected);
        Log.i(TAG, "onItemStateChanged: "+key+" to "+selected);
        setSelectionTitle();
    }

    @Override
    public void onSelectionRefresh() {
        super.onSelectionRefresh();
    }

    @Override
    public void onSelectionChanged() {
        super.onSelectionChanged();
        Log.i(TAG, "onSelectionChanged: ");
        setSelectionTitle();
    }

    @Override
    public void onSelectionRestored() {
        super.onSelectionRestored();
    }
});

至此醋粟,RecycleView創(chuàng)建上下文模式核心代碼已完成靡菇,詳情可參考RecyclerView-Selection

  • 為單個(gè)View設(shè)置上下文選擇模式
    單View上下文選擇模式

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

  1. 實(shí)現(xiàn)ActionMode.Callback接口。在這個(gè)接口的回調(diào)方法中昔穴,可以為上下文操作欄加載Menu資源镰官,也可以響應(yīng)操作項(xiàng)目的點(diǎn)擊事件,還可以處理其他需要的操作吗货。
  2. 當(dāng)需要顯示操作欄時(shí)(例如泳唠,用戶長按視圖),調(diào)用Activity的startActionMode方法宙搬,并傳入前面創(chuàng)建的Callback對象作為參數(shù)笨腥。
btnContextMode.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                if(actionMode == null){
                    actionMode = startSupportActionMode(callback);
                    v.setSelected(true);        //設(shè)置View為選中狀態(tài)
                    return true;
                }
                return false;
            }
        });
private ActionMode.Callback callback = new ActionMode.Callback() {
    @Override
    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
        mode.getMenuInflater().inflate(R.menu.context_menu,menu);
        return true;
    }

    @Override
    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
        return false;
    }

    @Override
    public boolean onActionItemClicked(ActionMode mode, MenuItem menuItem) {
        switch (menuItem.getItemId()){
            case R.id.context_menu_add:
                Toast.makeText(ContextMenu4Activity.this, "點(diǎn)擊了添加按鈕", Toast.LENGTH_SHORT).show();
                return true;
            case R.id.context_menu_del:
                Toast.makeText(ContextMenu4Activity.this, "點(diǎn)擊了刪除按鈕", Toast.LENGTH_SHORT).show();
                return true;
            case R.id.context_menu_save:
                Toast.makeText(ContextMenu4Activity.this, "點(diǎn)擊了保存按鈕", Toast.LENGTH_SHORT).show();
                return true;
            default:
                return false;
        }
    }

    @Override
    public void onDestroyActionMode(ActionMode mode) {
        actionMode = null;
    }
};

彈出菜單

彈出菜單

彈出菜單: 以垂直列表形式顯示一系列操作選項(xiàng)拓哺,一般由某一控件觸發(fā),彈出菜單將顯示在對應(yīng)控件的上方或下方脖母。它適用于提供與特定內(nèi)容相關(guān)的大量操作士鸥。
主要核心代碼:

private void createPopupMenu(View view){
        PopupMenu popupMenu = new PopupMenu(this,view);
        if(Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH){
            //寫法1:getMenuInflater().inflate(R.menu.context_menu,popupMenu.getMenu());
            popupMenu.getMenuInflater().inflate(R.menu.context_menu,popupMenu.getMenu());
        }else {
            //在 API 級別 14 及更高版本中,您可以將兩行合并在一起谆级,使用 PopupMenu.inflate() 擴(kuò)充菜單烤礁。
            popupMenu.inflate(R.menu.context_menu);
        }

        popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
            @Override
            public boolean onMenuItemClick(MenuItem menuItem) {
                switch (menuItem.getItemId()){
                    case R.id.context_menu_add:
                        Toast.makeText(PopupMenuActivity.this, "點(diǎn)擊保存菜單", Toast.LENGTH_SHORT).show();
                        return true;
                    case R.id.context_menu_del:
                        Toast.makeText(PopupMenuActivity.this, "點(diǎn)擊刪除菜單", Toast.LENGTH_SHORT).show();
                        return true;
                    case R.id.context_menu_save:
                        Toast.makeText(PopupMenuActivity.this, "點(diǎn)擊保存菜單", Toast.LENGTH_SHORT).show();
                        return true;
                    default:
                        return false;
                }
            }
        });
        popupMenu.show();
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市肥照,隨后出現(xiàn)的幾起案子脚仔,更是在濱河造成了極大的恐慌,老刑警劉巖舆绎,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鲤脏,死亡現(xiàn)場離奇詭異,居然都是意外死亡吕朵,警方通過查閱死者的電腦和手機(jī)猎醇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來努溃,“玉大人硫嘶,你說我怎么就攤上這事∶┨常” “怎么了音半?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長贡蓖。 經(jīng)常有香客問我曹鸠,道長,這世上最難降的妖魔是什么斥铺? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任彻桃,我火速辦了婚禮,結(jié)果婚禮上晾蜘,老公的妹妹穿的比我還像新娘邻眷。我一直安慰自己,他們只是感情好剔交,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布肆饶。 她就那樣靜靜地躺著,像睡著了一般岖常。 火紅的嫁衣襯著肌膚如雪驯镊。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機(jī)與錄音板惑,去河邊找鬼橄镜。 笑死,一個(gè)胖子當(dāng)著我的面吹牛冯乘,可吹牛的內(nèi)容都是我干的洽胶。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼裆馒,長吁一口氣:“原來是場噩夢啊……” “哼姊氓!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起领追,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤他膳,失蹤者是張志新(化名)和其女友劉穎响逢,沒想到半個(gè)月后绒窑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡舔亭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年些膨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片钦铺。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡订雾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出矛洞,到底是詐尸還是另有隱情洼哎,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布沼本,位于F島的核電站噩峦,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏抽兆。R本人自食惡果不足惜识补,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望辫红。 院中可真熱鬧凭涂,春花似錦、人聲如沸贴妻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽名惩。三九已至澎胡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背滤馍。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工岛琼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人巢株。 一個(gè)月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓槐瑞,卻偏偏與公主長得像,于是被迫代替她去往敵國和親阁苞。 傳聞我的和親對象是個(gè)殘疾皇子困檩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容