Android中RecyclerView實(shí)現(xiàn)多級(jí)折疊列表效果(TreeRecyclerView)
先看看效果:
兩級(jí)的效果:
效果:
全部展開的效果(我只寫了五級(jí))
說說為什么寫這貨吧:
公司產(chǎn)品提出這個(gè)需求后,我就在網(wǎng)上找啊找.
找的第一個(gè),發(fā)現(xiàn)實(shí)現(xiàn)其實(shí)是ExpandListview嵌套.
找的第二個(gè),ExpandRecyclview,然后就用唄,發(fā)現(xiàn)展開很卡,看源碼,
發(fā)現(xiàn)是RecyclerView套R(shí)ecyclerView
就沒有不嵌套的么.....
然后找到hongyang的那個(gè)博客,寫個(gè)試試吧.
說說思路:
1.Treeadapter應(yīng)該只需要關(guān)心List<TreeAdapterItem> datas 的內(nèi)容
2.把每個(gè)item看成個(gè)體,布局樣式,每行所占比,bindViewHolder都由自己的來決定。
3.每一個(gè)item應(yīng)該只關(guān)心自己的數(shù)據(jù)和自己的下一級(jí)的數(shù)據(jù),不會(huì)去關(guān)心上上級(jí),下下級(jí)
4.展開的實(shí)現(xiàn),item把子數(shù)據(jù)集拿出來,然后添加到List<TreeAdapterItem> datas,變成與自己同級(jí),因?yàn)槊看握归_只會(huì)展開一級(jí)數(shù)據(jù)。
5.折疊遞歸遍歷所有子數(shù)據(jù),遞歸拿到自己所有的子數(shù)據(jù)集(可以理解因?yàn)橐粋€(gè)文件夾下所有的文件,包括子文件夾下的所有),然后從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ù)的兩級(jí),都設(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; /** * 存儲(chǔ)所有可見的Node */ protected List<T> mDatas;//處理后的展示數(shù)據(jù) /** * 點(diǎn)擊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的點(diǎn)擊事件 展開或關(guān)閉 * 重要 * @param position 觸發(fā)的條目 */ public void expandOrCollapse(int position) { //獲取當(dāng)前點(diǎn)擊的條目 TreeAdapterItem treeAdapterItem = mDatas.get(position); //判斷點(diǎn)擊的條目有沒有下一級(jí) if (!treeAdapterItem.isParent()) { return; } //判斷是否展開 boolean expand = treeAdapterItem.isExpand(); if (expand) { //獲取所有的子數(shù)據(jù). List allChilds = treeAdapterItem.getAllChilds(); mDatas.removeAll(allChilds); //告訴item,折疊 treeAdapterItem.onCollapse(); } else { //獲取下一級(jí)的數(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,因?yàn)镚ridLayoutManager才能設(shè)置每個(gè)條目的行占比. 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ǎn)擊回調(diào).一般不是最后一級(jí),不需要處理吧. 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ù)用的是,一個(gè)城市列表數(shù)據(jù)。 @Override protected List<TreeAdapterItem> initChildsList(CityBean data) {//這個(gè)CityBean 是一級(jí)數(shù)據(jù) ArrayList<TreeAdapterItem> oneChilds= new ArrayList<>(); List<CityBean.CitysBean> citys = data.getCitys(); if (citys == null) {//如果沒有二級(jí)數(shù)據(jù)就直接返回. return null; } for (int i = 0; i < citys.size(); i++) {//遍歷二級(jí)數(shù)據(jù). TwoItem twoItem = new TwoItem(citys.get(i));//創(chuàng)建二級(jí)條目。 oneChilds.add(twoItem); } return oneChilds; } @Override protected int initLayoutId() {//當(dāng)前級(jí)數(shù)的布局 return R.layout.itme_one; } @Override public void onExpand() { super.onExpand(); } @Override public void onBindViewHolder(ViewHolder holder) { //設(shè)置當(dāng)前級(jí)數(shù)的viewhodler. //如果需要某個(gè)view展開關(guān)閉時(shí)的動(dòng)畫,可以在這里保存view到成員變量。 //然后在onExpand()方法里面操作。 holder.setText(R.id.tv_content, data.getProvinceName()); } }
如果是同一級(jí)想要設(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("我是五級(jí)"); //在遍歷的時(shí)候,通過條件,重設(shè)孩子的布局id.和所占比 if (i % 4 == 0) {//偷個(gè)懶,不多寫布局了. 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, "我是第一種五級(jí)"); } else if (layoutId == R.layout.item_five) { holder.setText(R.id.tv_content, "我是第二種五級(jí)"); }else if (layoutId == R.layout.item_two) { holder.setText(R.id.tv_content, "我是第三種五級(jí)"); } } }
更新及詳解:
更深入的介紹可以查看這篇文章://www.dbjr.com.cn/article/113516.htm
下面附上Demo下載地址:
github傳送門:TreeRecyclerView
本地下載:http://xiazai.jb51.net/201705/yuanma/TreeRecyclerView(jb51.net).rar
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來一定的幫助,如果有疑問大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
C/C++在Java、Android和Objective-C三大平臺(tái)下實(shí)現(xiàn)混合編程
本文主要介紹C/C++在Java、Android和Objective-C三大平臺(tái)下實(shí)現(xiàn)混合編程,這里舉例說明實(shí)現(xiàn)不同平臺(tái)用C/C++實(shí)現(xiàn)編程的方法,有興趣的小伙伴可以參考下2016-08-08Flutter 包管理器和資源管理使用學(xué)習(xí)
這篇文章主要為大家介紹了Flutter 包管理器和資源管理使用學(xué)習(xí),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12Android嚴(yán)苛模式StrictMode使用詳解
StrictMode類是Android 2.3 (API 9)引入的一個(gè)工具類,可以用來幫助開發(fā)者發(fā)現(xiàn)代碼中的一些不規(guī)范的問題,以達(dá)到提升應(yīng)用響應(yīng)能力的目的2018-01-01Flutter開發(fā)實(shí)現(xiàn)底部留言板
這篇文章主要為大家詳細(xì)介紹了Flutter開發(fā)實(shí)現(xiàn)底部留言板,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03Android Studio 升級(jí)到3.0后輸入法中文狀態(tài)下無法選詞的終極解決方案
這篇文章主要介紹了 AndroidStudio 升級(jí)到3.0后輸入法中文狀態(tài)下無法選詞的解決方案,需要的朋友可以參考下2017-11-11Android開發(fā)實(shí)現(xiàn)的Log統(tǒng)一管理類
這篇文章主要介紹了Android開發(fā)實(shí)現(xiàn)的Log統(tǒng)一管理類,涉及Android日志管理及方法重載等相關(guān)操作技巧,需要的朋友可以參考下2017-12-12Android?Flutter控件封裝之視頻進(jìn)度條的實(shí)現(xiàn)
這篇文章主要來和大家分享一個(gè)很簡單的控制器封裝案例,包含了基本的播放暫停,全屏和退出全屏,文中的示例代碼講解詳細(xì),感興趣的可以了解一下2023-06-06