Android中RecyclerView实现多级折叠列表效果(二)
前言
在本文开始之前请大家先看一下这篇文章:https://www.yiidian.com/article/113510.htm
上面的这篇文章是之前写的,里面发现有很多不好用地方,也学到些新姿势,改动了许多地方。下面来看看详细的介绍:
要点:
1.可以通过后台控制Item的展示.
2.TreeRecyclerAdapter,可以展开,折叠.多级展示
3.adapter可以使用装饰者模式进行扩展.支持EmptyAdapter.可以添加headview和footview
4.item的样式可以编写文档,type与Class进行对应,实现后台控置,相同Item复用.
思路:(包含第一篇的思路)
1.adapter应该只需要关心List<baseItem> datas 的内容
2.把每个item看成独立的个体. 布局样式,每行所占比,onbindViewHolder由各个item实现。
3.每一个item应该只关心自己的数据和自己的下一级的数据,不会去关心上上级,下下级
4.展开的实现,点击时item把子数据拿出来,然后添加到adapter的datas中,变成同级,因为只会展开自己的下级数据。
5.折叠的实现,拿到下级数据(可以理解因为一个文件夹下文件),然后从adapter的datas中删除这些数据。
6.后台控制可以通过初始化注册的方法,将Item的Class注册.保存到集合里
7.后台返回字段,获取对应class文件,通过Class.newInstance()方法构建实例.
8.将ViewHolder与Adapter写成通用的,不需要再写多个Adatper与ViewHolder,只需要写多个Baseitem.与BaseItamData(JavaBean).
目录介绍
+ 1.Adapter * Wapper------扩展的wapper, * EmptyWapper --------当无数据时显示页面. * HeaderAndFootWapper --------添加头部view和尾部view - BaseRecyclerAdapter --------封装的Adatper基类 - ItemManager --------接口,管理Adatper刷新,增删操作 - TreeRecyclerAdapter ----多级列表,树形结构的adapter - TreeRecyclerViewType ----多级列表的显示样式,枚举 - ViewHolder----封装的通用viewHodler * 2.base BaseItem<D extends BaseItemData> ------item的封装 BaseItemData-----item的数据要求.javabean需要继承该类. * 3.factory ItemConfig ----添加item的class,配置样式 Itemfactory----通过class生成BaseItem的工厂类 * 4.view TreeItem ----树形列表的子item TreeItemGroup ----树形列表的父item TreeParent---TreeItemGroup 实现该接口 TreeSelectItemGroup---可以选中子item的TreeItemGroup. demo:见购物页面
来张丑丑的图:
下面贴出部分代码:
(一).BaseRecyclerAdapter :
/** * 普通BaseRecyclerAdapter,itme无父子关系. * 限定泛型为BaseItem的子类. * 通过BaseItem去处理ViewHolder */ public class BaseRecyclerAdapter<T extends BaseItem> extends RecyclerView.Adapter<ViewHolder> { private List<T> mDatas;//展示数据 private ItemManager<T> mItemManager; private CheckItem mCheckItem; @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { //看源码,这里的parent就是Recyclerview,所以不会为null.可以通过它拿到context return ViewHolder.createViewHolder(parent.getContext(), parent, viewType); } @Override public void onBindViewHolder(ViewHolder holder, int position) { T t = getDatas().get(position); //检查是否绑定了ItemManage,因为item需要通过ItemManage告诉adapter刷新,增删 checkItemManage(t); //具体onBindViewHolder放到item里面去实现 t.onBindViewHolder(holder); //实现点击事件 onBindViewHolderClick(holder); } /** * 实现item的点击事件 * * @param holder 绑定点击事件的ViewHolder */ public void onBindViewHolderClick(final ViewHolder holder) { //判断当前holder是否已经设置了点击事件 if (!holder.itemView.hasOnClickListeners()) { holder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //获得holder的position int layoutPosition = holder.getLayoutPosition(); //检查position是否可以点击 if (getCheckItem().checkPosition(layoutPosition)) { //检查并得到真实的position int itemPosition = getCheckItem().getAfterCheckingPosition(layoutPosition); //拿到对应item,回调. getDatas().get(itemPosition).onClick(); } } }); } } /** * 这里将LayoutId作为type,因为LayoutId不可能相同,个人觉得可以作为item的标志 * @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; } /** * 需要手动setDatas(List<T> datas),否则数据为空 * @param datas */ public void setDatas(List<T> datas) { if (datas != null) { mDatas = datas; getItemManager().notifyDataSetChanged(); } } }
(二).TreeRecyclerAdapter
/** * Created by baozi on 2017/4/20. * 树级结构recycleradapter. * item之间有子父级关系, */ public class TreeRecyclerAdapter<T extends TreeItem> extends BaseRecyclerAdapter<T> { private TreeRecyclerViewType type; /** * 最初的数据.没有经过增删操作. */ private List<T> initialDatas; @Override public void onBindViewHolderClick(final ViewHolder holder) { //判断是否已有点击监听 if (!holder.itemView.hasOnClickListeners()) { holder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //获得holder的LayoutPosition. int layoutPosition = holder.getLayoutPosition(); //判断是否需要点击 if (getCheckItem().checkPosition(layoutPosition)) { int itemPosition = getCheckItem().getAfterCheckingPosition(layoutPosition); //展开,折叠和item点击不应该同时响应事件. if (type != TreeRecyclerViewType.SHOW_ALL) { //展开,折叠 expandOrCollapse(itemPosition); } else { //点击事件 T item = getDatas().get(itemPosition); TreeItemGroup itemParentItem = item.getParentItem(); //判断上一级是否需要拦截这次事件,只处理当前item的上级,不关心上上级如何处理. if (itemParentItem != null && itemParentItem.onInterceptClick(item)) { return; } item.onClick(); } } } }); } } @Override public void setDatas(List<T> datas) { //保存未修改过的List initialDatas = datas; //如果展开显示全部,则需要递归处理添加所以的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); } } /** * 相应RecyclerView的点击事件 展开或关闭某节点 * * @param position 触发的条目 */ 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> { /** * 当前item的数据 */ protected D data; /** * item在每行中的spansize * 默认为0,如果为0则占满一行 * * @return 所占值, 比如recyclerview的列数为6, item需要占一半宽度, 就设置3 */ private int spanSize; /** * 可以通过ItemManager ,操作adatper * @return */ private ItemManager mItemManager; public int getLayoutId() { if (initLayoutId() <= 0) { throw new Resources.NotFoundException("请设置布局Id"); } return initLayoutId(); } /** * 子类需要实现该方法,并返回item的布局id * * @return 返回布局id.如果返回0,则会抛出异常 */ protected abstract int initLayoutId(); /** * 抽象holder的绑定 */ public abstract void onBindViewHolder(ViewHolder viewHolder); /** * 当前条目的点击回调 * 如果不需要点击事件,则可以不复写该方法. */ 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; } /** * 获取当前item的父级 * * @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; /** * 是否展开 */ private boolean isExpand; /** * 能否展开 */ protected boolean isCanChangeExpand = true; /** * 展开 */ @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++) { //下级 BaseItem baseItem = childs.get(i); baseItems.add(baseItem); //判断是否还有下下级,并且处于expand的状态 if (baseItem instanceof TreeItemGroup && ((TreeItemGroup) baseItem).isExpand()) { //调用下级的getAllChilds遍历,相当于递归遍历 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); /** * 是否消费child的click事件 * * @param child 具体click的item * @return 返回true代表消费此次事件,child不会走onclick(),返回false说明不消费此次事件,child依然会走onclick() */ public boolean onInterceptClick(TreeItem child) { return false; }
(六).TreeSelectItemGroup
/** * Created by baozi on 2016/12/22. * 可以选中子item的TreeItemGroup,点击的item会保存起来.可以通过 getSelectItems()获得选中item */ public abstract class TreeSelectItemGroup<D extends BaseItemData> extends TreeItemGroup<D> { /** * 选中的子item.只支持下一级,不支持下下级 */ 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) { //如果已经有选中的,则替换 if (getSelectItems().size() != 0) { getSelectItems().set(0, child); } else { //没有选中的则添加 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 }
具体的使用实例效果:
1.购物页面:
Demo中的代码:
//拿到数据 List<StoreBean> storeBean = initData(); //通过ItemFactory生成第一级Item,如果是通过后台控制,则不需要传Class //List<ShopTitileItem> itemList = ItemFactory.createItemList(storeBean); List<ShopTitileItem> itemList = ItemFactory.createItemList(storeBean, ShopTitileItem.class); //创建TreeRecyclerAdapter mAdapter = new TreeRecyclerAdapter<>(); //设置adapter的显示样式 mAdapter.setType(TreeRecyclerViewType.SHOW_ALL); //将数据设置到Adapter中 mAdapter.setDatas(itemList); //设置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.多级列表
Demo中的代码:
//拿到数据 List<CityBean> cityBeen = initData(); //通过ItemFactory生成List<BaseItem> List<OneTreeItemParent> itemList = ItemFactory.createItemList(cityBeen); TreeRecyclerAdapter treeRecyclerAdapter = new TreeRecyclerAdapter(); //设置数据 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的列表
总结:
1.我觉得像购物车那种页面挺复杂的,既然写了多级列表,何不扩展一个出来
2.RecyclerView的点击事件,看了很多封装,发现很多都是每次onBindViewHolder去重新设置一遍,感觉挺不好的.
3.我喜欢把数据集合让Adatper去持有,然后通过Adapter进行增删改查操作.直接在Activity里持有数据集合进行操作,我不是习惯(- -)
4.用的习惯没bug的才是好东西,如果你觉得实用或者能学到姿势,就点个赞把,哈哈.
5.大部分的逻辑都在Item中,由于拆分开了,会发现每个item的代码也不会很多
6.多级列表我已经用在某个项目里了(- -),还没发现什么问题(多级列表的简单使用- -)
下面附上Demo.详细代码
github传送门:TreeRecyclerView
本地下载:http://xiazai.jb51.net/201705/yuanma/TreeRecyclerView(jb51.net).rar
好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对呐喊教程的支持。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:notice#yiidian.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。