mAttachedScrap:onlayout可能执行多次,remove add view性能损耗过大,因而在其中先加入scrap缓存,执行viewgroup的detach子view方法,然后在fill()方法中addView的时候再将scrap中的缓存移出。
mChangedView:应该只在动画时我们不做多余分析
mCatchedViews:缓存刚溢出屏幕的viewholder,只缓存两个,满了以后再加回移出旧的至mReyclerViewPool,然后再添加新的。
mReyclerViewPool:每种viewholder缓存5个
源码分析:
情况1:可视窗口区域直接展示,不做任何滑动或notify方法
RecyclerView.onMeasure->dispatchLayoutStep2
onLayout->dispatchLayout()
void dispatchLayout() {
...
} else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
|| mLayout.getHeight() != getHeight()) {
// First 2 steps are done in onMeasure but looks like we have to run again due to
// changed size.
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
}
...
viewgroup多次执行onlayout 如果给定宽高有变动还会再执行一次。
dispatchLayoutStep2->layoutManager.onLayoutChildren
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
//确定了布局方向,正着倒着
...
//在此处scrap了view
detachAndScrapAttachedViews(recycler);
//倒着fill
if (mAnchorInfo.mLayoutFromEnd) {
...
} else {
//确认锚点位置,从锚点出开始向上向下填充
// fill towards end
updateLayoutStateToFillEnd(mAnchorInfo);
fill(recycler, mLayoutState, state, false);
// fill towards start
updateLayoutStateToFillStart(mAnchorInfo);
fill(recycler, mLayoutState, state, false);
}
}
linearLayoutManager.detachAndScrapAttachedViews->遍历viewgroup中的childview执行scrapOrRecycleView
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
if (viewHolder.shouldIgnore()) {
if (DEBUG) {
Log.d(TAG, "ignoring view " + viewHolder);
}
return;
}
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
removeViewAt(index);
recycler.recycleViewHolderInternal(viewHolder);
} else {
//detach并且scrap
detachViewAt(index);
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}
fill过程
int fill(RecyclerView.Recycler recycler, LinearLayoutManager.LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable{
//循环,只要计算出下面还有空白区域需要填满则持续layoutChunk
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
layoutChunk(recycler, state, layoutState, layoutChunkResult);
layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
//如果进入这个判断,会将移出屏幕的view回收至mCatchedViews或者recyclerViewPool中,但在这种情况下不会走入改循环,
// 因为updateLayoutStateToFillStart方法设了layoutState.mScrollingOffset =SCROLLING_OFFSET_NaN
if (layoutState.mScrollingOffset != LinearLayoutManager.LayoutState.SCROLLING_OFFSET_NaN) {
layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
recycleByLayoutState(recycler, layoutState);
}
}
}
layoutManager.fill->layoutManager.layoutChunk
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
//从各级缓存中取view 描述a
View view = layoutState.next(recycler);
LayoutParams params = (LayoutParams) view.getLayoutParams();
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
//此处addView并且unscrapView
addView(view);
} else {
addView(view, 0);
}
}
}
next()->recycler.getViewForPosition(mCurrentPosition)->tryGetViewHolderForPositionByDeadline()
RecyclerView.ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
...
//如果scrap有缓存此处取出缓存,不然此种情况无catchedViews缓存只能总recyclerViewPool中取重新bind或者createViewholder
// 1) Find by position from scrap/hidden list/cache
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
}
...
}
情况2:窗口滑动情况
RecyclerView的在onTouchEvent的ACTION_MOVE事件,这里对canScrollVertically方法进行了判断,并最终将偏移量传给了scrollByInternal方法,而在scrollByInternal方法中,调用了LayoutManager的scrollVerticallyBy方法。而scrollVerticallyBy最后调用了scrollBy方法,从而调到fill方法。
int fill(RecyclerView.Recycler recycler, LinearLayoutManager.LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable{
...
//循环,会继续计算填满下方因为滑动空出来需要填充的区域
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
//分析b
layoutChunk(recycler, state, layoutState, layoutChunkResult);
layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
//如果进入这个判断,会将移出屏幕的view回收至mCatchedViews中,可能会将之前回收到mCatched中的viewholder挤到recyclerViewPool中,此时有偏移量走进该方法了 分析c
if (layoutState.mScrollingOffset != LinearLayoutManager.LayoutState.SCROLLING_OFFSET_NaN) {
layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
recycleByLayoutState(recycler, layoutState);
}
}
}
分析b
还是从next取view,最终调到tryGetViewHolderForPositionByDeadline
RecyclerView.ViewHolder tryGetViewHolderForPositionByDeadline(int position,
//此时从scrap中是取不到 ,如果从上向下滑一个从未展示过的position,mCachedView中是肯定取不到的,
//如果该position展示过就看mCatchedViews缓存的那俩是不是这个position的view吧,否则走recyclerPool+bind或createViewHOlder boolean dryRun, long deadlineNs) {
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
}
}
分析c
recycleByLayoutState
->recycleViewsFromEnd()
->找到哪些移出了屏幕要被回收并recycleChildren(recycler, 0, i);
->遍历removeAndRecycleViewAt()
public void removeAndRecycleViewAt(int index, Recycler recycler) {
final View view = getChildAt(index);
//实际通过callback调用到了recyclerView即viewgroup的removeView
removeViewAt(index);
//在这个方法中调到recycleViewHolderInternal方法 回收view到mCatchedView中慢了就挤走原来的至pool中
recycler.recycleView(view);
}
情况3:notifyDataChanged的情况
以RecyclerView中notifyItemRemoved(1)为例,最终会调用requestLayout(),使整个RecyclerView重新绘制,过程为:
onMeasure()→onLayout()→onDraw()
走到dispathLayoutStep2()->onLayoutChildren()
数据源如果改变会更新所有的子view和mCatchedView标记FLAG_UPDATE,FLAG_INVALID
onLayoutChildren中同上面过程
linearLayoutManager.detachAndScrapAttachedViews->遍历viewgroup中的childview执行scrapOrRecycleView
private void scrapOrRecycleView(RecyclerView.Recycler recycler, int index, View view) {
final RecyclerView.ViewHolder viewHolder = getChildViewHolderInt(view);
//因为isInvalid不能加入scrap重用了 只能removeView并放入pool中等待bind重用了
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
removeViewAt(index);
recycler.recycleViewHolderInternal(viewHolder);
} else {
detachViewAt(index);
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}
在fill->next时也因为invalid无法拿到scrap mCatchedViews缓存
类图[https://www.jianshu.com/p/ff2e1fb56070/]
参考资料:
https://mp.weixin.qq.com/s/-CzDkEur-iIX0lPMsIS0aA
https://www.jianshu.com/p/2b19e9bcda84!
(https://www.jianshu.com/p/ff2e1fb56070/)
网友评论