ViewHolder的属性
View itemView:对应RecyclerView的子View
int mPosition: View当前对应数据在数据源中的位置
int mOldPosition: View上次绑定的数据在数据源中的位置
long mItemId:可以判断ViewHolder是否需要重新绑定数据
int mItemViewType:itemView对应的类型
int mPreLayoutPosition: 在预布局阶段ViewHolder对应数据在数据源中的位置
int mFlags:ViewHolder对应的标记位
List
Recycler mScrapContainer:如果不为空,表示ViewHolder是存放在Scrap缓存中
RecyclerView 为了做ViewHolder动画,对ViewHolder做了 2 次布局,第一次叫预布局pre-layout,第二次叫后布局post-layout
mAttachedScrap:没有容量限制,缓存的ViewHolder是不做修改的,不会重新走Adapter的绑定方法
作用是解藕LayoutManager 和 RecyclerView.Recycler 之间的职责所在
mChangedScrap:没有容量限制,缓存的ViewHolder是做修改的,重新走Adapter的绑定方法
作用是让变化的ViewHolder执行动画时更加顺滑
mCachedViews:有容量限制,默认是2,缓存的ViewHolder是被dettach掉的,缓存的ViewHolder依然保存dettach前的数据,如position,绑定数据等,不会重新走Adapter的绑定方法
作用是保证快速滑动时获取ViewHolder时不走Adapter的绑定方法
mRecyclerPool:有容量限制,默认是每个itemType缓存5个ViewHolder,缓存的ViewHolder是被dettach掉的,缓存的ViewHolder是恢复出厂设置的,需要重新走Adapter的绑定方法,根据itemType来分开存储的, 可以提供给多个RecyclerView共享
mViewCacheExtension:留给开发者自由发挥的,官方并没有默认实现
mHiddenViews:不在Recycler类中,在ChildHelper类,缓存正在从 RecyclerView 边界中脱离的 ViewHolder,ChildHelper 的职责是重新计算非隐藏的子 view 列表和完整的子 view 列表之间的索引
作用是为了让脱离的ViewHolder 正确地执行对应的分离动画
RecyclerView从无到有的加载过程
如何获取一个ViewHolder?
第一步,先判断当前布局状态是否为预布局状态,一般在调用notifyItemChanged方法或数据变化时为预布局状态,先从mChangedScrap中获取已经被detach掉的viewholder进行重新绑定新的数据
第二步,如果没有找到viewholder,再从Scrap,Hidden,Cached三处缓存中获取viewholder,先是mAttachedScrap,其次mHiddenViews,最后mCachedViews
第三步,mViewCacheExtension中获取,默认是null,由开发者自定义的缓存策略,一般不会特别自定义过缓存策略,所以这里也获取不到viewholder
第四步,RecyclerPool中获取,通过itemType从SparseArray类型的ScrapData,再获取到viewholder,需要重新对viewholder绑定
第五步,调用了mAdapter. onCreateViewHolder来创建一个新的viewholder
注意:当第一次onLayoutChildren时还没有任何viewholder,所有的viewholder都需要onCreateViewHolder创建。
RecyclerView增删viewholder的过程
RecyclerView 为了做增删viewholder的平滑动画,对表项做了 2 次布局,一次是预布局,一次是实际布局
列表中有两个表项(1、2),删除 2,此时 3 会从屏幕底部平滑地移入并占据原来 2 的位置。
为了实现该效果,RecyclerView的策略是:为动画前的表项先执行一次pre-layout,将不可见的表项 3 也加载到布局中,形成一张布局快照(1、2、3)。再为动画后的表项执行一次post-layout,同样形成一张布局快照(1、3)。比对两张快照中表项 3 的位置,就知道它该如何做动画了。
如果支持viewholder动画,则 onLayoutChildren() 就会被调用 2 次
RecyclerView 在预布局阶段准备向列表中填充ViewHolder前,会清空现有的ViewHolder 1、2,把它们都 detach 并回收对应的 ViewHolder 到 mAttachedScrap列表中。
RecyclerView滑动时的缓存过程
先处理被滚动出去的viewholder,会把viewholder remove掉而不是detach掉,remove的作用是保留绑定关系,数据等,重新获取时不用重新绑定,然后将remove掉的viewholder判断是否满足缓存在mCachedViews的条件,如果满足,就判断mCachedViews是否已经满了,如果满了的话就会将缓存中最老的viewholder转移到RecyclerPool中,再将需要缓存的viewholder缓存进mCachedViews中,如果不满足,直接被放进RecyclerPool中
再处理被滚动进来的viewholder,实际还是在获取一个ViewHolder的流程,但是因为在布局完成之后,Scrap层的缓存就是空的了,所以只能从mCachedViews或者RecyclerPool中获取viewholder了,都取不到最后就会走onCreateViewHolder创建视图
RecyclerView数据更新时的缓存过程
在调用notifyDataSetChanged方法后,所有的子view会被标记,这个标记导致它们最后都被缓存到RecyclerPool中,然后重新绑定数据。并且由于RecyclerPool有容量限制,如果不够最后就要重新创建新的视图了。
但是使用notifyItemChanged等方法会将视图缓存到mChangedScrap和mAttachedScrap中,这两个缓存是没有容量限制的,所以基本不会重新创建新的视图,只是mChangedScrap中的视图需要重新绑定一下。
预布局过程中从mChangedScrap缓存中获取ViewHolder。获取逻辑如下:
线性遍历 mChangedScrap,position == ViewHolder.mPreLayoutPosition,返回该ViewHolder,否则走逻辑2
Adapter.hasStableIds()返回false,返回null,否则走逻辑3
线性遍历 mChangedScrap,mAdapter.getItemId(offsetPosition) == holder.getItemId,返回该ViewHolder,否则走逻辑4
上述都没有获取到,返回null
Stable Id 的作用是什么?
只会在调用 notifyDataSetChanged 方法之后,影响 RecyclerView 的行为。
如果调用 notifyDataSetChanged 的时候,Adapter 并没有设置 hasStableId,RecyclerView 不知道 发生了什么,哪一些东西变化了,所以,它假设所有的东西都变了,每一个 ViewHolder 都是无效的,因此应该把它们放到 RecyclerViewPool 而不是 scrap 中。
Adapter 设置了 StableId,ViewHolder 会进入 scrap 而不是 pool 中。然后会通过特定的 Id(Adapter 中的 getItemId 获取到的 id)而不是 postion 到 scrap 中查找 ViewHolder。
RecyclerView缓存优化实践
尽量使用 notifyItemXxx 局部更新方法进行细粒度的通知更新,而不是 notifyDatasetChanged
如果变更前后是两个数据集,无法确定具体哪一些数据项变化了,可以考虑使用DiffUtil。
如果数据集较大,建议结合使用AsyncListDiffer在子线程做 diff 运算。
如果特定 viewType 的 item 只有一个,可以通过 RecyclerView#getRecycledViewPool()#setMaxRecycledViews(viewType,1); 来调整缓存区的大小,减少内存占用
如果 RecyclerView 中的每个 item 都是一个 RecyclerView, 并且子 RecyclerView 的 item type 相同可以通过 RecyclerView#setRecycledViewPool(); 方法,实现缓存池的复用
RecyclerView数据处理上:
数据处理与视图绑定分离
加大RecyclerView的缓存
用空间换时间,来提高滚动的流畅性。
recyclerView.setItemViewCacheSize(20);
recyclerView.setDrawingCacheEnabled(true);
recyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
减少过度绘制
设置高度固定
可以使用RecyclerView.setHasFixedSize(true);来避免requestLayout浪费资源。
扁平化布局,减少view对象的创建