十六、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

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

相关推荐
WeiShuai8 分钟前
vue-cli3使用DllPlugin优化webpack打包性能
前端·javascript
ice___Cpu14 分钟前
Linux 基本使用和 web 程序部署 ( 8000 字 Linux 入门 )
linux·运维·前端
JYbill17 分钟前
nestjs使用ESM模块化
前端
加油吧x青年36 分钟前
Web端开启直播技术方案分享
前端·webrtc·直播
吕彬-前端1 小时前
使用vite+react+ts+Ant Design开发后台管理项目(二)
前端·react.js·前端框架
小白小白从不日白1 小时前
react hooks--useCallback
前端·react.js·前端框架
恩婧2 小时前
React项目中使用发布订阅模式
前端·react.js·前端框架·发布订阅模式
mez_Blog2 小时前
个人小结(2.0)
前端·javascript·vue.js·学习·typescript
珊珊而川2 小时前
【浏览器面试真题】sessionStorage和localStorage
前端·javascript·面试
森叶2 小时前
Electron 安装包 asar 解压定位问题实战
前端·javascript·electron