×

RecyclerView 知识梳理(1) - 综述

96
泽毛
2017.04.03 15:11* 字数 3081

一、概述

对于RecyclerView的学习,主要是需要掌握以下几点:

要理解整个RecyclerView的思想,有一个视频是一定要看的:RecyclerView ins and outs - Google I/O 2016。今天,我们就通过这个视频,把上面所学到的东西串联起来。

二、为什么要使用RecyclerView

RecyclerView诞生的目的就是为了替代ListView,我们先总结一下在使用ListView过程当中所遇到的问题:

  • 复用Item需要编写很多的代码
    在使用ListView的时候,有经验的程序员一定会告诉你在getView中要这么写,如果忘了,那么会产生很严重的性能问题。
if (convertView == null) {
     //通过LayoutInflator生成convertView,并产生一个ViewHolder,通过setTag关联起来.       
} else {
    //通过getTag获取ViewHolder,进行更新操作.
}
  • 焦点冲突问题
    Item有焦点时,Item的子控件就无法获取到焦点;而如果子控件抢夺了焦点,那么Item的点击事件又不能响应,这个相信大家都遇到过。
  • 重复的API
    ListView中提供了很多的API,但是这些API又和View的一些API重复了,例如我们可以给ListView设置setOnItemClickListener,也可以在getView中给某个View设置setOnClickListener,这就让人很疑惑,到底应当选用哪个。
  • 动画
    当我们需要在ListView中进行添加、删除、移动等操作的时候,如果希望加上动画,那么是很困难的,根本原因是我们是通过Adapter通知ListView进行更新,然而ListView根本就没法确定到底是哪些View发生了变化。
  • 更加复杂的布局需求
    ListView在布局是规整的列表的时候能满足大多数人的使用,然而如果想要实现像瀑布流这种复杂的布局,并且保证View能够复用,那么需要编写很多的代码。

如果之前有了解过RecyclerView的基本用法,那么你会发现,对于上述这些问题,它都给出了自己的解决方案:

  • 强制使用开发者使用ViewHolder,提供了onCreateViewHolderonBindViewHolder这两个方法,把创建View和绑定View的操作分离开。
  • 把焦点交给系统处理。
  • 去掉了onItemClickListener,以及一些重复的API
  • Adapter中增加了notifyItemChanged()等方法,让我们可以指定变化的类型和范围,并且提供了setItemAnimator()方法,让开发者能够方便地定义添加、删除、移动的动画。
  • 把布局的工作抽象出来,放到了LayoutManager当中,并预制了瀑布流布局。

了解了这些,我们就能知道RecyclerView能帮我们解决什么问题,也就能更好地理解它为什么要这么设计,下面就开始进入真正的RecyclerView的学习。

三、RecyclerView架构


整个RecyclerView体系包含三大组件:

  • LayoutManagerposition the view
  • ItemAnimatoranimate the view
  • Adapterprovide the view

这三大组件各司其职,而RecyclerView负责管理,就组成了整个RecyclerView的架构。

3.1 LayoutManager

LayoutManager需要负责以下几部分的工作:

  • Position
    它负责View的摆放,可以是线性、宫格、瀑布流式或者任意类型,而RecyclerView不知道也不关心这些,这是LayoutManager的职责。
  • Scroll
    对于滚动事件的处理,RecyclerView负责接收事件,但是最终还是由LayoutManager进行处理滚动后的逻辑,因为只有它在知道View具体摆放的位置。
  • Focus traversal
    当焦点转移导致需要一个新的Item出现在可视区域中时,也是由LayoutManager处理的。

3.2 Adapter

Adapter需要负责以下几部分的工作:

  • 创建ViewViewHolder,后者作为整个复用机制的跟踪单元。
  • 把具体位置的ItemViewHolder进行绑定,并存储相关的信息。
  • 通知RecyclerView数据变化,支持局部的更新,在提高效率的同时也有效地支持了动画。
  • Item点击事件的处理。
  • 多类型布局的支持。

四、ViewHolder的生命周期

4.1 LayoutManager请求RecyclerView提供指定positionView

