Android RecyclerView 二級列表實(shí)現(xiàn)

[TOC]

Android RecyclerView 二級列表實(shí)現(xiàn)

2017.5.16 添加demo

git

Demo

簡述

在開發(fā) Android APP 的時(shí)候感耙,難免會需要實(shí)現(xiàn)二級列表的情況塑娇,而在自己的項(xiàng)目中使用的列表是 android.support.v7.widget 包里面的 RecyclerView磕谅,好處是可以根據(jù)情況實(shí)現(xiàn)不同樣式的列表业稼,可擴(kuò)展程度高突倍。而壞處是什么都要自己實(shí)現(xiàn)芭商。所以在想要用 RecyclerView 實(shí)現(xiàn)的二級列表的時(shí)候,卻發(fā)現(xiàn)沒有類似 ListViewExpandableListView须蜗,只能自己去實(shí)現(xiàn)。

實(shí)現(xiàn)基礎(chǔ)

在使用 RecyclerView 的時(shí)候目溉,與 ListView 類似明肮,需要創(chuàng)建一個(gè) Adapter 去告訴 RecyclerView 如何工作[1],而在創(chuàng)建 RecyclerViewAdapter 的時(shí)候缭付,一般需要重載以下幾個(gè)方法:
onCreateViewHolder() 為每個(gè)項(xiàng)目創(chuàng)建 ViewHolder
onBindViewHolder() 處理每個(gè) item
getItemViewType()onCreateViewHolder 前調(diào)用柿估,返回 item 類型
getItemCount() 獲取 item 總數(shù)
加載 RecyclerView 的過程如下圖:

flow.PNG

ps簡書的MD不支持流程圖?

除此之外陷猫,還需要創(chuàng)建一個(gè) ViewHolder 用于尋找自定義 item的各個(gè)控件秫舌。

實(shí)現(xiàn)思路

根據(jù)上述,在實(shí)現(xiàn)二級列表的時(shí)候绣檬,我們在 onCreateViewHolder()onBindViewHolder() 中足陨,判斷是加載一級項(xiàng)目(GroupItem)還是二級子項(xiàng)目(SubItem)。

實(shí)現(xiàn)過程

二級列表數(shù)據(jù)格式

一般來說娇未,一個(gè) GroupItem 下面有一個(gè)墨缘,或多個(gè) SubItem,一對多零抬。
在這里镊讼,用一個(gè) DataTree 來封裝這種數(shù)據(jù)格式,代碼如下:

public class DataTree<K, V> {

    private K groupItem;
    private List<V> subItems;

    public DataTree(K groupItem, List<V> subItems) {
        this.groupItem = groupItem;
        this.subItems = subItems;
    }

    public K getGroupItem() {
        return groupItem;
    }

    public List<V> getSubItems() {
        return subItems;
    }
}

RecyclerView Item狀態(tài)

ItemStatus 用封裝列表每一項(xiàng)的狀態(tài)平夜,包括:
viewType item的類型蝶棋,group item 還是 subitem
groupItemIndex 一級索引位置
subItemIndex 如果該 item 是一個(gè)二級子項(xiàng)目,則保存子項(xiàng)目索引

    private static class ItemStatus {

        public static final int VIEW_TYPE_GROUPITEM = 0;
        public static final int VIEW_TYPE_SUBITEM = 1;

        private int viewType;
        private int groupItemIndex = 0;
        private int subItemIndex = -1;

        public ItemStatus() {
        }

        public int getViewType() {
            return viewType;
        }

        public void setViewType(int viewType) {
            this.viewType = viewType;
        }

        public int getGroupItemIndex() {
            return groupItemIndex;
        }

        public void setGroupItemIndex(int groupItemIndex) {
            this.groupItemIndex = groupItemIndex;
        }

        public int getSubItemIndex() {
            return subItemIndex;
        }

        public void setSubItemIndex(int subItemIndex) {
            this.subItemIndex = subItemIndex;
        }
    }

ViewHolder

這里的 groupItemsubItem 分別用不同的布局文件忽妒,所以我把 ViewHolder 分開寫玩裙,如下:

    public static class GroupItemViewHolder extends RecyclerView.ViewHolder {
        ...
        public GroupItemViewHolder(View itemView) {
            super(itemView);
            ...
        }
    }

    public static class SubItemViewHolder extends RecyclerView.ViewHolder {
        ...
        public SubItemViewHolder(View itemView) {
            super(itemView);
            ...
        }
    }

其它屬性和方法

