Recycleview基于LinearLayoutManager的初次布局

看了Recycleview代码后你会发现Recycleview真的牛逼,他在onLayout里面通过detachViewFromParent将全部的子view全部从recycleview里面移除,用这个方法会不触发requestLayout。然后添加view用的是addView,用addView会触发requestLayout呀。但是为什么每次addView。Recycleview都没有执行onLayout呢。原因是recycleview重写了requetLayout方法。绝绝子。。

    讲到布局那么就要先进入到RecycleviewonLayout方法。

1
2
3
4
5
6
7
8
// Recycleview
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
dispatchLayout();
TraceCompat.endSection();
mFirstLayoutComplete = true;
}

    在布局的时候调用了dispatchLayout方法。在看源码的时候可以发现,dispatchLayout方法又被分成了dispatchLayoutStep1dispatchLayoutStep2dispatchLayoutStep3。在首次布局完成后Recycleview会将mFirstLayoutComplete设置为true。这里最终的完成布局的操作都在dispatchLayoutStep2方法。

    来看下dispatchLayoutStep2:最主要是调用了我们的LayoutManager的onLayoutChildren方法。完成页面的布局。

1
2
3
private void dispatchLayoutStep2() {
mLayout.onLayoutChildren(mRecycler, mState);
}
1
2
3
4
5
6
7
// LinearLayoutManager
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
detachAndScrapAttachedViews(recycler);
updateLayoutStateToFillEnd(mAnchorInfo);
fill(recycler, mLayoutState, state, false);
}

    代码一删,关键代码就这些。

    先来看下这个detechAndScrapAttachedViews方法。这个方法看名之意就是从父视图分离掉全部的子view。内部调用了ViewGroup.detachViewFromParent方法。这是一个轻量级的移除操作。不会触发页面的requestLayout操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// Recycleview
public void detachAndScrapAttachedViews(@NonNull Recycler recycler) {
final int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
final View v = getChildAt(i);
scrapOrRecycleView(recycler, i, v);
}
}
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
// isInvalid:这是视图是否还有用,没用的话直接走remove操作,彻底父视图删掉
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
removeViewAt(index);
recycler.recycleViewHolderInternal(viewHolder);
} else {
// 一般都走这里
// 最终走的是RecyclerView.this.detachViewFromParent(offset);
// 这是轻量级的移除,不会触发requestLayout操作
detachViewAt(index);
// 将view添加到Recycler的mAttachedScrap数组里面。
// mAttachedScrap执行add操作是在detachAndScrapAttachedViews方法里面。而这个方法又只会在LinearLayoutManager的
//onLayoutChildren方法里才会被调用。而onLayoutChildren方法又只会在Recycleview的onLayout方法被调用。而要触发
//onLayout方法就要触发requestLayout方法。不然这个mAttachedScrap的值就一直为空。
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}
public void detachViewAt(int index) {
detachViewInternal(index, getChildAt(index));
}
private void detachViewInternal(int index, @NonNull View view) {
if (DISPATCH_TEMP_DETACH) {
ViewCompat.dispatchStartTemporaryDetach(view);
}
//最终走的是RecyclerView.this.detachViewFromParent(offset);不会触发requestLayout
mChildHelper.detachViewFromParent(index);
}

        现在来看下updateLayoutStateToFillEnd方法。看名知意就是更新LayoutState的信息。

1
2
3
4
5
6
7
8
9
10
11
12
private void updateLayoutStateToFillEnd(AnchorInfo anchorInfo) {
updateLayoutStateToFillEnd(anchorInfo.mPosition, anchorInfo.mCoordinate);
}
private void updateLayoutStateToFillEnd(int itemPosition, int offset) {
mLayoutState.mAvailable = mOrientationHelper.getEndAfterPadding() - offset;
mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD :
LayoutState.ITEM_DIRECTION_TAIL;
mLayoutState.mCurrentPosition = itemPosition;
mLayoutState.mLayoutDirection = LayoutState.LAYOUT_END;
mLayoutState.mOffset = offset;
mLayoutState.mScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
}

    这个里面出现了很重要的几个参数信息:

  • LayoutState.mAvailable:官方解释是:在布局放向上,我们应该填充的像素数。这个参数在第一次布局的时候被赋值为recycleview的高度。然后在滑动的时候这个值 = 滑动量 - 最后一个view全部展示出来需要的量。
  • LayoutState.mScrollingOffset:官方解释是:在滚动的时候,在不创建新视图的情况下的滚动量。这个值在第一次布局的时候等于一个固定值。然后在滚动的时候,这个值起到了确定哪些view应该被回收的作用。
img-0

    现在进入fill方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
