参考
基于滑动场景解析RecyclerView的回收复用机制原理
RecyclerView剖析
一、 缓存机制
1.1 scrap缓存
// 添加
LayoutManager#onLayoutChildren
// 删除recyclerview中的子view并保存到scrap
detachAndScrapAttachedViews(recycler);
// 从4级缓存中查找viewholder后执行addView
// 删除
RecyclerView#dispatchLayoutStep3
// 重置scrap,即清空
mLayout.removeAndRecycleScrapInt(mRecycler);
提示:mAttachedScrap和mChangedScrap只用在layout过程中的临时变量, 当recyclerview布局完成后(即STATE_IDLE后)scrap是空数组;
1.2 mCachedViews
添加到RecyclerView时不用执行onBindViewHolder函数, 默认大小是2; 从安卓5开始添加了Prefetch功能, 如果使用LinearLayoutManager则大小增1; 如果使用瀑布流StaggeredGridLayoutManager则增加span的个数(例如2列瀑布流则增2)。
mCachedView数量 = mRequestedCacheMax + prefetch数量
在做滑动性能调优时可以增加该数组长度, 减少onBindViewHolder的执行次数;
二、瀑布流
瀑布流最为复杂, 坑也最多。
常见顶部留白和item抖动是由于viewholder在layout时和实际高度有差别;
(百度能搜到一堆,不再赘述)
1、 在onBindViewHolder中设置itemView的宽度和高度为固定值;
2、 设置GAP_HANDLING_NONE
3、 取消默认动画, RecyclerView#setItemAnimator(null);
4、 滑动到顶部时执行invalidateSpanAssignments, 其实就是清空mLazyLookupSpan并requestLayout。
三、布局流程
RecyclerView#setAdapter
RecyclerView.Adapter#notifyDataSetChanged
实际上都是修改标志位,然后执行requestLayout()。 等到下一个Choreographer触发的doTraversal函数;
dispatchLayoutStep1: preLayout阶段, 会缓存动画信息到mViewInfoStore并可能执行一次mLayout.onLayoutChildren
dispatchLayoutStep2: 真正的布局阶段, 肯定执行mLayout.onLayoutChildren函数;
dispatchLayoutStep3:postLayout阶段,执行mViewInfoStore中的动画并清空scrap数组, 执行mLayout.onLayoutComplete
tip:重载LayoutManager#onLayoutComplete函数表示布局已完成;
四、瀑布流缓存
mLazySpanLookup保存每个item在第几列, 从而滑动列表后记录以前的位置并在重新显示时直接使用。否则使用getNextSpan函数计算出在哪一列;
mSpans数组, 元素个数等于列数。 保存每一列的view和开始、结束座标, 从而在每一列添加view时从mSpans中拿到以前的位置并添加。
mSpans是瀑布流的大坑, 这意味着notifyDataSetChanged后并不会从顶部开始添加view, 而是从mSpans缓存的位置添加。
下拉刷新时添加滑动位置, 其实就是设置参数并在一次layout中完成操作;
notifyDateSetChanged
RecyclerView#scrollToPosition(0);
// Child is not visible. Set anchor coordinate depending on in which direction
// child will be visible.
anchorInfo.mPosition = mPendingScrollPosition;
if (mPendingScrollPositionOffset == INVALID_OFFSET) {
final int position = calculateScrollDirectionForPosition(
anchorInfo.mPosition);
anchorInfo.mLayoutFromEnd = position == LayoutState.LAYOUT_END;
anchorInfo.assignCoordinateFromPadding();
} else {
anchorInfo.assignCoordinateFromPadding(mPendingScrollPositionOffset);
}
anchorInfo.mInvalidateOffsets = true;
// 执行scrollToPosition(0)后mInvalidateOffsets为true, 并重置了mSpan
if (getChildCount() > 0 && (mPendingSavedState == null
|| mPendingSavedState.mSpanOffsetsSize < 1)) {
if (anchorInfo.mInvalidateOffsets) {
for (int i = 0; i < mSpanCount; i++) {
// Scroll to position is set, clear.
mSpans[i].clear();
if (anchorInfo.mOffset != INVALID_OFFSET) {
mSpans[i].setLine(anchorInfo.mOffset);
}
}
五、神坑
瀑布流放开item动画后出现诡异问题如被删除的item再次显示到recyclerview(上下滑动后刷新为正确的数据)、间距各种不对。
解决措施:在onScrollListener里的statechanged函数里判断当前正在itemanimation动画则结束动画;
常见问题:
1、滑动列表时先删除再添加view;
2、recyclerview使用观察者模式, 更新各种标志位后调用requestLayout; requestLayout函数可以调用多次, 其实就是设置标志位;
3、瀑布流滑动一段距离后执行notifyDataSetChanged时按照mSpans的位置进行layout。
4、itemdecoration是在RecyclerView的onDraw函数中绘制的。
后续抽时间补上流程图、示意图。