context
list dataTrees 用于顯示的數(shù)據(jù)
list<Boolean> groupItemStatus 保存 groupItem 狀態(tài)兼贸,開還是關(guān)

    //向外暴露設(shè)置顯示數(shù)據(jù)的方法
    public void setDataTrees(List<DataTree<Album, Track>> dt) {
        this.dataTrees = dt;
        initGroupItemStatus(groupItemStatus);
        notifyDataSetChanged();
    }

    //設(shè)置初始值,所有 groupItem 默認(rèn)為關(guān)閉狀態(tài)
    private void initGroupItemStatus(List l) {
        for (int i = 0; i < dataTrees.size(); i++) {
            l.add(false);
        }
    }

getItemStatusByPosition() 方法實(shí)現(xiàn)

顧名思義献酗,用于根據(jù) position 來計(jì)算判斷該 item 的狀態(tài)寝受,返回一個(gè) ItemStatus

position

代碼如下:

private ItemStatus getItemStatusByPosition(int position) {

        ItemStatus itemStatus = new ItemStatus();

        int count = 0;    //計(jì)算groupItemIndex = i 時(shí)罕偎,position最大值
        int i = 0;

        //輪詢 groupItem 的開關(guān)狀態(tài)
        for (i = 0; i < groupItemStatus.size(); i++ ) {
            
            //pos剛好等于計(jì)數(shù)時(shí)很澄,item為groupItem
            if (count == position) {
                itemStatus.setViewType(ItemStatus.VIEW_TYPE_GROUPITEM);
                itemStatus.setGroupItemIndex(i);
                break;
                
            //pos大于計(jì)數(shù)時(shí),item為groupItem(i - 1)中的某個(gè)subItem
            } else if (count > position) {

                itemStatus.setViewType(ItemStatus.VIEW_TYPE_SUBITEM);
                itemStatus.setGroupItemIndex(i - 1);
                itemStatus.setSubItemIndex(position - ( count - dataTrees.get(i - 1).getSubItems().size() ) );
                break;

            }
            
            //無論groupItem狀態(tài)是開或者關(guān)颜及,它在列表中都會存在甩苛,所有count++
            count++;

            //當(dāng)輪詢到的groupItem的狀態(tài)為“開”的話,count需要加上該groupItem下面的子項(xiàng)目數(shù)目
            if (groupItemStatus.get(i)) {

                count += dataTrees.get(i).getSubItems().size();

            }


        }
        
        //簡單地處理當(dāng)輪詢到最后一項(xiàng)groupItem的時(shí)候
        if (i >= groupItemStatus.size()) {
            itemStatus.setGroupItemIndex(i - 1);
            itemStatus.setViewType(ItemStatus.VIEW_TYPE_SUBITEM);
            itemStatus.setSubItemIndex(position - ( count - dataTrees.get(i - 1).getSubItems().size() ) );
        }

        return itemStatus;
    }

getItemCount()方法實(shí)現(xiàn)

該方法在顯示列表的時(shí)候會執(zhí)行多次俏站,如果返回的項(xiàng)目計(jì)數(shù)不正確的話程序會出現(xiàn)錯(cuò)誤奔潰讯蒲,代碼如下:

    @Override
    public int getItemCount() {

        Logger.i("1");

        int itemCount = 0;

        if (groupItemStatus.size() == 0) {
            return 0;
        }

        for (int i = 0; i < dataTrees.size(); i++) {

            if (groupItemStatus.get(i)) {
                itemCount += dataTrees.get(i).getSubItems().size() + 1;
            } else {
                itemCount++;
            }

        }

        return itemCount;
    }

其它方法實(shí)現(xiàn)

  • getItemViewType()

