RecyclerView 知识梳理(1) 综述

1、概述

对于RecyclerView的学习,主要是须要掌握如下几点:html

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

2、为何要使用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的基本用法,那么你会发现,对于上述这些问题,它都给出了本身的解决方案:github

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

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

3、RecyclerView架构

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

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

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

3.1 LayoutManager

LayoutManager须要负责如下几部分的工做:架构

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

3.2 Adapter

Adapter须要负责如下几部分的工做:app

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

4、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已经被添加到了布局当中, ide

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)改变回收池的大小。

5、ItemAnimator

对于Item的动画,主要有如下几种状况:

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

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

6、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是不相同的:

7、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 使用彻底解析 体验艺术般的控件

8、RecycledViewPool

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

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

9、ItemTouchHelper

以前使用ListView的时候,若是须要支持侧滑删除、拖动排序这种操做,那么咱们通常用引入一些开源库,如今RecyclerView已经帮咱们提供了实现的接口,经过重写ItemTouchHelper的方法,就能够实现上面提到的那些操做。

  • Drag & Drop
  • Swipe to dismiss

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

10、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 createonCreateViewHolder中,始终应当返回一个新的ViewHolder,而不是返回一个缓存的ViewHolder
  • Adapter position and Layout position 就像咱们前面在AdapterHelper中讨论的那样,在某些时刻,Adapter PositionLayout Position并不相等,咱们应当根据状况选择须要使用哪一个,Adapter Position数据所处的位置,而Layout Position则对应当前**View的所处的位置**。

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

相关文章
相关标签/搜索