2019独角兽企业重金招聘Python工程师标准>>>

version:2.8.5

更多分享请看:http://cherylgood.cn

今天我们来学习BaseRecyclerViewAdapterHelper中有关实现可展开和折叠二级Item或多级Item的源码。在开始学习之前,我想先分析下实现的思路,这样对于进行源码的理解效果比较好。

实现伸展and折叠,很多控件都有,网上也有用linearlayout实现的功能很强大、很炫酷的开源项目,平时要实现一些伸缩性的自定义控件,我们也可以是用属性动画,或者动态控制控件的Layout属性等都可以实现。那么现在我们来想象一下,如果在recyclerview中实现该功能,相对来说能想到的比较合适的方式是什么呢?

其实我们可以很好的利用RecyclerView.Adapter给我们提供的如下一些通知数据源更新的方法来实现我们的动态伸展and折叠功能。当要伸展时,我们动态将下一级item的数据添加在与adapter绑定的数据集合中,然后通知layoutManger更新数据源。当要收缩时,同理,将下一级的item的数据源从与adapter绑定的数据集合中移除,然后通知更新。

* @see #notifyItemChanged(int)* @see #notifyItemInserted(int)* @see #notifyItemRemoved(int)* @see #notifyItemRangeChanged(int, int)* @see #notifyItemRangeInserted(int, int)* @see #notifyItemRangeRemoved(int, int)

思路:

  1. 数据bean应该有存储自己数据的字段
  2. 数据bean应该有存储下一级item列表的集合类型的字段
  3. 数据bean应该有一个字段标识当前item的状态(伸展or收缩)
  4. 初始化adapter时只渲染顶级的item
  5. 点击item是检测该item是否支持伸缩
  6. 支持伸缩:当前状态展开->折叠(将次级list插入adapter绑定的data集合中,刷新数据);当前状态折叠->展开(将次级的list从与adapter绑定的data集合中移除,刷新数据)
  7. 插入或移除的位置根据点击的item确定,插入量与移除量根据下一级item数量确定
  8. 插入移除过程中可以使用动画效果

思路理清之后我们接下来开始学习源代码:

实现Expandable And collapse 效果我们仍然是使用BaseMultiItemQuickAdapter实现即可

然后我们需要先看两个相关的类:IExpandable接口;AbstractExpandableItem: 对数据bean的再次封装,某个bean如果有次级的list 可以实现该抽象类。

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 里面定义了四个接口方法:

  1. isExpanded判断当前的bean是否已展开
  2. setExoanded更新bean的当前状态
  3. getSubItems返回下一级的数据集合
  4. getLevel 返回当前item属于第几个层级, 第一级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;@Overridepublic boolean isExpanded() {return mExpandable;}@Overridepublic void setExpanded(boolean expanded) {mExpandable = expanded;}@Overridepublic 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;}
}

字段方法解析:

  1. mExpandable 保存当前的状态值,默认为false
  2. mSubItems 存储数据bean集合

里面还包装了一些常用的方法,这里就不一一解析了。

接下来我们以一个使用demo的实现来进行分析:

我们可以看群主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;}

这段代码的作用是生成一个支持Expandable and collapse 的数据集合,创建一个0级的LevelOItem 然后将下一级的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;}@Overridepublic int getItemType() {return ExpandableItemAdapter.TYPE_LEVEL_0;}@Overridepublic int getLevel() {return 0;}
}

可以看到Level0Item继承了AbstractExpandableItem 并实现MultiItemEntity接口。里面根据实际需求定义相应的字段即可。

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;}@Overridepublic int getItemType() {return ExpandableItemAdapter.TYPE_LEVEL_1;}@Overridepublic int getLevel() {return 1;}
}

当如过某一级的item没有下一级的list时,就不需要在实现AbstractExpandableItem了

