RecycleView
描述
在 RecycleView
的 onMeasure
阶段,默认采用自动测量模式,如果自身宽高不确定,会尝试通过 LayoutManager
测量子 Item
来确定自己的宽高,所以尽可能的使用 match_parent
或固定宽高来布局 RecycleView
,减少性能的损耗
在 RecycleView
的 onLayout
阶段,才会真正调用调用 LayoutManager.onLayoutChildren()
来对 Item
进行测量和布局
在 LayoutManager.onLayoutChildren()
方法中,会先通过 detachAndScrapAttachedViews()
函数回收布局,然后再通过 file()
函数进行布局,具体流程如下:
file() -> layoutChunk() -> LayoutState.next() -> addView() -> measureChildWithMargins() -> layoutDecoratedWithMargins()
可以看到 LayoutState.next()
函数里最终调用 tryGetViewHolderForPositionByDeadline()
函数,然后通过 Recyler
四级缓存或创建来获取 ViewHolder
从上面可以得出,会先回收布局,然后再从缓存中取出来,这里是不是没事找事做了?这里应该是一种职责分离吧,layout
的事归 LayoutManager
管,缓存的事归 Recycler
管。LayoutManager
不应该知道哪个 viewHolder
是否有效
可以看到 RecycleView
为了提高效率,部分交互是通过 attachViewToParent
和 detachViewFromParent
来对 View
进行轻量级的添加和移除,其中 attachViewToParent
可以调整 View
的布局顺序,注意与 getChildDrawingOrder
的区别
【备注一】detach
在 ViewGroup
中的实现很简单,只是将当前 View
从 ParentView
的 ChildView
数组中移除,将当前 View
的 mParent
设置为null, 可以理解为轻量级的临时 remove
。
【备注二】remove
代表真正的移除,不光从 ChildView
数组中移除,其他和View树各项联系也会被彻底斩断。
核心组件
LayoutManager
:负责item
的measure
、layout
Recycler
:四级回收、复用机制SmoothScroller
:滑动速度控制LinearSmoothScroller
SnapHelper
:惯性滑动控制LinearSnapHelper
PagerSnapHelper
ItemAnimator
:Item
动画SimpleItemAnimator
DefaultlteAnimator
ItemDecoration
:Item
样式装饰DividerItemDecoration
ItemTouchHelper
:可以实现侧滑删除,拖动排序- 可以扩展吸点悬浮的效果
OnItemTouchListener
:手势拦截器DiffUtil
:差分刷新
Recyler
回收池 - RecycleView
四级缓存
1 | public final class Recycler { |
优化点
当我们调用
notifyDataSetChanged()
或notifyItemRangeChanged(p, c)
(c这个范围非常大的时候),会将所有的ViewHolder
全部放入到pool
中,即第四级缓存中,由于第四级缓存默认大小为5,所以当界面显示的数量大于5时,会出现丢弃的情况,造成需要重新craete
和bind
来补充缺失的item
,对性能影响比较大(notifyItemRangeChanged
哪怕超出屏幕的item
都会尝试create
和bind
,同样性能损耗较大)。如果列表显示很多行,且notifyDataSetChanged()
函数调用较为频繁,可以考虑设置pool
的大小。1
recyclerView.getRecycledViewPool().setMaxRecycledViews(0, 20);
或者给条目设置全局ID:
1
2
3
4
5
6// adapter 中复写
override fun getItemId(position: Int): Long {
return position.toLong()
}
mAdapter.setHasStableIds(true);
当知道
Item
的改变不会影响到RecycleView
的宽高时,可以通过setHasFixedSize(true)
函数增加提高增删改插时的性能,原因如下:1
2
3
4
5
6
7
8
9
10
11
12
13// onItemRangeChanged
// onItemRangeRemoved
// onItemRangeMoved
// onItemRangeInserted
// 当 mHasFixedSize = true 时,上述四种方法都不会触发 requestLayout 重新布局
void triggerUpdateProcessor() {
if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
} else {
mAdapterUpdateDuringMeasure = true;
requestLayout();
}
}【注意】
notifyDataSetChanged()
最终会触发onChanged()
并调用requestLayout()
重新测量布局数据预取,
Android 5.0
起,显示分为两个阶段:- UI线程先处理布局信息,即创建和绑定View
- 渲染(Render)线程把指令发送给GPU
在不卡顿的前提下,帧与帧之间UI线程会有闲置的时机,
RecycleView
在新条目即将进入视野前,会花大量的时间来创建和绑定View
,造成下一帧出现卡顿,数据预取的思想就是利用闲置的线程,提前将下一帧创建和绑定View
的工作移至前一帧的空闲时间来完成。如果使用
RecycleView
内部的LayoutManager
,已经自动优化了。需要手动优化有以下两种场景:
- 嵌套
RecyclerView
:内部的LayoutManager
需要调用LinearLayoutManager#setInitialPrefetchItemCount
来设置预存取的数量,如:至少展示三个条目,则需要传入4 - 自定义
LayoutManager
:需要重写LayoutManager#collectInitialPrefetchPositions
方法实现预取逻辑,参考:https://juejin.cn/post/6844903661382959118
在
TabLayout+ViewPager+RecyclerView
的场景中,多个界面的条目布局是一致的,可以考虑多个RecyclerView
共用一个RecycledViewPool
来减少创建ViewHolder
的开销在条目中需要加载图片等场景,滚动时加载或只加载内存中的图片
尽可能的使用局部刷新函数,善用
DiffUtil
避免在
onCreate
和onBind
中创建过多对象RecycleView
和Item
尽可能的不要使用wrap_content
不要在
ScrollView
中添加RecycleView