ViewHolder是和View相绑定的,同时它也是整个复用框架的跟踪单元。在RecyclerView体系中,对ViewHolder采用了二级缓存,分为CacheRecycled Pool,当LayoutManagerRecyclerView请求位于某个PositionView时,Recycled View会先去Cache中寻找,如果找到,那么直接返回;如果找不到,那么再去Recycled Pool中寻找,下面就是整个寻找过程的几种情况:

  • 命中Cache
    这种情况下,不会调用AdapteronCreateViewHolder或者onBindViewHolder方法:
  • Cache不存在,Recycled Pool也不存在
    这种情况下,会调用AdapteronCreateViewHolder方法,让它提供一个对应viewTypeViewHolder,我们在其中建立ViewHolderView之间的关联。
  • Cache不存在,Recycled Pool存在
    这种情况下,会回调AdapteronBindViewHolder方法,我们在其中使用当前的数据集合来更新ViewHolder所绑定的itemView的状态。

4.2 LayoutManager找到对应位置的View

LayoutManager通过addView方法把之前找到的View添加进RecyclerViewRecyclerView通过onViewAttachToWindow(VH viewHolder)方法,通知Adapter这个viewHolder所关联的itemView已经被添加到了布局当中,

4.3 LayoutManager请求RecyclerView移除某一个位置的View

4.3.1 普通情况

LayoutManager发现不再需要某一个positionView时,它会通知RecyclerViewRecyclerView通过onViewDetachFromWindow(VH viewHolder)通知Adapter和它绑定的itemView被移出了。同时,RecyclerView判断它是否能够被缓存,假设能够被缓存,那么它会先被放到Cache当中,在Cache中又会判断它内部是否有需要转移到Recycled Pool中的ViewHolder,在放入之后回收池后,通过onViewRecycled(VH viewHolder)方法通知Adapter它被回收了。

4.3.2 特殊情况

在上面的普通的情况中,onViewDetachFromWindow(VH viewHolder)是立即被回调的。然而在实际当中,由于我们需要对View的添加、删除做一些过度动画,这时候,我们需要等待ItemAnimator进行完动画操作之后,才做detachrecycle的逻辑,这一过程对于LayoutManager是不可见的。

4.4 ViewHolder的销毁

在一般情况下,我们不会去销毁ViewHolder,而是把它放入到缓存当中,除非出现以下两种情况。

4.4.1 ViewHolder所绑定的itemView当前状态异常

在放入Recycled Pool时,会去检查itemView的状态是否正常。这一操作的目的主要是为了避免出现诸如此类的情况:当前itemView正在执行动画,此时它可能呈现半透明的状态,如果此时把它放入到回收池中,那么当另一个位置的position需要复用它时就可能会出现问题。
当出现上面的情况后,Recycled Pool会先通过AdapteronFailedToRecycled(VH viewHolder)告诉它我们现在出现了异常的情况,由Adapter的实现者通过返回值来决定是否仍然要把它放入到Recycled Pool,默认是返回false,也就是不放入,那么这个ViewHolder就会被销毁了。

4.4.2 Recycled Pool中已经没有足够的空间

Recycled Pool的空间并不是无限大的,因此,如果没有足够的空间存放要被回收的ViewHolder,那么它也会被销毁。


造成这种情况的一般是动画引起的,例如,我们调用了notifyItemRangeChanged(0, getItemCount())方法,这时候为了进行渐出渐进的动画,那么我们就需要创建两倍的ViewHolder,出现这种情况时一般有两种解决方法:

  • 只通知具体发生变化的Item
  • 通过pool.setMaxRecycledViews(type, count)改变回收池的大小。

五、ItemAnimator

对于Item的动画,主要有以下几种情况:

  • 添加:Fade In
  • 删除:Fade Out
  • 移动:Translate
  • 更新:Cross Fade

RecyclerView对于动画的处理采用了Predictive的方式,除了当前已经在RecyclerView布局中的View(实线框部分),它还需要知道在屏幕意外的信息(虚线框部分),这样在H被删除的时候,它才能够对J-K进行上移动画,并把原来不在屏幕内的L上移到可视范围之内。

六、ChildHelperAdapterHelper

6.1 ChildHelper

对于ChildHelper的作用是:Provide a virtual children list to layoutmanager,下面我们就首先看一下为什么需要它。

6.1.1 解决什么问题