final int start = layoutState.mAvailable;
// 这个mScrollingOffset的值在触发滑动的时候会被赋值为
// 最后一个view全部展示在屏幕上需要的距离
// mScrollingOffset的值只有在滑动的时候才会赋值
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
// 这里面只会在滚动的时候才会调用。
// 小于0代表,这次的滑动不足以让最下面的视图全部展示出来,
也就说明,我们不需要添加新的view到父视图里面来。
if (layoutState.mAvailable < 0) {
// 当小于0的时候这一步骤的操作无异于就是让 mScrollingOffset的值 等于 这次滑动的值。
layoutState.mScrollingOffset += layoutState.mAvailable;
}
// 这是回收那些不可见的视图。这个方法很重要,回收的操作就在这里进行的。
// 回收的条件就是上面的mScrollingOffset这个方法,在回收的时候有一个基准线limit 他的值等于 mScrollingOffset
// 然后会循环遍历recycleview所有的子view,找到孩子view底部距离顶部的值 > limit 的所有符合条件的view。然后将他们
// 从父视图进行移除。 具体讲解参考我的博客: 《Recycleview回收原理分析》
recycleByLayoutState(recycler, layoutState);
}
int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
// remainSpace > 0 表示 有可用空间可以使用,那么为了填补这个可用空间,我们就需要执行addVie操作,不然怎么填补这部分空间,
// 你说是吧。 添加结束的标志就是 可用空间被填满。
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
// 这个方法就是加载一个子view
layoutChunk(recycler, state, layoutState, layoutChunkResult);
if (layoutChunkResult.mFinished) {
break;
}
layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
// mIgnoreConsumed看名之意:忽略消耗。一般都不会忽略。
if (!layoutChunkResult.mIgnoreConsumed || layoutState.mScrapList != null
|| !state.isPreLayout()) {
// layoutChunkResult.mConsumed 的值就等于 一个itemView所需要消耗的高度。
layoutState.mAvailable -= layoutChunkResult.mConsumed;
remainingSpace -= layoutChunkResult.mConsumed;
}
// 这一段和上面那一段代码一样。
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
if (layoutState.mAvailable < 0) {
// 这一步的操作无疑就是让mScrollingOffset的值 = 这次移动的值。
layoutState.mScrollingOffset += layoutState.mAvailable;
}
// 回收那些view底部距离recycleview顶部的值 大于 这次滑动的值的页面。
recycleByLayoutState(recycler, layoutState);
}
if (stopOnFocusable && layoutChunkResult.mFocusable) {
break;
}
}
// 当我们还有可以展示的更过的孩子的时候这个mAvailable的值一定是负数的。
return start - layoutState.mAvailable;
}

    现在来看下layoutChunk方法,看他是怎么添加一个孩子进去的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
// LinearLayoutManager
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
// next:就是从RecycleView的缓存存里面取得。这个看我得博客:《Recycleview回收原理分析》
// 这里我就分析已经拿出来了。
View view = layoutState.next(recycler);
if (view == null) {
result.mFinished = true;
return;
}
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addView(view);
} else {
addView(view, 0);
}
} else {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addDisappearingView(view);
} else {
addDisappearingView(view, 0);
}
}
measureChildWithMargins(view, 0, 0);
result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
int left, top, right, bottom;
if (mOrientation == VERTICAL) {
if (isLayoutRTL()) {
right = getWidth() - getPaddingRight();
left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
} else {
left = getPaddingLeft();
right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
}
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
bottom = layoutState.mOffset;
top = layoutState.mOffset - result.mConsumed;
} else {
top = layoutState.mOffset;
bottom = layoutState.mOffset + result.mConsumed;
}
} else {
top = getPaddingTop();
bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);

if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
right = layoutState.mOffset;
left = layoutState.mOffset - result.mConsumed;
} else {
left = layoutState.mOffset;
right = layoutState.mOffset + result.mConsumed;
}
}
layoutDecoratedWithMargins(view, left, top, right, bottom);
if (params.isItemRemoved() || params.isItemChanged()) {
result.mIgnoreConsumed = true;
}
result.mFocusable = view.hasFocusable();
}
public void addView(View child) {
addView(child, -1);
}
public void addView(View child, int index) {
addViewInt(child, index, false);
}
private void addViewInt(View child, int index, boolean disappearing) {
final ViewHolder holder = getChildViewHolderInt(child);
if (disappearing || holder.isRemoved()) {
mRecyclerView.mViewInfoStore.addToDisappearedInLayout(holder);
} else {
mRecyclerView.mViewInfoStore.removeFromDisappearedInLayout(holder);
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (holder.wasReturnedFromScrap() || holder.isScrap()) {
if (holder.isScrap()) {
holder.unScrap();
} else {
holder.clearReturnedFromScrapFlag();
}
mChildHelper.attachViewToParent(child, index, child.getLayoutParams(), false);
if (DISPATCH_TEMP_DETACH) {
ViewCompat.dispatchFinishTemporaryDetach(child);
}
} else if (child.getParent() == mRecyclerView) { // it was not a scrap but a valid child
// ensure in correct position
int currentIndex = mChildHelper.indexOfChild(child);
if (index == -1) {
index = mChildHelper.getChildCount();
}
if (currentIndex != index) {
mRecyclerView.mLayout.moveView(currentIndex, index);
}
} else {
// 执行了Recycleview.addView操作。
mChildHelper.addView(child, index, false);
lp.mInsetsDirty = true;
if (mSmoothScroller != null && mSmoothScroller.isRunning()) {
mSmoothScroller.onChildAttachedToWindow(child);
}
}
if (lp.mPendingInvalidate) {
holder.itemView.invalidate();
lp.mPendingInvalidate = false;
}
}

    在layoutChunk里面通过addView操作将子view添加进去了。