RecyclerView 知识梳理(1) - 综述

一、概述

对于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 知识梳理系列:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容

  • 这篇文章分三个部分,简单跟大家讲一下 RecyclerView 的常用方法与奇葩用法;工作原理与ListView比...
    LucasAdam阅读 4,320评论 0 27
  • 简介: 提供一个让有限的窗口变成一个大数据集的灵活视图。 术语表: Adapter:RecyclerView的子类...
    酷泡泡阅读 5,051评论 0 16
  • RecyclerView包含以下几个重要的组件:1.LayoutManager: 测量和布局子View2.Recy...
    乌龟爱吃肉阅读 3,475评论 4 7
  • 每个夜晚来临的时候 孤独总在我左右 在我温柔的笑容背后 有多少泪水哀愁
    古月陌阅读 152评论 0 0
  • 当你在襁褓的时候, 你发现依赖他们的怀抱才进入梦乡; 当你在童年的时候, 你发现听到他们的呼唤才想起回家; 当你在...
    義隰阅读 127评论 0 2