然后我们的切入点时adapter,因为默认是折叠状态,当我们点击具备展开折叠能力的item时才会触发该功能,所以逻辑的控制是在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<MultiItemEntity, BaseViewHolder> {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);}@Overrideprotected 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() {@Overridepublic 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() {@Overridepublic 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个level的布局资源文件。重点在convert回调方法;

  1. 最外层进行viewholder 的类型判断进行数据绑定
  2. 添加点击事件的监听
  3. 当被点击时,判断当前的levelitem是不是展开的或折叠的,然后根据你的需要调用collapse或者expand进行折叠或展开操作。

重点来的,最终实现展开、折叠功能其实是依赖collapse和expand这些api;那我们来看下这些api到底内部是怎么实现的,我们从expand开始。代码中expand(pos);传了一个pos进来,而这个pos就是被点击的item在adapter数据集合中的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是一个方法多态,提供了三种参数类型的调用。支持是否需要动画,是否更新数据源。

排除headerview的干扰,获得实际的位置position

position -= getHeaderLayoutCount();

判断其是否支持展开折叠,是否有下一级items需要展开,没有就直接返回0

IExpandable expandable = getExpandableItem(position);if (expandable == null) {return 0;}if (!hasSubItems(expandable)) {expandable.setExpanded(false);return 0;}

下面代码作用:如果处于折叠状态且需要展开,则执行到下面代码,通过getSubItems获得要展开的list,将其添加到mdata中,通过recursiveExpand获得要展开的items的数量

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的源码如下:下面是一个递归调用,一直遍历到最后一层不支持展开折叠的item才会回溯回来,遍历过程中可以看到一个判断,if(item.isExpanded) 就是如果下一级的items原来已经是处于展开状态的,此时我们也需要展开他。最终返回的是所需展开的items的数量。

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;}

获得需要展开的items的数量值,也将数据集合添加到了mData中,此时我们通知layoutManager刷新数据即可

int parentPos = position + getHeaderLayoutCount();if (shouldNotify) {if (animate) {notifyItemChanged(parentPos);notifyItemRangeInserted(parentPos + 1, subItemCount);} else {notifyDataSetChanged();}}

刷新的时候我们要先确定开始刷新位置,所以需要加上headerview的数量

然后调用如上代码即可。折叠是反向进行的,根据这个思路看就可以了。

总结:折叠->展开:mData添加需展开的数据集,更新数据源;展开->折叠:mData移除需折叠的数据集,更新数据源。

后面会继续分析其他功能的实现源码,欢迎一起学习!

转载于:https://my.oschina.net/zaizaiangels/blog/865365

BaseRecyclerViewAdapterHelper开源项目之BaseSectionQuickAdapter 实现Expandable And collapse效果的源码学习...相关推荐

  1. 【机器人学】机器人开源项目KDL源码学习:(5)KDL如何求解几何雅克比矩阵

    这篇文章试图说清楚两件事:1. 几何雅克比矩阵的本质:2. KDL如何求解机械臂的几何雅克比矩阵. 一.几何雅克比矩阵的本质 机械臂的关节空间的速度可以映射到执行器末端在操作空间的速度,这种映射可以通 ...

  2. 小剧场短剧影视小程序源码 全开源 带支付收益等模式 付费短剧小程序源码

    简介: 小剧场短剧影视小程序源码 全开源 带支付收益等模式 付费短剧小程序源码 多平台小程序支持 项目功能介绍 支持无限滑动 高性能滑动 预加载 视频预览 支持剧情介绍,集合壁纸另外仿抖音滑动效果 支 ...

  3. java计算机毕业设计科技项目在线评审系统MyBatis+系统+LW文档+源码+调试部署

    java计算机毕业设计科技项目在线评审系统MyBatis+系统+LW文档+源码+调试部署 java计算机毕业设计科技项目在线评审系统MyBatis+系统+LW文档+源码+调试部署 本源码技术栈: 项目 ...

  4. Java毕设项目菜鸟驿站快递分发系统计算机(附源码+系统+数据库+LW)

    Java毕设项目菜鸟驿站快递分发系统计算机(附源码+系统+数据库+LW) 项目运行 环境配置: Jdk1.8 + Tomcat8.5 + Mysql + HBuilderX(Webstorm也行)+ ...

  5. Java毕设项目校园外卖系统Web端计算机(附源码+系统+数据库+LW)

    Java毕设项目校园外卖系统Web端计算机(附源码+系统+数据库+LW) 项目运行 环境配置: Jdk1.8 + Tomcat8.5 + Mysql + HBuilderX(Webstorm也行)+ ...

  6. Java毕设项目餐厅线上点菜系统计算机(附源码+系统+数据库+LW)

    Java毕设项目餐厅线上点菜系统计算机(附源码+系统+数据库+LW) 项目运行 环境配置: Jdk1.8 + Tomcat8.5 + Mysql + HBuilderX(Webstorm也行)+ Ec ...

  7. Java毕设项目东理咨询交流论坛计算机(附源码+系统+数据库+LW)

    Java毕设项目东理咨询交流论坛计算机(附源码+系统+数据库+LW) 项目运行 环境配置: Jdk1.8 + Tomcat8.5 + Mysql + HBuilderX(Webstorm也行)+ Ec ...

  8. 蚂蚁金服开源-SofaRpc源码学习篇

    大家好,我叫大鸡腿,大家可以关注下我,会持续更新技术文章还有人生感悟,感谢~ 文章目录 官网 基本流程 SofaRpc学习 代码入手 服务端-发布过程 服务端-构造执行链 服务端-注册到注册中心 客户 ...

  9. 开源中国源码学习(一)——简介

    前段时间学习了git-osc客户端的源码,感觉收获不少.尽管,代码并未完全吃透,但是,还是尝到了学习源码的甜头. git-osc客户端源码的第一期学习,暂告一段落. git-osc 客户端源码 git ...

  10. 开源中国源码学习UI篇(一)之FragmentTabHost的使用分析

    最近在有意读开源中国的源码来提升Android开发能力,开通博客来提升一下自己的积极性- -我参考的是开源中国2.2版,完整源码地址为http://git.oschina.net/oschina/an ...

最新文章

  1. Windows 8 Metro App学习笔记(九)—磁砖
  2. [MFC] MFC编译程序,缺少MFC动态链接库的解决
  3. Java基础day14
  4. vld不输出泄漏信息_光纤天线方案和泄漏电缆方案的比较
  5. memset详解 设置无穷大INF
  6. avs3 ts格式封装 标准_Go time.RFC3339 时间格式化
  7. Cognos 10.2.1安装
  8. 手势密码解锁微信小程序项目源码
  9. 时区是怎么划分的?世界各时区的时间如何统一表达?GMT、UTC、UNIX有什么区别?
  10. QQ正式推出“超级QQ秀”布局元宇宙
  11. python爬虫获取下一页_Python爬虫怎么获取下一页的URL和网页内容?
  12. 华为云注册登录之图像标签识别
  13. 交换机路由器常用命令大全
  14. Scheduled定时任务
  15. 高电平和低电平之间的电平芯片会识别成什么
  16. ad软件one pin错误是啥意思_AD19的错误提示大总结解释
  17. 经纬度手动简单调整整位置
  18. Statement和PreparedStatement的区别/PreparedStatement和Statement比较的优点
  19. 【video frame interpolation系列1】背景知识: forward and backward image warping (图像扭曲/变换)
  20. 入门python爬虫

热门文章

  1. 人类为什么不会被人工智能取代?
  2. SQL如何本地数据库连接服务器的数据库
  3. python之小坑:IndentationError: expected an indented block
  4. pandas38 df.assign添加修改列( tcy)
  5. 杰奇2.4珊瑚模板简繁转换出现乱码怎么解决
  6. 教你快速记住公司工作邮箱格式,再也不用百度“邮箱格式怎么写”啦!
  7. spss变量视图转数据视图_教大家SPSS如何导入数据进行分析
  8. linux unip命令
  9. python中def demo是什么意思_python中def是什么意思
  10. Linux终端默认配色方案