Android中RecyclerView實(shí)現(xiàn)多級(jí)折疊列表效果(二)
前言
在本文開(kāi)始之前請(qǐng)大家先看一下這篇文章:http://www.dbjr.com.cn/article/113510.htm
上面的這篇文章是之前寫的,里面發(fā)現(xiàn)有很多不好用地方,也學(xué)到些新姿勢(shì),改動(dòng)了許多地方。下面來(lái)看看詳細(xì)的介紹:
要點(diǎn):
1.可以通過(guò)后臺(tái)控制Item的展示.
2.TreeRecyclerAdapter,可以展開(kāi),折疊.多級(jí)展示
3.adapter可以使用裝飾者模式進(jìn)行擴(kuò)展.支持EmptyAdapter.可以添加headview和footview
4.item的樣式可以編寫文檔,type與Class進(jìn)行對(duì)應(yīng),實(shí)現(xiàn)后臺(tái)控置,相同Item復(fù)用.
思路:(包含第一篇的思路)
1.adapter應(yīng)該只需要關(guān)心List<baseItem> datas 的內(nèi)容
2.把每個(gè)item看成獨(dú)立的個(gè)體. 布局樣式,每行所占比,onbindViewHolder由各個(gè)item實(shí)現(xiàn)。
3.每一個(gè)item應(yīng)該只關(guān)心自己的數(shù)據(jù)和自己的下一級(jí)的數(shù)據(jù),不會(huì)去關(guān)心上上級(jí),下下級(jí)
4.展開(kāi)的實(shí)現(xiàn),點(diǎn)擊時(shí)item把子數(shù)據(jù)拿出來(lái),然后添加到adapter的datas中,變成同級(jí),因?yàn)橹粫?huì)展開(kāi)自己的下級(jí)數(shù)據(jù)。
5.折疊的實(shí)現(xiàn),拿到下級(jí)數(shù)據(jù)(可以理解因?yàn)橐粋€(gè)文件夾下文件),然后從adapter的datas中刪除這些數(shù)據(jù)。
6.后臺(tái)控制可以通過(guò)初始化注冊(cè)的方法,將Item的Class注冊(cè).保存到集合里
7.后臺(tái)返回字段,獲取對(duì)應(yīng)class文件,通過(guò)Class.newInstance()方法構(gòu)建實(shí)例.
8.將ViewHolder與Adapter寫成通用的,不需要再寫多個(gè)Adatper與ViewHolder,只需要寫多個(gè)Baseitem.與BaseItamData(JavaBean).
目錄介紹
+ 1.Adapter * Wapper------擴(kuò)展的wapper, * EmptyWapper --------當(dāng)無(wú)數(shù)據(jù)時(shí)顯示頁(yè)面. * HeaderAndFootWapper --------添加頭部view和尾部view - BaseRecyclerAdapter --------封裝的Adatper基類 - ItemManager --------接口,管理Adatper刷新,增刪操作 - TreeRecyclerAdapter ----多級(jí)列表,樹(shù)形結(jié)構(gòu)的adapter - TreeRecyclerViewType ----多級(jí)列表的顯示樣式,枚舉 - ViewHolder----封裝的通用viewHodler * 2.base BaseItem<D extends BaseItemData> ------item的封裝 BaseItemData-----item的數(shù)據(jù)要求.javabean需要繼承該類. * 3.factory ItemConfig ----添加item的class,配置樣式 Itemfactory----通過(guò)class生成BaseItem的工廠類 * 4.view TreeItem ----樹(shù)形列表的子item TreeItemGroup ----樹(shù)形列表的父item TreeParent---TreeItemGroup 實(shí)現(xiàn)該接口 TreeSelectItemGroup---可以選中子item的TreeItemGroup. demo:見(jiàn)購(gòu)物頁(yè)面
來(lái)張丑丑的圖:

