BaseRecyclerViewAdapterHelper開(kāi)源項(xiàng)目之BaseSectionQuickAdapter 實(shí)現(xiàn)Expandable And collapse效果的源碼學(xué)習(xí)

version:2.8.5

更多分享請(qǐng)看:http://cherylgood.cn

今天我們來(lái)學(xué)習(xí)BaseRecyclerViewAdapterHelper中有關(guān)實(shí)現(xiàn)可展開(kāi)和折疊二級(jí)Item或多級(jí)Item的源碼。在開(kāi)始學(xué)習(xí)之前,我想先分析下實(shí)現(xiàn)的思路,這樣對(duì)于進(jìn)行源碼的理解效果比較好。

實(shí)現(xiàn)伸展and折疊,很多控件都有,網(wǎng)上也有用linearlayout實(shí)現(xiàn)的功能很強(qiáng)大、很炫酷的開(kāi)源項(xiàng)目,平時(shí)要實(shí)現(xiàn)一些伸縮性的自定義控件,我們也可以是用屬性動(dòng)畫,或者動(dòng)態(tài)控制控件的Layout屬性等都可以實(shí)現(xiàn)。那么現(xiàn)在我們來(lái)想象一下,如果在recyclerview中實(shí)現(xiàn)該功能,相對(duì)來(lái)說(shuō)能想到的比較合適的方式是什么呢?

其實(shí)我們可以很好的利用RecyclerView.Adapter給我們提供的如下一些通知數(shù)據(jù)源更新的方法來(lái)實(shí)現(xiàn)我們的動(dòng)態(tài)伸展and折疊功能。當(dāng)要伸展時(shí),我們動(dòng)態(tài)將下一級(jí)item的數(shù)據(jù)添加在與adapter綁定的數(shù)據(jù)集合中,然后通知layoutManger更新數(shù)據(jù)源。當(dāng)要收縮時(shí),同理,將下一級(jí)的item的數(shù)據(jù)源從與adapter綁定的數(shù)據(jù)集合中移除,然后通知更新。

* @see #notifyItemChanged(int)

* @see #notifyItemInserted(int)

* @see #notifyItemRemoved(int)

* @see #notifyItemRangeChanged(int, int)

* @see #notifyItemRangeInserted(int, int)

* @see #notifyItemRangeRemoved(int, int)

思路:

數(shù)據(jù)bean應(yīng)該有存儲(chǔ)自己數(shù)據(jù)的字段

數(shù)據(jù)bean應(yīng)該有存儲(chǔ)下一級(jí)item列表的集合類型的字段

數(shù)據(jù)bean應(yīng)該有一個(gè)字段標(biāo)識(shí)當(dāng)前item的狀態(tài)(伸展or收縮)

初始化adapter時(shí)只渲染頂級(jí)的item

點(diǎn)擊item是檢測(cè)該item是否支持伸縮

支持伸縮:當(dāng)前狀態(tài)展開(kāi)->折疊(將次級(jí)list插入adapter綁定的data集合中,刷新數(shù)據(jù));當(dāng)前狀態(tài)折疊->展開(kāi)(將次級(jí)的list從與adapter綁定的data集合中移除,刷新數(shù)據(jù))

插入或移除的位置根據(jù)點(diǎn)擊的item確定,插入量與移除量根據(jù)下一級(jí)item數(shù)量確定

插入移除過(guò)程中可以使用動(dòng)畫效果

思路理清之后我們接下來(lái)開(kāi)始學(xué)習(xí)源代碼:

實(shí)現(xiàn)Expandable?And collapse 效果我們?nèi)匀皇鞘褂肂aseMultiItemQuickAdapter實(shí)現(xiàn)即可

然后我們需要先看兩個(gè)相關(guān)的類:IExpandable接口;AbstractExpandableItem: 對(duì)數(shù)據(jù)bean的再次封裝,某個(gè)bean如果有次級(jí)的list 可以實(shí)現(xiàn)該抽象類。

package com.chad.library.adapter.base.entity;

import java.util.List;

/**

* implement the interface if the item is expandable

* Created by luoxw on 2016/8/8.

*/

