Android中RecyclerView實現(xiàn)多級折疊列表效果(TreeRecyclerView)
先看看效果:
兩級的效果:

效果:

全部展開的效果(我只寫了五級)

說說為什么寫這貨吧:
公司產(chǎn)品提出這個需求后,我就在網(wǎng)上找啊找.
找的第一個,發(fā)現(xiàn)實現(xiàn)其實是ExpandListview嵌套.
找的第二個,ExpandRecyclview,然后就用唄,發(fā)現(xiàn)展開很卡,看源碼,
發(fā)現(xiàn)是RecyclerView套RecyclerView
就沒有不嵌套的么.....
然后找到hongyang的那個博客,寫個試試吧.
說說思路:
1.Treeadapter應(yīng)該只需要關(guān)心List<TreeAdapterItem> datas 的內(nèi)容
2.把每個item看成個體,布局樣式,每行所占比,bindViewHolder都由自己的來決定。
3.每一個item應(yīng)該只關(guān)心自己的數(shù)據(jù)和自己的下一級的數(shù)據(jù),不會去關(guān)心上上級,下下級
4.展開的實現(xiàn),item把子數(shù)據(jù)集拿出來,然后添加到List<TreeAdapterItem> datas,變成與自己同級,因為每次展開只會展開一級數(shù)據(jù)。
5.折疊遞歸遍歷所有子數(shù)據(jù),遞歸拿到自己所有的子數(shù)據(jù)集(可以理解因為一個文件夾下所有的文件,包括子文件夾下的所有),然后從List<TreeAdapterItem> datas刪除這些數(shù)據(jù)。
見代碼:
/**
* Created by Jlanglang on 2016/12/7.
*
*/
public abstract class TreeAdapterItem<D> {
/**
* 當(dāng)前item的數(shù)據(jù)
*/
protected D data;
/**
* 持有的子數(shù)據(jù)
*/
protected List<TreeAdapterItem> childs;
/**
* 是否展開
*/
protected boolean isExpand;
/**
* 布局資源id
*/
protected int layoutId;
/**
* 在每行中所占的比例
*/
protected int spanSize;
····
get/set方法省略。。。。
····
public TreeAdapterItem(D data) {
this.data = data;
childs = initChildsList(data);
layoutId = initLayoutId();
spanSize = initSpansize();
}
/**
* 展開
*/
public void onExpand() {
isExpand = true;
}
/**
* 折疊
*/
public void onCollapse() {
isExpand = false;
}
/**
* 遞歸遍歷所有的子數(shù)據(jù),包括子數(shù)據(jù)的子數(shù)據(jù)
*
* @return List<TreeAdapterItem>
*/
public List<TreeAdapterItem> getAllChilds() {
ArrayList<TreeAdapterItem> treeAdapterItems = new ArrayList<>();
for (int i = 0; i < childs.size(); i++) {
TreeAdapterItem treeAdapterItem = childs.get(i);
treeAdapterItems.add(treeAdapterItem);
if (treeAdapterItem.isParent()) {
List list = treeAdapterItem.getAllChilds();
if (list != null && list.size() > 0) {
treeAdapterItems.addAll(list);
}
}
}
return treeAdapterItems;
}
/**
* 是否持有子數(shù)據(jù)
*
* @return
*/
public boolean isParent() {
return childs != null && childs.size() > 0;
}
/**
* item在每行中的spansize
* 默認(rèn)為0,如果為0則占滿一行
* 不建議連續(xù)的兩級,都設(shè)置該數(shù)值
*
* @return 所占值
*/
public int initSpansize() {
return spanSize;
}
/**
* 初始化子數(shù)據(jù)
*
* @param data
* @return
*/
protected abstract List<TreeAdapterItem> initChildsList(D data);
/**
* 該條目的布局id
*
* @return 布局id
*/
protected abstract int initLayoutId();
/**
* 抽象holder的綁定
*
* @param holder ViewHolder
*/
public abstract void onBindViewHolder(ViewHolder holder);
}再來看看Adapter
public class TreeRecyclerViewAdapter<T extends TreeAdapterItem> extends RecyclerView.Adapter<ViewHolder> {
protected Context mContext;
/**
* 存儲所有可見的Node
*/
protected List<T> mDatas;//處理后的展示數(shù)據(jù)
/**
* 點擊item的回調(diào)接口
*/
private OnTreeItemClickListener onTreeItemClickListener;
public void setOnTreeItemClickListener(OnTreeItemClickListener onTreeItemClickListener) {
this.onTreeItemClickListener = onTreeItemClickListener;
}
/**
*
* @param context 上下文
* @param datas 條目數(shù)據(jù)
*/
public TreeRecyclerViewAdapter(Context context, List<T> datas) {
mContext = context;
mDatas = datas;
}
/**
* 相應(yīng)RecyclerView的點擊事件 展開或關(guān)閉
* 重要
* @param position 觸發(fā)的條目
*/
public void expandOrCollapse(int position) {
//獲取當(dāng)前點擊的條目
TreeAdapterItem treeAdapterItem = mDatas.get(position);
//判斷點擊的條目有沒有下一級
if (!treeAdapterItem.isParent()) {
return;
}
//判斷是否展開
boolean expand = treeAdapterItem.isExpand();
if (expand) {
//獲取所有的子數(shù)據(jù).
List allChilds = treeAdapterItem.getAllChilds();
mDatas.removeAll(allChilds);
//告訴item,折疊
treeAdapterItem.onCollapse();
} else {
//獲取下一級的數(shù)據(jù)
mDatas.addAll(position + 1, treeAdapterItem.getChilds());
//告訴item,展開
treeAdapterItem.onExpand();
}
notifyDataSetChanged();
}
//adapter綁定Recycleview后.
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
//拿到布局管理器
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
//判斷是否是GridLayoutManager,因為GridLayoutManager才能設(shè)置每個條目的行占比.
if (layoutManager instanceof GridLayoutManager) {
final GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
TreeAdapterItem treeAdapterItem = mDatas.get(position);
if (treeAdapterItem.getSpanSize() == 0) {
//如果是默認(rèn)的大小,則占一行
return gridLayoutManager.getSpanCount();
}
//根據(jù)item的SpanSize來決定所占大小
return treeAdapterItem.getSpanSize();
}
});
}
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//這里,直接通過item設(shè)置的id來創(chuàng)建Viewholder
return ViewHolder.createViewHolder(mContext, parent, viewType);
}
@Override
public void onBindViewHolder(ViewHolder holder, final int position) {
final TreeAdapterItem treeAdapterItem = mDatas.get(position);
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//折疊或展開
expandOrCollapse(position);
if (onTreeItemClickListener != null) {
//點擊回調(diào).一般不是最后一級,不需要處理吧.
onTreeItemClickListener.onClick(treeAdapterItem, position);
}
}
});
treeAdapterItem.onBindViewHolder(holder);
}
@Override
public int getItemViewType(int position) {
//返回item的layoutId
return mDatas.get(position).getLayoutId();
}
@Override
public int getItemCount() {
return mDatas == null ? 0 : mDatas.size();
}
public interface OnTreeItemClickListener {
void onClick(TreeAdapterItem node, int position);
}
}具體使用:
/**
* Created by baozi on 2016/12/8.
*/
public class OneItem extends TreeAdapterItem<CityBean> {
public OneItem(CityBean data) {
super(data);
}
//這里數(shù)據(jù)用的是,一個城市列表數(shù)據(jù)。
@Override
protected List<TreeAdapterItem> initChildsList(CityBean data) {//這個CityBean 是一級數(shù)據(jù)
ArrayList<TreeAdapterItem> oneChilds= new ArrayList<>();
List<CityBean.CitysBean> citys = data.getCitys();
if (citys == null) {//如果沒有二級數(shù)據(jù)就直接返回.
return null;
}
for (int i = 0; i < citys.size(); i++) {//遍歷二級數(shù)據(jù).
TwoItem twoItem = new TwoItem(citys.get(i));//創(chuàng)建二級條目。
oneChilds.add(twoItem);
}
return oneChilds;
}
@Override
protected int initLayoutId() {//當(dāng)前級數(shù)的布局
return R.layout.itme_one;
}
@Override
public void onExpand() {
super.onExpand();
}
@Override
public void onBindViewHolder(ViewHolder holder) {
//設(shè)置當(dāng)前級數(shù)的viewhodler.
//如果需要某個view展開關(guān)閉時的動畫,可以在這里保存view到成員變量。
//然后在onExpand()方法里面操作。
holder.setText(R.id.tv_content, data.getProvinceName());
}
}如果是同一級想要設(shè)置不同的布局,接著看
/**
* Created by baozi on 2016/12/8.
*/
public class FourItem extends TreeAdapterItem<String> {
....
@Override
protected List<TreeAdapterItem> initChildsList(String data) {
ArrayList<TreeAdapterItem> treeAdapterItems = new ArrayList<>();
for (int i = 0; i < 10; i++) {
FiveItem threeItem = new FiveItem("我是五級");
//在遍歷的時候,通過條件,重設(shè)孩子的布局id.和所占比
if (i % 4 == 0) {//偷個懶,不多寫布局了.
threeItem.setLayoutId(R.layout.itme_one);
threeItem.setSpanSize(0);
} else if (i % 3 == 0) {
threeItem.setLayoutId(R.layout.item_two);
threeItem.setSpanSize(2);
}
treeAdapterItems.add(threeItem);
}
return treeAdapterItems;
}
....
}
/**
* Created by baozi on 2016/12/8.
*/
public class FiveItem extends TreeAdapterItem<String> {
.......
//設(shè)置默認(rèn)的布局
@Override
protected int initLayoutId() {
return R.layout.item_five;
}
//設(shè)置默認(rèn)的占比
@Override
public int initSpansize() {
return 2;
}
//根據(jù)layoutId來判斷viewhodler并設(shè)置
@Override
public void onBindViewHolder(ViewHolder holder) {
if (layoutId == R.layout.itme_one) {
holder.setText(R.id.tv_content, "我是第一種五級");
} else if (layoutId == R.layout.item_five) {
holder.setText(R.id.tv_content, "我是第二種五級");
}else if (layoutId == R.layout.item_two) {
holder.setText(R.id.tv_content, "我是第三種五級");
}
}
}更新及詳解:
更深入的介紹可以查看這篇文章://www.dhdzp.com/article/113516.htm
下面附上Demo下載地址:
github傳送門:TreeRecyclerView
本地下載:http://xiazai.jb51.net/201705/yuanma/TreeRecyclerView(jb51.net).rar
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作能帶來一定的幫助,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
相關(guān)文章
Android studio 出現(xiàn) Unsupported major.minor version 52.0解決辦法
這篇文章主要介紹了Android studio 出現(xiàn) Unsupported major.minor version 52.0解決辦法的相關(guān)資料,需要的朋友可以參考下2016-12-12
Android利用WindowManager實現(xiàn)懸浮窗
這篇文章主要為大家詳細(xì)介紹了Android利用WindowManager實現(xiàn)懸浮窗效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-07-07
Android下拉列表(Spinner)效果(使用C#和Java分別實現(xiàn))
這篇文章主要介紹了Android下拉列表(Spinner)效果(使用C#和Java分別實現(xiàn)),本文直接給出效果圖和兩種語言的實現(xiàn)代碼及布局代碼,需要的朋友可以參考下2015-06-06
Android Bitmap的截取及狀態(tài)欄的隱藏和顯示功能
Bitmap是Android系統(tǒng)中的圖像處理中最重要類之一。Bitmap可以獲取圖像文件信息,對圖像進(jìn)行剪切、旋轉(zhuǎn)、縮放,壓縮等操作,并可以以指定格式保存圖像文件。這篇文章主要介紹了Android Bitmap的截取及狀態(tài)欄的隱藏和顯示功能,需要的朋友可以參考下2017-11-11
android使用SkinManager實現(xiàn)換膚功能的示例
本篇文章主要介紹了android使用SkinManager實現(xiàn)換膚功能的示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-02-02
Android Studio 3.1.X中導(dǎo)入項目的正確方法分享
這篇文章主要給大家介紹了關(guān)于Android Studio 3.1.X中導(dǎo)入項目的正確方法,文中一步步將解決的方法以及可能遇到的問題介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-07-07

