概述
RecyclerView简称Rv,是作为ListView和GridView的加强版出现的。目的是在有限的屏幕空间上显示大量的内容。它的最有含金量的设计是 item复用机制
。
使用方式
RecyclerView这个类里面有几个关键方法:
- setLayoutManager()
- setAdapter()
- setItemAnimator()
- addItemDecoration()
解释如下:
setLayoutManager
这是必选项。它的作用是设置Rv的布局管理器。常用的布局管理器有,线性(LinearLayout),表格(GridLayoutManganer),以及瀑布流(StaggeredGridLayoutManager).
setAdapter
必选项。它的作用是设置RV的数据适配器,当数据发生改变时,以通过者的身份告知RV进行列表刷新操作。
setItemAnimator
非必选。它用于设置item的动画
addItemDecoration
非必选,用于设置item的装饰器,比如分割线。
核心问题
- rv是如何一步步将每一个itemView显示在屏幕上的。
- 分析在滑动过程中,tv是如何通过缓存机制提升整体性能的
rv是按照自定义View的定制规则一步步开发出来的。所以我们分析的重点其实还是在 它的onMeasure,onLayout,onDraw上。
下图是 rv的测量方法:
以上代码可以看出,
- 优先判断rv的宽高是不是外部指定的具体数值。如果是,就不必测量子view,而是直接用具体数值指定给自身的宽高即可。
- 如果不是具体数值,那就委托给LayoutManager来进行测量,此时会调用dispatchLayoutStep2 测量子view。
下图是rv的布局方法;
其核心代码在 dispatchLayout
方法中:
如果在 onMeasure阶段没有调用 dispatchLayoutStep2()来测量子view,那么,在onLayout阶段会重新测量子view
测量子view的方法 dspatchLayoutStep2()
码如下:
可以看到,mLayout.onLayoutChildren(mRecycler,mState)
这句代码中 rv把子view的测量布局逻辑,全部交给了mLayout。 这个mLayout就是rv的关键部分之一:LayoutManger布局管理器
。Rv的开发者,用这种接口解耦的方式,将 rv的布局托管给了外界传入的 布局管理器实例。 常用的实例无非三种,线性,表格,瀑布流
。
以线性布局管理器为例,它的源代码如下:
- 在onLayoutChildren 方法中,用 fill方法完成子view的测量布局工作
- 在fill方法中,用while循环调用 layoutChunk来判断是否有足够空间来放置完整的子view。 layoutChunk方法中是子view测量布局的核心实现。
- 每循环一次,都要重新计算 剩余空间。
layoutChunk方法,每执行一次,就填充一个子view到rv中源代码如下:
- layoutState是子view的缓存,每次next方法都会返回一个子view,然后调用addView或者addDisappearView将子view添加到rv中
- 测量被添加进去的子view的宽高间距
- 根据宽高和间距,最终确定子view的显示位置
测量和布局都完成之后,就轮到了绘制了。
onDraw方法中,只是将装饰 decoration逐个绘制出来。 所有的子view,则是通过android的渲染递归机制,逐个执行子view自己的onDraw显示在屏幕上。
小结1
Rv显示所有的子view会经历 测量布局绘制3个过程,但是它将测量和布局,都委托给了 LayoutManager
最后的装饰才是自己的onDraw去绘制的。
根据传入具体类型的LayoutMamager实例,rv能表现出不同的布局方式。这个叫做 策略模式。
缓存复用原理
缓存复用机制是 rv性能的保证。核心代码在下面的Recycler中。主要用来缓存屏幕内的viewholder以及部分屏幕外的viewHolder。
实际上Rv的缓存分为4个等级,之所以分为4个等级,其实是对应了4种不同的场景:
- 屏幕内的缓存 mAttachScrap 和 mChangedScrap
为什么屏幕内的viewHolder要缓存起来呢? 这是因为 rv在收到adapter的刷新通知之后,会对所有的子view进行全部刷新,但是,不分青红皂白,一律清空重新创建是不合理的。实际上,可能有相当数量的子view可以在屏幕内复用,仅做数据的刷新,viewHolder不进行销毁和重建。 一句话:屏幕内缓存机制,是为了应对列表刷新时,屏幕内的子view无需销毁重建的情况
- 刚移除到屏幕外的缓存 mCachedViews
它的默认缓存为2,且不可修改。 如果 屏幕滑动时,有些子view滑动到了屏幕外,那么,它将会进入到 mCachedViews,而由于容量有限,这些view会遵循先进先出的原则。
- viewCachedExtension
这是RV预留给 开发者的一个抽象类,支持我们编写自己的缓存机制,只不过不建议这么做。所以忽略它。
- 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:
- LayoutManager
- Recycler
左右护法,一个解耦了 布局方式,一个利用缓存保证了性能。