下面貼出部分代碼:
(一).BaseRecyclerAdapter :
/**
* 普通BaseRecyclerAdapter,itme無(wú)父子關(guān)系.
* 限定泛型為BaseItem的子類.
* 通過(guò)BaseItem去處理ViewHolder
*/
public class BaseRecyclerAdapter<T extends BaseItem> extends
RecyclerView.Adapter<ViewHolder> {
private List<T> mDatas;//展示數(shù)據(jù)
private ItemManager<T> mItemManager;
private CheckItem mCheckItem;
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//看源碼,這里的parent就是Recyclerview,所以不會(huì)為null.可以通過(guò)它拿到context
return ViewHolder.createViewHolder(parent.getContext(), parent, viewType);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
T t = getDatas().get(position);
//檢查是否綁定了ItemManage,因?yàn)閕tem需要通過(guò)ItemManage告訴adapter刷新,增刪
checkItemManage(t);
//具體onBindViewHolder放到item里面去實(shí)現(xiàn)
t.onBindViewHolder(holder);
//實(shí)現(xiàn)點(diǎn)擊事件
onBindViewHolderClick(holder);
}
/**
* 實(shí)現(xiàn)item的點(diǎn)擊事件
*
* @param holder 綁定點(diǎn)擊事件的ViewHolder
*/
public void onBindViewHolderClick(final ViewHolder holder) {
//判斷當(dāng)前holder是否已經(jīng)設(shè)置了點(diǎn)擊事件
if (!holder.itemView.hasOnClickListeners()) {
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//獲得holder的position
int layoutPosition = holder.getLayoutPosition();
//檢查position是否可以點(diǎn)擊
if (getCheckItem().checkPosition(layoutPosition)) {
//檢查并得到真實(shí)的position
int itemPosition = getCheckItem().getAfterCheckingPosition(layoutPosition);
//拿到對(duì)應(yīng)item,回調(diào).
getDatas().get(itemPosition).onClick();
}
}
});
}
}
/**
* 這里將LayoutId作為type,因?yàn)長(zhǎng)ayoutId不可能相同,個(gè)人覺(jué)得可以作為item的標(biāo)志
* @param position
* @return
*/
@Override
public int getItemViewType(int position) {
return mDatas.get(position).getLayoutId();
}
@Override
public int getItemCount() {
return mDatas == null ? 0 : mDatas.size();
}
public List<T> getDatas() {
if (mDatas == null) {
mDatas = new ArrayList<>();
}
return mDatas;
}
/**
* 需要手動(dòng)setDatas(List<T> datas),否則數(shù)據(jù)為空
* @param datas
*/
public void setDatas(List<T> datas) {
if (datas != null) {
mDatas = datas;
getItemManager().notifyDataSetChanged();
}
}
}
(二).TreeRecyclerAdapter
/**
* Created by baozi on 2017/4/20.
* 樹(shù)級(jí)結(jié)構(gòu)recycleradapter.
* item之間有子父級(jí)關(guān)系,
*/
public class TreeRecyclerAdapter<T extends TreeItem> extends BaseRecyclerAdapter<T> {
private TreeRecyclerViewType type;
/**
* 最初的數(shù)據(jù).沒(méi)有經(jīng)過(guò)增刪操作.
*/
private List<T> initialDatas;
@Override
public void onBindViewHolderClick(final ViewHolder holder) {
//判斷是否已有點(diǎn)擊監(jiān)聽(tīng)
if (!holder.itemView.hasOnClickListeners()) {
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//獲得holder的LayoutPosition.
int layoutPosition = holder.getLayoutPosition();
//判斷是否需要點(diǎn)擊
if (getCheckItem().checkPosition(layoutPosition)) {
int itemPosition = getCheckItem().getAfterCheckingPosition(layoutPosition);
//展開(kāi),折疊和item點(diǎn)擊不應(yīng)該同時(shí)響應(yīng)事件.
if (type != TreeRecyclerViewType.SHOW_ALL) {
//展開(kāi),折疊
expandOrCollapse(itemPosition);
} else {
//點(diǎn)擊事件
T item = getDatas().get(itemPosition);
TreeItemGroup itemParentItem = item.getParentItem();
//判斷上一級(jí)是否需要攔截這次事件,只處理當(dāng)前item的上級(jí),不關(guān)心上上級(jí)如何處理.
if (itemParentItem != null && itemParentItem.onInterceptClick(item)) {
return;
}
item.onClick();
}
}
}
});
}
}
@Override
public void setDatas(List<T> datas) {
//保存未修改過(guò)的List
initialDatas = datas;
//如果展開(kāi)顯示全部,則需要遞歸處理添加所以的item
if (type == TreeRecyclerViewType.SHOW_ALL) {
for (int i = 0; i < datas.size(); i++) {
T t = datas.get(i);
getDatas().add(t);
//判斷item是否是TreeItemGroup
if (t instanceof TreeItemGroup) {
List childs = ((TreeItemGroup) t).getAllChilds();
if (childs != null) {
//添加到item的后面.
getDatas().addAll(childs);
}
}
}
} else {
super.setDatas(datas);
}
}
/**
* 相應(yīng)RecyclerView的點(diǎn)擊事件 展開(kāi)或關(guān)閉某節(jié)點(diǎn)
*
* @param position 觸發(fā)的條目
*/
private void expandOrCollapse(int position) {
T baseItem = getDatas().get(position);
if (baseItem instanceof TreeItemGroup && ((TreeItemGroup) baseItem).isCanChangeExpand()) {
TreeItemGroup treeParentItem = (TreeItemGroup) baseItem;
boolean expand = treeParentItem.isExpand();
List<T> allChilds = treeParentItem.getAllChilds();
if (expand) {
getDatas().removeAll(allChilds);
treeParentItem.onCollapse();
treeParentItem.setExpand(false);
} else {
getDatas().addAll(position + 1, allChilds);
treeParentItem.onExpand();
treeParentItem.setExpand(true);
}
getItemManager().notifyDataSetChanged();
}
}
}
(三).BaseItem(部分get.set代碼省略)
/**
* Item的基類
*/
public abstract class BaseItem<D extends BaseItemData> {
/**
* 當(dāng)前item的數(shù)據(jù)
*/
protected D data;
/**
* item在每行中的spansize
* 默認(rèn)為0,如果為0則占滿一行
*
* @return 所占值, 比如recyclerview的列數(shù)為6, item需要占一半寬度, 就設(shè)置3
*/
private int spanSize;
/**
* 可以通過(guò)ItemManager ,操作adatper
* @return
*/
private ItemManager mItemManager;
public int getLayoutId() {
if (initLayoutId() <= 0) {
throw new Resources.NotFoundException("請(qǐng)?jiān)O(shè)置布局Id");
}
return initLayoutId();
}
/**
* 子類需要實(shí)現(xiàn)該方法,并返回item的布局id
*
* @return 返回布局id.如果返回0,則會(huì)拋出異常
*/
protected abstract int initLayoutId();
/**
* 抽象holder的綁定
*/
public abstract void onBindViewHolder(ViewHolder viewHolder);
/**
* 當(dāng)前條目的點(diǎn)擊回調(diào)
* 如果不需要點(diǎn)擊事件,則可以不復(fù)寫該方法.
*/
public void onClick() {
}
(四).TreeItem
/**
* 組合模式
* TreeRecyclerAdapter的item
*/
public abstract class TreeItem<D extends BaseItemData> extends BaseItem<D> {
private TreeItemGroup parentItem;
public void setParentItem(TreeItemGroup parentItem) {
this.parentItem = parentItem;
}
/**
* 獲取當(dāng)前item的父級(jí)
*
* @return
*/
@Nullable
public TreeItemGroup getParentItem() {
return parentItem;
}
}
(五).TreeItemGroup
/**
* Created by baozi on 2016/12/22.
* 擁有子集
* 子集可以是parent,也可以是child
*/
public abstract class TreeItemGroup<D extends BaseItemData> extends TreeItem<D>
implements TreeParent {
/**
* 持有的子item
*/
private List<? extends BaseItem> childs;
/**
* 是否展開(kāi)
*/
private boolean isExpand;
/**
* 能否展開(kāi)
*/
protected boolean isCanChangeExpand = true;
/**
* 展開(kāi)
*/
@Override
public void onExpand() {
}
/**
* 折疊
*/
@Override
public void onCollapse() {
}
/**
* 獲得自己的childs.
* @return
*/
@Nullable
public List<? extends BaseItem> getChilds() {
return childs;
}
/**
* 獲得所有childs,包括子item的childs
* @return
*/
@Nullable
public List<? extends BaseItem> getAllChilds() {
if (getChilds() == null) {
return null;
}
ArrayList<BaseItem> baseItems = new ArrayList<>();
for (int i = 0; i < childs.size(); i++) {
//下級(jí)
BaseItem baseItem = childs.get(i);
baseItems.add(baseItem);
//判斷是否還有下下級(jí),并且處于expand的狀態(tài)
if (baseItem instanceof TreeItemGroup && ((TreeItemGroup) baseItem).isExpand()) {
//調(diào)用下級(jí)的getAllChilds遍歷,相當(dāng)于遞歸遍歷
List list = ((TreeItemGroup) baseItem).getAllChilds();
if (list != null && list.size() > 0) {
baseItems.addAll(list);
}
}
}
return baseItems;
}
public int getChildsCount() {
return childs == null ? 0 : childs.size();
}
/**
* 初始化子集
*
* @param data
* @return
*/
protected abstract List<? extends BaseItem> initChildsList(D data);
/**
* 是否消費(fèi)child的click事件
*
* @param child 具體click的item
* @return 返回true代表消費(fèi)此次事件,child不會(huì)走onclick(),返回false說(shuō)明不消費(fèi)此次事件,child依然會(huì)走onclick()
*/
public boolean onInterceptClick(TreeItem child) {
return false;
}
(六).TreeSelectItemGroup
/**
* Created by baozi on 2016/12/22.
* 可以選中子item的TreeItemGroup,點(diǎn)擊的item會(huì)保存起來(lái).可以通過(guò) getSelectItems()獲得選中item
*/
public abstract class TreeSelectItemGroup<D extends BaseItemData>
extends TreeItemGroup<D> {
/**
* 選中的子item.只支持下一級(jí),不支持下下級(jí)
*/
private List<BaseItem> selectItems;
public List<BaseItem> getSelectItems() {
if (selectItems == null) {
selectItems = new ArrayList<>();
}
return selectItems;
}
/**
* 是否有選中item,
* @return
*/
public boolean isHaveCheck() {
return !getSelectItems().isEmpty();
}
@Override
public boolean onInterceptClick(TreeItem child) {
//單選
if (selectFlag() == SelectFlag.SINGLE_CHOICE) {
//如果已經(jīng)有選中的,則替換
if (getSelectItems().size() != 0) {
getSelectItems().set(0, child);
} else {
//沒(méi)有選中的則添加
getSelectItems().add(child);
}
} else {
//判斷是否已添加.
int index = getSelectItems().indexOf(child);
if (index == -1) {//不存在則添加
getSelectItems().add(child);
} else {//存在則刪除
getSelectItems().remove(index);
}
}
return super.onInterceptClick(child);
}
/**
* 必須指定選中樣式
* @return
*/
public abstract SelectFlag selectFlag();
/**
* 決定TreeSelectItemGroup的選中樣式
*/
public enum SelectFlag {
/**
* 單選
*/
SINGLE_CHOICE,
/**
* 多選
*/
MULTIPLE_CHOICE
}
具體的使用實(shí)例效果:
1.購(gòu)物頁(yè)面:

Demo中的代碼:
//拿到數(shù)據(jù)
List<StoreBean> storeBean = initData();
//通過(guò)ItemFactory生成第一級(jí)Item,如果是通過(guò)后臺(tái)控制,則不需要傳Class
//List<ShopTitileItem> itemList = ItemFactory.createItemList(storeBean);
List<ShopTitileItem> itemList = ItemFactory.createItemList(storeBean, ShopTitileItem.class);
//創(chuàng)建TreeRecyclerAdapter
mAdapter = new TreeRecyclerAdapter<>();
//設(shè)置adapter的顯示樣式
mAdapter.setType(TreeRecyclerViewType.SHOW_ALL);
//將數(shù)據(jù)設(shè)置到Adapter中
mAdapter.setDatas(itemList);
//設(shè)置adapter
mRecyclerView.setAdapter(mAdapter);
/**
* item的代碼
* Created by baozi on 2016/12/22.
*/
public class ShopTitileItem extends TreeSelectItemGroup<StoreBean> {
@Override
protected List<? extends BaseItem> initChildsList(StoreBean data) {
return ItemFactory.createTreeItemList(data.getShopListBeen(), this);
}
@Override
protected int initLayoutId() {
return R.layout.item_shopcart_title;
}
@Override
public void onBindViewHolder(ViewHolder holder) {
holder.setChecked(R.id.cb_ischeck, isHaveCheck());
}
@Override
public void onClick() {
if (!isHaveCheck()) {
getSelectItems().clear();
getSelectItems().addAll(getChilds());
} else {
getSelectItems().clear();
}
int size = getChilds().size();
for (int i = 0; i < size; i++) {
ShopListBean data = (ShopListBean) getChilds().get(i).getData();
data.setCheck(isHaveCheck());
}
getItemManager().notifyDataSetChanged();
}
@Override
public SelectFlag selectFlag() {
return SelectFlag.MULTIPLE_CHOICE;
}
@Override
public boolean canExpandOrCollapse() {
return false;
}
2.多級(jí)列表

Demo中的代碼:
//拿到數(shù)據(jù)
List<CityBean> cityBeen = initData();
//通過(guò)ItemFactory生成List<BaseItem>
List<OneTreeItemParent> itemList = ItemFactory.createItemList(cityBeen);
TreeRecyclerAdapter treeRecyclerAdapter = new TreeRecyclerAdapter();
//設(shè)置數(shù)據(jù)
treeRecyclerAdapter.setDatas(itemList);
recyclerView.setAdapter(treeRecyclerAdapter);
/**
*item的代碼
* Created by baozi on 2016/12/8.
*/
public class OneTreeItemParent extends TreeItemGroup<CityBean> {
@Override
public List<? extends TreeItem> initChildsList(CityBean data) {
return ItemFactory.createTreeItemList(data.getCitys(), TwoTreeItemParent.class, this);
}
@Override
public int initLayoutId() {
return R.layout.itme_one;
}
@Override
public void onBindViewHolder(ViewHolder holder) {
holder.setText(R.id.tv_content, data.getProvinceName());
}
@Override
public boolean canExpandOrCollapse() {
return false;
}
}
3.多種type的列表


總結(jié):
1.我覺(jué)得像購(gòu)物車那種頁(yè)面挺復(fù)雜的,既然寫了多級(jí)列表,何不擴(kuò)展一個(gè)出來(lái)
2.RecyclerView的點(diǎn)擊事件,看了很多封裝,發(fā)現(xiàn)很多都是每次onBindViewHolder去重新設(shè)置一遍,感覺(jué)挺不好的.
3.我喜歡把數(shù)據(jù)集合讓Adatper去持有,然后通過(guò)Adapter進(jìn)行增刪改查操作.直接在Activity里持有數(shù)據(jù)集合進(jìn)行操作,我不是習(xí)慣(- -)
4.用的習(xí)慣沒(méi)bug的才是好東西,如果你覺(jué)得實(shí)用或者能學(xué)到姿勢(shì),就點(diǎn)個(gè)贊把,哈哈.
5.大部分的邏輯都在Item中,由于拆分開(kāi)了,會(huì)發(fā)現(xiàn)每個(gè)item的代碼也不會(huì)很多
6.多級(jí)列表我已經(jīng)用在某個(gè)項(xiàng)目里了(- -),還沒(méi)發(fā)現(xiàn)什么問(wèn)題(多級(jí)列表的簡(jiǎn)單使用- -)
下面附上Demo.詳細(xì)代碼
github傳送門:TreeRecyclerView
本地下載:http://xiazai.jb51.net/201705/yuanma/TreeRecyclerView(jb51.net).rar
好了,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
利用Jetpack Compose實(shí)現(xiàn)主題切換功能
這篇文章主要介紹了如何利用Android中的Jetpack Compose實(shí)現(xiàn)主題切換功能,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)有一定幫助,需要的可以參考一下2022-01-01
Android RecyclerView添加FootView和HeadView
這篇文章主要介紹了Android RecyclerView添加FootView和HeadView的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10
android 實(shí)現(xiàn)APP中改變頭像圖片的實(shí)例代碼
這篇文章主要介紹了android 實(shí)現(xiàn)APP中改變頭像圖片的實(shí)例代碼,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-07-07
Android自定義View——扇形統(tǒng)計(jì)圖的實(shí)現(xiàn)代碼
本篇文章主要介紹了Android自定義View——扇形統(tǒng)計(jì)圖的實(shí)現(xiàn)代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-02-02
Android studio實(shí)現(xiàn)菜單操作
這篇文章主要為大家詳細(xì)介紹了Android studio實(shí)現(xiàn)菜單操作,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-10-10
詳解Android應(yīng)用中屏幕尺寸的獲取及dp和px值的轉(zhuǎn)換
這篇文章主要介紹了Android應(yīng)用中屏幕尺寸的獲取及dp和px值的轉(zhuǎn)換方法,這里主要介紹將dp轉(zhuǎn)化為px值的例子,需要的朋友可以參考下2016-03-03
Android靜態(tài)變量的生命周期 簡(jiǎn)單介紹
Android靜態(tài)變量的生命周期 簡(jiǎn)單介紹,需要的朋友可以參考一下2013-06-06
Android實(shí)現(xiàn)圖片壓縮(bitmap的六種壓縮方式)
Android中圖片是以bitmap形式存在的,這篇文章主要介紹了Android實(shí)現(xiàn)圖片壓縮(bitmap的六種壓縮方式),有興趣的可以了解一下。2017-02-02
Android編程之文件讀寫操作與技巧總結(jié)【經(jīng)典收藏】
這篇文章主要介紹了Android編程之文件讀寫操作與技巧,結(jié)合實(shí)例形式總結(jié)分析了Android常見(jiàn)的文件與目錄的讀寫操作,及相關(guān)函數(shù)的使用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2016-06-06

