ListView与RecyclerView

·  阅读 854

构成

  • ListView
    • View+Adapter
  • RecyclerView
    • View+Adapter+LayoutManager

ListView与RecyclerView的区别

  • RecyclerView的测量和布局工作都交由LayoutManager负责
    • rcy1.png
    • 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类型
  • RecyclerView负责缓存管理的对象是Recycler,对应ListView中的Recyclerbin,四级缓存如下
        final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
        ArrayList<ViewHolder> mChangedScrap = null;
    
        final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
        RecycledViewPool mRecyclerPool;
    复制代码

RecyclerView简单运行机制

rcy2.png

  • LayoutManager只会向Recycler索要view
  • onCreaterViewHolder是由Recycler调用,Adapter将ViewHolder返回

从Listview到RecyclerView

rcy3.png

  • 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都会添加到缓存中,绑定的时候重新取出来
      • 如图 rcy4.png
    • 滑动的情况
      • 一旦view滑出view,就会被会受到缓存中
      • 一旦滑入屏幕,就会从缓存中拿
      • 如果缓存中没有
      • 如图 rcy5.png
  • ListView复用逻辑总结
    • rcy6.png
    • 先从mActiveViews中取
    • 如果没有,从mScrapViews中取
    • 如果还是没有,需要创建新的view返回使用

RecyclerView中的四级缓存

  • mCachedViews
    • 主要是优化滑动时的性能,默认情况下只能存放两个回收的viewHolder,可以通过setItemViewCacheSize自定义容积
    • rcy7.png
    • 向上滑动屏幕,当上面的item被移出屏幕时,如position0和position1会被存入mCachedViews
    • 向下滑动屏幕,position1重新出现在屏幕范围内,会从mCachedViews中找有没有缓存position为1的view,存在就直接复用
    • mCachedViews的缓存不会按照viewType进行分类,也就是说不同类型的viewHolder都可以存放在mCachedViews中,也就是说上面的滑入滑出操作不会执行onBindViewHolder
    • 由于刚才的下拉操作,position6被移除屏幕,这时候它会被存在mCachedViews中
      • rcy8.png
  • 回收池mRecyclerPool
    • 在上面的操作中,如果position2被移出屏幕,那么它会加入到mCachedViews中,而position0会被挤出去,加到mRecyclerPool回收池中
      • rcy9.png
    • 数据结构:
      • 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会从回收池中取出
      • rcy10.png
    • mRecyclerPool相对于ListView中mScrapViews的优化
      • 支持多个RecyclerView共用同一个回收池
      • 可以单独根据viewType设置缓存容量,针对性优化
      • viewType的值由于不再是数组的索引,不需要连续,可以直接写R.layout.xxx作为viewType
  • mAttachedScrap暂存区
    • rcy11.png
    • mAttachedScrap存放
    • RecyclerView获取缓存的顺序,
      • 先从mAttachedScrap暂存区中去取
      • 然后是mCachedViews
      • 然后是mRecyclerViewPool
    • 与mCachedViews不同,作为一个暂时存放viewHodler的区域,只会在一次布局过程中临时保存viewholder,布局开始,RecyclerView把所有的布局都存到mAttachedScrap中,布局结束,即使暂存区中还有viewholder,也会被扔进回收池中
  • mChangedScrap暂存区
    • rcy12.png
    • 数据发生改变的viewHolder会进入mChangedScrap暂存区

核心机制pre-layout/post-layout

当 RecyclerView 支持 pre/post-layout 机制,则可以明确数据发生改变前后所有 ItemView 的位置,这样就可以容易的作出预测性动画。

  • RecyclerView在删除item、插入item、改变item的时候都为我们提供了动画,那么动画是如何实现的呢
  • 预测性动画:notifyItemRemoved动画原理
    • 如图 rcy13.png
    • 以notifyItemRemoved为例,B逐渐消失执行出场动画,C逐渐显示执行入场动画
    • adapter通知B被移除,由此pre-layout就可以通过B被移除的通知判断C要被显示出来,就会在pre-layout时把C摆放起来,一旦被摆放起来,就可以确定动画的起始点
    • 正因为这个机制,RecyclerView做预测性动画就变得容易
  • 局部刷新:notifyItemSetChanged动画原理:
    • notifyItemSetChanged实际上是两个item再做动画,老的item慢慢变得不透明,新的item逐渐变得透明,这似乎有违局部刷新的原理,但事实的确如此,这也是我们经常发现局部刷新图片闪烁的原因所在
    • 之所以这样做,是因为存在这样的一种场景,执行变更的item所有数据都发生了改变,那么整个item都需要改变,而RecyclerView默认使用的就是完全替换的方式
    • rcy14.png
  • 高效的局部刷新:防止局部刷新图片闪烁的两种方式
    • 方式一:可以使用setSupportChangeAnimations(false)来禁止预测性动画,不过这种方式也会使移除动画和插入动画失效
    • 方式二:传入payload -> notifyItemChanged(position,"payload");

RecyclerView 复用策略

  1. Scrap 中尝试获取
    • 如果获取到不需要重新绑定数据,直接使用
  2. Cache 中尝试获取
    • 如果获取到不需要重新绑定数据,直接使用
  3. 自定义缓存中尝试获取 (其实没有找到有意义的应用)
  4. Pool 中尝试获取
    • 除非特殊情况,需要重新绑定数据才能使用
分类:
Android
标签:
收藏成功!
已添加到「」, 点击更改