public interface IExpandable {

boolean isExpanded();

void setExpanded(boolean expanded);

List getSubItems();

/**

* Get the level of this item. The level start from 0.

* If you don't care about the level, just return a negative.

*/

int getLevel();

}

可以看到,IExpandable 里面定義了四個(gè)接口方法:

isExpanded判斷當(dāng)前的bean是否已展開(kāi)

setExoanded更新bean的當(dāng)前狀態(tài)

getSubItems返回下一級(jí)的數(shù)據(jù)集合

getLevel 返回當(dāng)前item屬于第幾個(gè)層級(jí), 第一級(jí)from 0

package com.chad.library.adapter.base.entity;

import java.util.ArrayList;

import java.util.List;

/**

*

A helper to implement expandable item.

*

if you don't want to extent a class, you can also implement the interface IExpandable

* Created by luoxw on 2016/8/9.

*/

public abstract class AbstractExpandableItem implements IExpandable {

protected boolean mExpandable = false;

protected List mSubItems;

@Override

public boolean isExpanded() {

return mExpandable;

}

@Override

public void setExpanded(boolean expanded) {

mExpandable = expanded;

}

@Override

public List getSubItems() {

return mSubItems;

}

public boolean hasSubItem() {

return mSubItems != null && mSubItems.size() > 0;

}

public void setSubItems(List list) {

mSubItems = list;

}

public T getSubItem(int position) {

if (hasSubItem() && position < mSubItems.size()) {

return mSubItems.get(position);

} else {

return null;

}

}

public int getSubItemPosition(T subItem) {

return mSubItems != null ? mSubItems.indexOf(subItem) : -1;

}

public void addSubItem(T subItem) {

if (mSubItems == null) {

mSubItems = new ArrayList<>();

}

mSubItems.add(subItem);

}

public void addSubItem(int position, T subItem) {

if (mSubItems != null && position >= 0 && position < mSubItems.size()) {

mSubItems.add(position, subItem);

} else {

addSubItem(subItem);

}

}

public boolean contains(T subItem) {

return mSubItems != null && mSubItems.contains(subItem);

}

public boolean removeSubItem(T subItem) {

return mSubItems != null && mSubItems.remove(subItem);

}

public boolean removeSubItem(int position) {

if (mSubItems != null && position >= 0 && position < mSubItems.size()) {

mSubItems.remove(position);

return true;

}

return false;

}

}

字段方法解析:

mExpandable 保存當(dāng)前的狀態(tài)值,默認(rèn)為false

mSubItems 存儲(chǔ)數(shù)據(jù)bean集合

里面還包裝了一些常用的方法,這里就不一一解析了。

接下來(lái)我們以一個(gè)使用demo的實(shí)現(xiàn)來(lái)進(jìn)行分析:

我們可以看群主demo中的ExpandableUseActivity :

private ArrayList generateData() {

int lv0Count = 9;

int lv1Count = 3;

int personCount = 5;

String[] nameList = {"Bob", "Andy", "Lily", "Brown", "Bruce"};

Random random = new Random();

ArrayList res = new ArrayList<>();

for (int i = 0; i < lv0Count; i++) {

Level0Item lv0 = new Level0Item("This is " + i + "th item in Level 0", "subtitle of " + i);

for (int j = 0; j < lv1Count; j++) {

Level1Item lv1 = new Level1Item("Level 1 item: " + j, "(no animation)");

for (int k = 0; k < personCount; k++) {

lv1.addSubItem(new Person(nameList[k], random.nextInt(40)));

}

lv0.addSubItem(lv1);

}

res.add(lv0);

}

return res;

}

這段代碼的作用是生成一個(gè)支持Expandable and collapse 的數(shù)據(jù)集合,創(chuàng)建一個(gè)0級(jí)的LevelOItem 然后將下一級(jí)的Level1Item添加到Level0Item中。

public class Level0Item extends AbstractExpandableItem implements MultiItemEntity {

public String title;

public String subTitle;

public Level0Item( String title, String subTitle) {

this.subTitle = subTitle;

this.title = title;

}

@Override

public int getItemType() {

return ExpandableItemAdapter.TYPE_LEVEL_0;

}

@Override

public int getLevel() {

return 0;

}

}