該方法會在 onCreateViewHolder() 前執(zhí)行,并返回 int viewType肄扎,代碼如下:

    @Override
    public int getItemViewType(int position) {
        return getItemStatusByPosition(position).getViewType();
    }
  • onCreateViewHolder()
    根據(jù)不同的由 getItemViewType() 返回的 viewType 選擇不同的項(xiàng)目布局墨林,代碼如下:
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v;
        RecyclerView.ViewHolder viewHolder = null;

        if (viewType == ItemStatus.VIEW_TYPE_GROUPITEM) {

            v = LayoutInflater.from(parent.getContext()).inflate(R.layout
                    .item_artist_detail_album, parent, false);
            viewHolder = new GroupItemViewHolder(v);

        } else if (viewType == ItemStatus.VIEW_TYPE_SUBITEM) {

            v = LayoutInflater.from(parent.getContext()).inflate(R.layout
                    .item_artist_detail_track, parent, false);
            viewHolder = new SubItemViewHolder(v);
        }

        return viewHolder;
    }
  • onBindViewHolder()
    根據(jù)不同的 viewType 綁定不同的 ViewHolder ,代碼如下:
   @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {

        final ItemStatus itemStatus = getItemStatusByPosition(position);

        final DataTree<Album, Track> dt = dataTrees.get(itemStatus.getGroupItemIndex());

        if ( itemStatus.getViewType() == ItemStatus.VIEW_TYPE_GROUPITEM ) {

            final GroupItemViewHolder groupItemVh = (GroupItemViewHolder) holder;

            . . .    //加載groupItem犯祠,處理groupItem控件

            groupItemVh.itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {

                    int groupItemIndex = itemStatus.getGroupItemIndex();

                    if ( !groupItemStatus.get(groupItemIndex) ) {

                        . . .  //groupItem由“關(guān)閉”狀態(tài)到“打開”狀態(tài)
                        
                        groupItemStatus.set(groupItemIndex, true);
                        notifyItemRangeInserted(groupItemVh.getAdapterPosition() + 1, dt.getSubItems().size());

                    } else {

                     . . .    //groupItem由“打開”狀態(tài)到“關(guān)閉”狀態(tài)
           
                    groupItemStatus.set(groupItemIndex, false);
                        notifyItemRangeRemoved(groupItemVh.getAdapterPosition() + 1, dt.getSubItems().size());
                        
                    }

                }
            });

        } else if (itemStatus.getViewType() == ItemStatus.VIEW_TYPE_SUBITEM) {

            SubItemViewHolder subItemVh = (SubItemViewHolder) holder;

             . . .    //加載subItem旭等,處理subItem控件

            subItemVh.itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {

                 . . .    //點(diǎn)擊subItem處理

                }
            });
        }
    }

總結(jié)

二級列表對 RecyclerView 小小地?cái)U(kuò)展,在實(shí)現(xiàn)的過程中衡载,有點(diǎn)麻煩的地方是對特定的 item 進(jìn)行識別搔耕,即 getItemViewType() 的實(shí)現(xiàn),在這里痰娱,我簡單地用遍歷輪詢的方法去判斷弃榨,應(yīng)該會有更簡單更節(jié)省資源的方法。實(shí)現(xiàn)二級列表之后梨睁,理論上可以實(shí)現(xiàn)多級列表鲸睛,可以試試。

在用這個(gè)方法實(shí)現(xiàn)之前坡贺,有嘗試過有 View 提供的方法—— View.setTag() 腊凶,但是由于 RecyclerView 的加載機(jī)制,當(dāng)列表被劃出界面時(shí)拴念,會被銷毀钧萍,而重新劃進(jìn)來顯示時(shí),RecyclerView 會重新創(chuàng)建新的 item政鼠,而不是之前的那個(gè)风瘦,所以,之前的 Tag 會不見公般,最后沒能實(shí)現(xiàn)出來万搔,感興趣的同學(xué)可以試試胡桨。


  1. [Android RecyclerView 使用完全解析 體驗(yàn)藝術(shù)般的控件 - hongyang - CSDN博客]http://blog.csdn.net/lmj623565791/article/details/45059587 ?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市瞬雹,隨后出現(xiàn)的幾起案子昧谊,更是在濱河造成了極大的恐慌,老刑警劉巖酗捌,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件呢诬,死亡現(xiàn)場離奇詭異,居然都是意外死亡胖缤,警方通過查閱死者的電腦和手機(jī)尚镰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來哪廓,“玉大人狗唉,你說我怎么就攤上這事∥姓妫” “怎么了分俯?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長哆料。 經(jīng)常有香客問我缸剪,道長,這世上最難降的妖魔是什么剧劝? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任橄登,我火速辦了婚禮抓歼,結(jié)果婚禮上讥此,老公的妹妹穿的比我還像新娘。我一直安慰自己谣妻,他們只是感情好萄喳,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蹋半,像睡著了一般他巨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上减江,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天染突,我揣著相機(jī)與錄音,去河邊找鬼辈灼。 笑死份企,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的巡莹。 我是一名探鬼主播司志,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼甜紫,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了骂远?” 一聲冷哼從身側(cè)響起囚霸,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎激才,沒想到半個(gè)月后拓型,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡贸营,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年吨述,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片钞脂。...
    茶點(diǎn)故事閱讀 39,834評論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡揣云,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出冰啃,到底是詐尸還是另有隱情邓夕,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布阎毅,位于F島的核電站焚刚,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏扇调。R本人自食惡果不足惜矿咕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望狼钮。 院中可真熱鬧碳柱,春花似錦、人聲如沸熬芜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽涎拉。三九已至瑞侮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鼓拧,已是汗流浹背半火。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留季俩,地道東北人钮糖。 一個(gè)月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像种玛,于是被迫代替她去往敵國和親藐鹤。 傳聞我的和親對象是個(gè)殘疾皇子瓤檐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評論 2 354

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