构成
- ListView
- View+Adapter
- RecyclerView
- View+Adapter+LayoutManager
ListView与RecyclerView的区别
- RecyclerView的测量和布局工作都交由LayoutManager负责
- RecyclerView 负责触摸反馈管理,布局控件有专门的 LayoutManager。
- 在回收复用上,RecyclerView复用的是ViewHolder,ListView复用的是View(一般通过手写Holder的方式来减少findviewbyid的重复工作)
- ListView 中有两层缓存,绝大多数情况下只有其中一层生效。
- RecyclerView 中 Scrap、Cache 和 Pool都会频繁的参与复用机制。并且还可以通过 自定义缓存来实现特殊策略的缓存实现(只是没有确切的使用场景和应用)。
- RecycerlView 的 RecycledViewPool 可以让多个 RecyclerView 公用一个 Pool 来极 大的提升复用效率。
回收复用
- ListView负责缓存管理的对象是Recyclerbin,包含两级缓存
- mActiveViews[] 和 ArrayList[] mScrapViews
- mActiveViews可以理解为屏幕可见的views
- mScrapViews可以理解为不在屏幕内的views
- 之所以声明为数组类型的List是因为要考虑viewType,即不同的item类型
- mActiveViews[] 和 ArrayList[] mScrapViews
- RecyclerView负责缓存管理的对象是Recycler,对应ListView中的Recyclerbin,四级缓存如下
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>(); ArrayList<ViewHolder> mChangedScrap = null; final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>(); RecycledViewPool mRecyclerPool;
RecyclerView简单运行机制
- LayoutManager只会向Recycler索要view
- onCreaterViewHolder是由Recycler调用,Adapter将ViewHolder返回
从Listview到RecyclerView
- ListView上做动画特别麻烦,所以这也是RecyclerView被推出的原因之一
- ListView之所以做动画麻烦,是因为不知道具体那几个View发生了改变,从而无法做到局部刷新
- RecyclerView上局部刷新变得简单
ListView复用机制
- mActiveViews[]作用:当ListView数据没有发生改变的时候,ListView又进行了重新布局layout(),将子元素在mActiveViews一存一取,达到快速复用的效果,复用效率比较高,listview根本不需要调用我们重写的getView方法,onCreate和onBind都不需要执行
- mActiveViews的使用率很低,由于历史原因,sdk版本很低的的时候listview会多次layout,这个时候mActiveViews有效提升了效率
- ArrayList[] mScrapViews:notifyDataSetChanged以及滑动时出场和进场都会和mScrapViews进行交互
- 屏幕内显示的情况
- 数组中每一个list对应一组viewType中view的集合
- 每一个view都会添加到缓存中,绑定的时候重新取出来
- 如图
- 滑动的情况
- 一旦view滑出view,就会被会受到缓存中
- 一旦滑入屏幕,就会从缓存中拿
- 如果缓存中没有
- 如图
- 屏幕内显示的情况
- ListView复用逻辑总结
- 先从mActiveViews中取
- 如果没有,从mScrapViews中取
- 如果还是没有,需要创建新的view返回使用
RecyclerView中的四级缓存
- mCachedViews
- 主要是优化滑动时的性能,默认情况下只能存放两个回收的viewHolder,可以通过setItemViewCacheSize自定义容积
- 向上滑动屏幕,当上面的item被移出屏幕时,如position0和position1会被存入mCachedViews
- 向下滑动屏幕,position1重新出现在屏幕范围内,会从mCachedViews中找有没有缓存position为1的view,存在就直接复用
- mCachedViews的缓存不会按照viewType进行分类,也就是说不同类型的viewHolder都可以存放在mCachedViews中,也就是说上面的滑入滑出操作不会执行onBindViewHolder
- 由于刚才的下拉操作,position6被移除屏幕,这时候它会被存在mCachedViews中
- 回收池mRecyclerPool
- 在上面的操作中,如果position2被移出屏幕,那么它会加入到mCachedViews中,而position0会被挤出去,加到mRecyclerPool回收池中
- 数据结构:
- mRecyclerPool中的成员变量mScrap
- DEFAULT_MAX_SCRAP表示每种viewType默认最多只能缓存5个view,可以通过setMaxRecycledViews指定最大值
private static final int DEFAULT_MAX_SCRAP = 5; static class ScrapData { ArrayList<ViewHolder> mScrapHeap = new ArrayList<>(); int mMaxScrap = DEFAULT_MAX_SCRAP; long mCreateRunningAverageNs = 0; long mBindRunningAverageNs = 0; } SparseArray<ScrapData> mScrap = new SparseArray<>(); - 继续滑动,position1和position2重新回到屏幕,会从mCachedViews中去取,此时position6和position7滑出屏幕,进入mCachedViews,继续向下滑动,position0会从回收池中取出
- mRecyclerPool相对于ListView中mScrapViews的优化
- 支持多个RecyclerView共用同一个回收池
- 可以单独根据viewType设置缓存容量,针对性优化
- viewType的值由于不再是数组的索引,不需要连续,可以直接写R.layout.xxx作为viewType
- 在上面的操作中,如果position2被移出屏幕,那么它会加入到mCachedViews中,而position0会被挤出去,加到mRecyclerPool回收池中
- mAttachedScrap暂存区
- mAttachedScrap存放
- RecyclerView获取缓存的顺序,
- 先从mAttachedScrap暂存区中去取
- 然后是mCachedViews
- 然后是mRecyclerViewPool
- 与mCachedViews不同,作为一个暂时存放viewHodler的区域,只会在一次布局过程中临时保存viewholder,布局开始,RecyclerView把所有的布局都存到mAttachedScrap中,布局结束,即使暂存区中还有viewholder,也会被扔进回收池中
- mChangedScrap暂存区
- 数据发生改变的viewHolder会进入mChangedScrap暂存区
核心机制pre-layout/post-layout
当 RecyclerView 支持 pre/post-layout 机制,则可以明确数据发生改变前后所有 ItemView 的位置,这样就可以容易的作出预测性动画。
- RecyclerView在删除item、插入item、改变item的时候都为我们提供了动画,那么动画是如何实现的呢
- 预测性动画:notifyItemRemoved动画原理
- 如图
- 以notifyItemRemoved为例,B逐渐消失执行出场动画,C逐渐显示执行入场动画
- adapter通知B被移除,由此pre-layout就可以通过B被移除的通知判断C要被显示出来,就会在pre-layout时把C摆放起来,一旦被摆放起来,就可以确定动画的起始点
- 正因为这个机制,RecyclerView做预测性动画就变得容易
- 如图
- 局部刷新:notifyItemSetChanged动画原理:
- notifyItemSetChanged实际上是两个item再做动画,老的item慢慢变得不透明,新的item逐渐变得透明,这似乎有违局部刷新的原理,但事实的确如此,这也是我们经常发现局部刷新图片闪烁的原因所在
- 之所以这样做,是因为存在这样的一种场景,执行变更的item所有数据都发生了改变,那么整个item都需要改变,而RecyclerView默认使用的就是完全替换的方式
- 高效的局部刷新:防止局部刷新图片闪烁的两种方式
- 方式一:可以使用setSupportChangeAnimations(false)来禁止预测性动画,不过这种方式也会使移除动画和插入动画失效
- 方式二:传入payload ->
notifyItemChanged(position,"payload");
RecyclerView 复用策略
- Scrap 中尝试获取
- 如果获取到不需要重新绑定数据,直接使用
- Cache 中尝试获取
- 如果获取到不需要重新绑定数据,直接使用
- 自定义缓存中尝试获取 (其实没有找到有意义的应用)
- Pool 中尝试获取
- 除非特殊情况,需要重新绑定数据才能使用