可以看到Level0Item繼承了AbstractExpandableItem 并實(shí)現(xiàn)MultiItemEntity接口。里面根據(jù)實(shí)際需求定義相應(yīng)的字段即可。

Level1Item 與Level0Item一樣,只是返回的Level =1:

public class Level1Item extends AbstractExpandableItem implements MultiItemEntity{

public String title;

public String subTitle;

public Level1Item(String title, String subTitle) {

this.subTitle = subTitle;

this.title = title;

}

@Override

public int getItemType() {

return ExpandableItemAdapter.TYPE_LEVEL_1;

}

@Override

public int getLevel() {

return 1;

}

}

當(dāng)如過(guò)某一級(jí)的item沒(méi)有下一級(jí)的list時(shí),就不需要在實(shí)現(xiàn)AbstractExpandableItem了

然后我們的切入點(diǎn)時(shí)adapter,因?yàn)槟J(rèn)是折疊狀態(tài),當(dāng)我們點(diǎn)擊具備展開(kāi)折疊能力的item時(shí)才會(huì)觸發(fā)該功能,所以邏輯的控制是在adapter中的。

package com.chad.baserecyclerviewadapterhelper.adapter;

import android.util.Log;

import android.view.View;

import android.view.ViewGroup;

import com.chad.baserecyclerviewadapterhelper.R;

import com.chad.baserecyclerviewadapterhelper.entity.Level0Item;

import com.chad.baserecyclerviewadapterhelper.entity.Level1Item;

import com.chad.baserecyclerviewadapterhelper.entity.Person;

import com.chad.library.adapter.base.BaseMultiItemQuickAdapter;

import com.chad.library.adapter.base.BaseViewHolder;

import com.chad.library.adapter.base.entity.MultiItemEntity;

import java.util.List;

/**

* Created by luoxw on 2016/8/9.

*/

public class ExpandableItemAdapter extends BaseMultiItemQuickAdapter {

private static final String TAG = ExpandableItemAdapter.class.getSimpleName();

public static final int TYPE_LEVEL_0 = 0;

public static final int TYPE_LEVEL_1 = 1;

public static final int TYPE_PERSON = 2;

/**

* Same as QuickAdapter#QuickAdapter(Context,int) but with

* some initialization data.

*

* @param data A new list is created out of this one to avoid mutable list

*/

public ExpandableItemAdapter(List data) {

super(data);

addItemType(TYPE_LEVEL_0, R.layout.item_expandable_lv0);

addItemType(TYPE_LEVEL_1, R.layout.item_expandable_lv1);

addItemType(TYPE_PERSON, R.layout.item_expandable_lv2);

}

@Override

protected void convert(final BaseViewHolder holder, final MultiItemEntity item) {

switch (holder.getItemViewType()) {

case TYPE_LEVEL_0:

switch (holder.getLayoutPosition() %

3) {

case 0:

holder.setImageResource(R.id.iv_head, R.mipmap.head_img0);

break;

case 1:

holder.setImageResource(R.id.iv_head, R.mipmap.head_img1);

break;

case 2:

holder.setImageResource(R.id.iv_head, R.mipmap.head_img2);

break;

}

final Level0Item lv0 = (Level0Item)item;

holder.setText(R.id.title, lv0.title)

.setText(R.id.sub_title, lv0.subTitle)

.setImageResource(R.id.iv, lv0.isExpanded() ? R.mipmap.arrow_b : R.mipmap.arrow_r);

holder.itemView.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

int pos = holder.getAdapterPosition();

Log.d(TAG, "Level 0 item pos: " + pos);

if (lv0.isExpanded()) {

collapse(pos);

} else {

//? ? ? ? ? ? ? ? ? ? ? ? ? ? if (pos % 3 == 0) {

//? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? expandAll(pos, false);

//? ? ? ? ? ? ? ? ? ? ? ? ? ? } else {

expand(pos);

//? ? ? ? ? ? ? ? ? ? ? ? ? ? }

}

}

});

