十六、RecyclerView前世今生

概述

RecyclerView简称Rv,是作为ListView和GridView的加强版出现的。目的是在有限的屏幕空间上显示大量的内容。它的最有含金量的设计是 item复用机制

使用方式

RecyclerView这个类里面有几个关键方法:

  1. setLayoutManager()
  2. setAdapter()
  3. setItemAnimator()
  4. addItemDecoration()

解释如下:

setLayoutManager

这是必选项。它的作用是设置Rv的布局管理器。常用的布局管理器有,线性(LinearLayout),表格(GridLayoutManganer),以及瀑布流(StaggeredGridLayoutManager).

setAdapter

必选项。它的作用是设置RV的数据适配器,当数据发生改变时,以通过者的身份告知RV进行列表刷新操作。

setItemAnimator

非必选。它用于设置item的动画

addItemDecoration

非必选,用于设置item的装饰器,比如分割线。

核心问题

  • rv是如何一步步将每一个itemView显示在屏幕上的。
  • 分析在滑动过程中,tv是如何通过缓存机制提升整体性能的

rv是按照自定义View的定制规则一步步开发出来的。所以我们分析的重点其实还是在 它的onMeasure,onLayout,onDraw上。

下图是 rv的测量方法:

以上代码可以看出,

  1. 优先判断rv的宽高是不是外部指定的具体数值。如果是,就不必测量子view,而是直接用具体数值指定给自身的宽高即可。
  2. 如果不是具体数值,那就委托给LayoutManager来进行测量,此时会调用dispatchLayoutStep2 测量子view。

下图是rv的布局方法;

其核心代码在 dispatchLayout 方法中:

如果在 onMeasure阶段没有调用 dispatchLayoutStep2()来测量子view,那么,在onLayout阶段会重新测量子view

测量子view的方法 dspatchLayoutStep2()码如下:

可以看到,mLayout.onLayoutChildren(mRecycler,mState)这句代码中 rv把子view的测量布局逻辑,全部交给了mLayout。 这个mLayout就是rv的关键部分之一:LayoutManger布局管理器。Rv的开发者,用这种接口解耦的方式,将 rv的布局托管给了外界传入的 布局管理器实例。 常用的实例无非三种,线性,表格,瀑布流

以线性布局管理器为例,它的源代码如下:

  1. 在onLayoutChildren 方法中,用 fill方法完成子view的测量布局工作
  2. 在fill方法中,用while循环调用 layoutChunk来判断是否有足够空间来放置完整的子view。 layoutChunk方法中是子view测量布局的核心实现。
  3. 每循环一次,都要重新计算 剩余空间。

layoutChunk方法,每执行一次,就填充一个子view到rv中源代码如下:

  1. layoutState是子view的缓存,每次next方法都会返回一个子view,然后调用addView或者addDisappearView将子view添加到rv中
  2. 测量被添加进去的子view的宽高间距
  3. 根据宽高和间距,最终确定子view的显示位置

测量和布局都完成之后,就轮到了绘制了。

onDraw方法中,只是将装饰 decoration逐个绘制出来。 所有的子view,则是通过android的渲染递归机制,逐个执行子view自己的onDraw显示在屏幕上。

小结1

Rv显示所有的子view会经历 测量布局绘制3个过程,但是它将测量和布局,都委托给了 LayoutManager 最后的装饰才是自己的onDraw去绘制的。

根据传入具体类型的LayoutMamager实例,rv能表现出不同的布局方式。这个叫做 策略模式。

缓存复用原理

缓存复用机制是 rv性能的保证。核心代码在下面的Recycler中。主要用来缓存屏幕内的viewholder以及部分屏幕外的viewHolder。

实际上Rv的缓存分为4个等级,之所以分为4个等级,其实是对应了4种不同的场景:

  1. 屏幕内的缓存 mAttachScrap 和 mChangedScrap