我们看下面这种情况,假如LayoutManager想要移除一个View,而ItemAnimator又希望给这一移除的操作增加一个动画,那么这时候就会产生冲突,到底应该怎么办,为此,RecyclerView通过ChildHelper来把它们隔离开。

6.1.2 解决问题的方法

RecyclerView收到LayoutManager要求改变布局的请求时,它并不是直接去更改ViewGroup,而是让ChildHelperItemAnimator去协调,并由它来操作ViewGroup


最明显的例子是,假如我们当前列表中状态为0,1,2,3,此时我们移除了position=0Item,这时候假如删除的动画还没有完成,那么LayoutManagerRecyclerViewgetChildAt(0)返回值将会不同,因为在LayoutManager并不清楚ChildHelper的存在,在它看来,position=0Item已经被移除了。

layoutManager.getChildAt(0); //return 1;
recyclerView.getChildAt(0); //return 0;

6.2 AdapterHelper

AdapterHelper所解决的问题和ChildHelper类似,ChildHelper是处理View的,而AdapterHelper用来跟踪ViewHolder的,其作用为:

  • Tracks ViewHolder positions
  • Virtual Adapter for LayoutManager

说起来可能比较抽象,我们用下面这种图理解一下,当我们移动某个Item并且它的onLayout方法还没有完成,那么AdapterLayoutpostion是不相同的:

七、ItemDecoration

ItemDecoration用来在RecyclerViewCanvas上进行额外的绘制操作,我们不仅可以在单个Item(例如给每个Item添加分割线)的Canvas上进行绘制,也可以在整个RecyclerViewCanvas上进行绘制,此外,我们还可以指定Item之间的间隔:

  • Custom Drawing on RecyclerViews Canvas
  • Add offset to View bounds
  • Have multiple ItemDecoration

需要注意的点:

  • Do not try to access to adapter
  • Keep necessary information in viewHolder
  • General onDraw rules apply
  • recyclerView.getChildViewHolder(View view)

参考文章:Android RecyclerView 使用完全解析 体验艺术般的控件

八、RecycledViewPool

RecyclerViewPool用来缓存那些回收的View,这些缓存不仅可以提供给单个RecyclerView使用,还可以提供和别的自定义控件共享。

  • Sanctuary for reserve ViewHolders
  • Can be shared between RecyclerViews or Custom ViewGroups
  • PerActivity Context

九、ItemTouchHelper

之前使用ListView的时候,如果需要支持侧滑删除、拖动排序这种操作,那么我们一般用引入一些开源库,现在RecyclerView已经帮我们提供了实现的接口,通过重写ItemTouchHelper的方法,就可以实现上面提到的那些操作。

  • Drag & Drop
  • Swipe to dismiss

参考文章:RecyclerView 进阶:使用 ItemTouchHelper 实现拖拽和侧滑删除

十、Tips

  • onBind Position != finaluse holder.getAdapterPostion()
    如果我们像下面这样,在onBindViewHolder中绑定了监听:
public void onBindViewHolder(final ViewHolder, final int position) {
    holder.itemView.setOnClickListener(new View.onClickListener) {
        @Override
        public void onClick(View view) {
            removeAtPostion(position);
        }
    }
}

由于Item会被添加、删除、移动,因此,我们在onBindViewHolder中获得位置,并不一定是当前的位置,例如像下面这样:

onBindViewHolder(holder, 5);
notifyItemMoved(5, 15);
holder.itemView.callOnClick();

那么就会得到错误的位置,这时候应当使用holder.getAdapterPostion()来保证能够得到预期的结果。

  • Payloads
    通过onBindViewHolder中的List payloads,我们可以指定在bind的时候只更新某一部分的信息,而不是全部更新。
  • onCreate means create
    onCreateViewHolder中,始终应当返回一个新的ViewHolder,而不是返回一个缓存的ViewHolder
  • Adapter position and Layout position
    就像我们前面在AdapterHelper中讨论的那样,在某些时刻,Adapter PositionLayout Position并不相等,我们应当根据情况选择需要使用哪个,Adapter Position数据所处的位置,而Layout Position则对应当前View的所处的位置

更多文章,欢迎访问我的 Android 知识梳理系列:

RecyclerView
Web note ad 1