break;

case TYPE_LEVEL_1:

final Level1Item lv1 = (Level1Item)item;

holder.setText(R.id.title, lv1.title)

.setText(R.id.sub_title, lv1.subTitle)

.setImageResource(R.id.iv, lv1.isExpanded() ? R.mipmap.arrow_b : R.mipmap.arrow_r);

holder.itemView.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

int pos = holder.getAdapterPosition();

Log.d(TAG, "Level 1 item pos: " + pos);

if (lv1.isExpanded()) {

collapse(pos, false);

} else {

expand(pos, false);

}

}

});

break;

case TYPE_PERSON:

final Person person = (Person)item;

holder.setText(R.id.tv, person.name + " parent pos: " + getParentPosition(person));

break;

}

}

}

可以看到里面我們先添加3個(gè)level的布局資源文件。重點(diǎn)在convert回調(diào)方法;

最外層進(jìn)行viewholder 的類型判斷進(jìn)行數(shù)據(jù)綁定

添加點(diǎn)擊事件的監(jiān)聽(tīng)

當(dāng)被點(diǎn)擊時(shí),判斷當(dāng)前的levelitem是不是展開(kāi)的或折疊的,然后根據(jù)你的需要調(diào)用collapse或者expand進(jìn)行折疊或展開(kāi)操作。

重點(diǎn)來(lái)的,最終實(shí)現(xiàn)展開(kāi)、折疊功能其實(shí)是依賴collapse和expand這些api;那我們來(lái)看下這些api到底內(nèi)部是怎么實(shí)現(xiàn)的,我們從expand開(kāi)始。代碼中expand(pos);傳了一個(gè)pos進(jìn)來(lái),而這個(gè)pos就是被點(diǎn)擊的item在adapter數(shù)據(jù)集合中的index。

/**

* Expand an expandable item

*

* @param position? ? position of the item

* @param animate? ? ? expand items with animation

* @param shouldNotify notify the RecyclerView to rebind items, false if you want to do it

*? ? ? ? ? ? ? ? ? ? yourself.

* @return the number of items that have been added.

*/

public int expand(@IntRange(from = 0) int position, boolean animate, boolean shouldNotify) {

position -= getHeaderLayoutCount();

IExpandable expandable = getExpandableItem(position);

if (expandable == null) {

return 0;

}

if (!hasSubItems(expandable)) {

expandable.setExpanded(false);

return 0;

}

int subItemCount = 0;

if (!expandable.isExpanded()) {

List list = expandable.getSubItems();

mData.addAll(position + 1, list);

subItemCount += recursiveExpand(position + 1, list);

expandable.setExpanded(true);

subItemCount += list.size();

}

int parentPos = position + getHeaderLayoutCount();

if (shouldNotify) {

if (animate) {

notifyItemChanged(parentPos);

notifyItemRangeInserted(parentPos + 1, subItemCount);

} else {

notifyDataSetChanged();

}

}

return subItemCount;

}

/**

* Expand an expandable item

*

* @param position position of the item, which includes the header layout count.

* @param animate? expand items with animation

* @return the number of items that have been added.

*/

public int expand(@IntRange(from = 0) int position, boolean animate) {

return expand(position, animate, true);

}

/**

* Expand an expandable item with animation.

*

* @param position position of the item, which includes the header layout count.

* @return the number of items that have been added.

*/

public int expand(@IntRange(from = 0) int position) {

return expand(position, true, true);

}

可以看到expand是一個(gè)方法多態(tài),提供了三種參數(shù)類型的調(diào)用。支持是否需要?jiǎng)赢?,是否更新?shù)據(jù)源。

排除headerview的干擾,獲得實(shí)際的位置position

position -= getHeaderLayoutCount();

判斷其是否支持展開(kāi)折疊,是否有下一級(jí)items需要展開(kāi),沒(méi)有就直接返回0

IExpandable expandable = getExpandableItem(position);

if (expandable == null) {

return 0;

}

if (!hasSubItems(expandable)) {

expandable.setExpanded(false);

return 0;

}