为什么屏幕内的viewHolder要缓存起来呢? 这是因为 rv在收到adapter的刷新通知之后,会对所有的子view进行全部刷新,但是,不分青红皂白,一律清空重新创建是不合理的。实际上,可能有相当数量的子view可以在屏幕内复用,仅做数据的刷新,viewHolder不进行销毁和重建。 一句话:屏幕内缓存机制,是为了应对列表刷新时,屏幕内的子view无需销毁重建的情况

  1. 刚移除到屏幕外的缓存 mCachedViews

它的默认缓存为2,且不可修改。 如果 屏幕滑动时,有些子view滑动到了屏幕外,那么,它将会进入到 mCachedViews,而由于容量有限,这些view会遵循先进先出的原则。

  1. viewCachedExtension

这是RV预留给 开发者的一个抽象类,支持我们编写自己的缓存机制,只不过不建议这么做。所以忽略它。

  1. RecycledViewPool

它也是用来缓存屏幕外的viewHolder,由于 mCachedViews 容量只有2,那些从 mCachedViews 中溢出来的viewHolder就会进入到 RecycledViewPool,同时,它的内部数据会进行清理,变成干净的ViewHolder. 所以,从RecycledViewPool 中取出的viewholder一定会重新执行 onBindViewHolder .

容易忽略的是,多个Rv之间可以共享 RecycledViewPool,这种特性经常用在 多tab界面的优化中。多个tab都有类似的rv数据。

注意:RecycledViewPool 是根据ViewType来获取viewHolder,每个viewType最大缓存数量为5.

Rv是如何从缓存中获取viewHolder的

还记得 layoutState.next() 方法么,它用来从缓存中获取viewholder。

这个方法,其实就是从这四级缓存中逐个查找,如果找到最后都没找到,那么就执行adatper中的 createViewHolder

viewHolder是何时进入到缓存的

首先,是第一次执行 setLayoutManager -> setAdapter 时 ,由于缓存为空,所以所有的viewHolder都是 createViewHolder创建出来的。

接下来,如果发生了 数据变更并且通知了adapter。那么,所有屏幕内的viewHolder被填充到一级(屏幕内)缓存。

这个过程之后,一级缓存中就有了屏幕内的viewHolder缓存。此时,createViewHolder不会执行,由于数据需要被填充,所以,rv会执行 onBindViewHodlder,将旧的viewHolder结合新的数据,展示在屏幕上。

总结

RecyclerView 核心点有2:

  1. LayoutManager
  2. Recycler

左右护法,一个解耦了 布局方式,一个利用缓存保证了性能。

相关推荐
吴永琦(桂林电子科技大学)42 分钟前
HTML5
前端·html·html5
爱因斯坦乐44 分钟前
【HTML】纯前端网页小游戏-戳破彩泡
前端·javascript·html
恋猫de小郭1 小时前
注意,暂时不要升级 MacOS ,Flutter/RN 等构建 ipa 可能会因 「ITMS-90048」This bundle is invalid 被拒绝
android·前端·flutter
大莲芒5 小时前
react 15-16-17-18各版本的核心区别、底层原理及演进逻辑的深度解析--react17
前端·react.js·前端框架
木木黄木木7 小时前
html5炫酷3D文字效果项目开发实践
前端·3d·html5
Li_Ning217 小时前
【接口重复请求】axios通过AbortController解决页面切换过快,接口重复请求问题
前端
胡八一8 小时前
Window调试 ios 的 Safari 浏览器
前端·ios·safari
Dontla8 小时前
前端页面鼠标移动监控(鼠标运动、鼠标监控)鼠标节流处理、throttle、限制触发频率(setTimeout、clearInterval)
前端·javascript
再学一点就睡8 小时前
深拷贝与浅拷贝:代码世界里的永恒与瞬间
前端·javascript
CrimsonHu8 小时前
B站首页的 Banner 这么好看,我用原生 JS + 三大框架统统给你复刻一遍!
前端·javascript·css