下面代碼作用:如果處于折疊狀態(tài)且需要展開(kāi),則執(zhí)行到下面代碼,通過(guò)getSubItems獲得要展開(kāi)的list,將其添加到mdata中,通過(guò)recursiveExpand獲得要展開(kāi)的items的數(shù)量

int subItemCount = 0;

if (!expandable.isExpanded()) {

List list = expandable.getSubItems();

mData.addAll(position + 1, list);

subItemCount += recursiveExpand(position + 1, list);

expandable.setExpanded(true);

subItemCount += list.size();

}

我們可以看到recursiveExpand的源碼如下:下面是一個(gè)遞歸調(diào)用,一直遍歷到最后一層不支持展開(kāi)折疊的item才會(huì)回溯回來(lái),遍歷過(guò)程中可以看到一個(gè)判斷,if(item.isExpanded) 就是如果下一級(jí)的items原來(lái)已經(jīng)是處于展開(kāi)狀態(tài)的,此時(shí)我們也需要展開(kāi)他。最終返回的是所需展開(kāi)的items的數(shù)量。

private int recursiveExpand(int position, @NonNull List list) {

int count = 0;

int pos = position + list.size() - 1;

for (int i = list.size() - 1; i >= 0; i--, pos--) {

if (list.get(i) instanceof IExpandable) {

IExpandable item = (IExpandable) list.get(i);

if (item.isExpanded() && hasSubItems(item)) {

List subList = item.getSubItems();

mData.addAll(pos + 1, subList);

int subItemCount = recursiveExpand(pos + 1, subList);

count += subItemCount;

}

}

}

return count;

}

獲得需要展開(kāi)的items的數(shù)量值,也將數(shù)據(jù)集合添加到了mData中,此時(shí)我們通知layoutManager刷新數(shù)據(jù)即可

int parentPos = position + getHeaderLayoutCount();

if (shouldNotify) {

if (animate) {

notifyItemChanged(parentPos);

notifyItemRangeInserted(parentPos + 1, subItemCount);

} else {

notifyDataSetChanged();

}

}

刷新的時(shí)候我們要先確定開(kāi)始刷新位置,所以需要加上headerview的數(shù)量

然后調(diào)用如上代碼即可。折疊是反向進(jìn)行的,根據(jù)這個(gè)思路看就可以了。

總結(jié):折疊->展開(kāi):mData添加需展開(kāi)的數(shù)據(jù)集,更新數(shù)據(jù)源;展開(kāi)->折疊:mData移除需折疊的數(shù)據(jù)集,更新數(shù)據(jù)源。

后面會(huì)繼續(xù)分析其他功能的實(shí)現(xiàn)源碼,歡迎一起學(xué)習(xí)!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 背景 一年多以前我在知乎上答了有關(guān)LeetCode的問(wèn)題, 分享了一些自己做題目的經(jīng)驗(yàn)。 張土汪:刷leetcod...
    土汪閱讀 12,890評(píng)論 0 33
  • version:2.8.5 更多分享請(qǐng)看:http://cherylgood.cn 今天我們來(lái)學(xué)習(xí)下BaseRec...
    Angels_安杰閱讀 3,815評(píng)論 0 1
  • 因?yàn)辇R言楚語(yǔ)在微博上分享她的簡(jiǎn)書文章《2015效率手冊(cè)整裝待發(fā)》,我在時(shí)隔10個(gè)月之后,重新登錄了我的簡(jiǎn)書賬號(hào)“八...
    草上霜閱讀 496評(píng)論 0 1
  • 今日得到: 一、毛遂自薦是“非禮”的,三顧茅廬才“合禮”。君子應(yīng)該矜持,“用之則行,舍之則藏” 二、傳統(tǒng)上反對(duì)自由...
    爺有蔓草閱讀 328評(píng)論 0 0
  • RunTime_運(yùn)行時(shí)詳解 運(yùn)行時(shí)機(jī)制: 消息發(fā)送機(jī)制: RunTime 運(yùn)行時(shí):蘋果提供了一個(gè)API,屬于C語(yǔ)言...
    _CLAY_閱讀 355評(píng)論 0 1

友情鏈接更多精彩內(nèi)容