作者:maxcion
Recyclerview在日常开发中所使用的控件中绝对是顶流一般的存在,想嚼它这个想法一次两次了。在网上也看了很多关于Recyclerview源码解析的文章,大佬们写的都很深刻,但是对于像我们这种储备知识不足的小白读者来说,那感觉就像外地人坐上了黑车,你说咋走就咋走。
测量流程
Recyclerview
既然是一个View,那他的测量流程必然会走到onMeasure()
onMeasure
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
//如果没设置LayoutManager那就不用走测量和布局流程
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
return;
}
//LinearLayoutManager isAutoMeasureEnabled() 默认是true
if (mLayout.isAutoMeasureEnabled()) {
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
/**
* This specific call should be considered deprecated and replaced with
* {@link #defaultOnMeasure(int, int)}. It can't actually be replaced as it could
* break existing third party code but all documentation directs developers to not
* override {@link LayoutManager#onMeasure(int, int)} when
* {@link LayoutManager#isAutoMeasureEnabled()} returns true.
*/
//其实这里从源码注释中可以看到这个地方已经废弃了,但是之所以没有删除是为了保证第三方库的稳定性
//所以这里直接跳过
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
//在这里我们判断给的宽高约束是否都是EXACTLY,如果是的话那就不用走复杂的测量逻辑了
mLastAutoMeasureSkippedDueToExact =
widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
if (mLastAutoMeasureSkippedDueToExact || mAdapter == null) {
return;
}
//这里mState.mLayoutStep的默认值就是State.STEP_START
//但是呢,在静态布局的时候我们不用管这个流程,因为这里主要是
//为了处理动画相关的逻辑,这里会在第四章重点介绍的
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
}
//将目前测量出来的大小传递给LayoutManager,因为最终复测layout children
//的LayoutManager,所以他需要只要大小
mLayout.setMeasureSpecs(widthSpec, heightSpec);
mState.mIsMeasuring = true;
//这里要重点关注,因为这里是真正执行layout的地方
dispatchLayoutStep2();
//因为dispatchLayoutStep2()执行了layout逻辑,所以Recyclerview已经被
//children撑起来了,那就可以通过children获取RV的尺寸
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
//如果layout还没测量出明确的尺寸,就需要第二次测量
if (mLayout.shouldMeasureTwice()) {
mLayout.setMeasureSpecs(
MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
mState.mIsMeasuring = true;
dispatchLayoutStep2();
// now we can get the width and height from the children.
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
}
mLastAutoMeasureNonExactMeasuredWidth = getMeasuredWidth();
mLastAutoMeasureNonExactMeasuredHeight = getMeasuredHeight();
} else {
...
}
}
为了对代码有全面的了解,我选择在代码中以注释的形式讲解,然后再对代码中的细节进行详细讲解,关于onMeasure
的大体流程都在上方面以注释的形式进行解释了,从注释中我们可以看到我们需要重点关注的逻辑有dispatchLayoutStep1()
、dispatchLayoutStep2()
Recyclerview.dispatchLayoutStep1()
/**
* The first step of a layout where we;
* - process adapter updates
* - decide which animation should run
* - save information about current views
* - If necessary, run predictive layout and save its information
*/
private void dispatchLayoutStep1() {
...
...
mState.mLayoutStep = State.STEP_LAYOUT;
}
从官方注释中能看到dispatchLayoutStep1()
主要有四个职责:
- 处理adapter发起的布局更新
- 决定执行什么样的动画
- 保存当前children的特定信息
- 如果有必要就执行predictive动画
从注释上也看看出来这段逻辑和静态布局几乎没有关系,但是在这里把代码贴出来而且仅贴了最后一行,因为这里更新了mState.mLayoutStep
的状态,在dispatchLayoutStep2()
中会用到
Recyclerview.dispatchLayoutStep2()
private void dispatchLayoutStep2() {
startInterceptRequestLayout();
onEnterLayoutOrScroll();
//因为在 dispatchLayoutStep1()中最后一行代码设置为State.STEP_LAYOUT,所以正常往下走
mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
//这里主要是处理adapter 的增删改的逻辑
mAdapterHelper.consumeUpdatesInOnePass();
mState.mItemCount = mAdapter.getItemCount();
mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
if (mPendingSavedState != null && mAdapter.canRestoreState()) {
if (mPendingSavedState.mLayoutState != null) {
mLayout.onRestoreInstanceState(mPendingSavedState.mLayoutState);
}
mPendingSavedState = null;
}
// Step 2: Run layout
//标记当前已经不是预布局(dispatchLayoutStep1)阶段
mState.mInPreLayout = false;
//交给LayoutManager进行布局
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = false;
// onLayoutChildren may have caused client code to disable item animations; re-check
//检查是否支持动画
mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
//更新布局阶段的状态
mState.mLayoutStep = State.STEP_ANIMATIONS;
onExitLayoutOrScroll();
stopInterceptRequestLayout(false);
}
因为这章只讲静态布局,所以真正相关的逻辑只在mLayout.onLayoutChildren(mRecycler, mState)
,逻辑已经进入LM(LayoutManager)中了,那这里我们就以LLM(LinearLayoutManager)为例
LLM.onLayoutChildren()
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
// layout algorithm:
// 1) by checking children and other variables, find an anchor coordinate and an anchor
// item position.
// 2) fill towards start, stacking from bottom
// 3) fill towards end, stacking from top
// 4) scroll to fulfill requirements like stack from bottom.
// create layout state
if (DEBUG) {
Log.d(TAG, "is pre layout:" + state.isPreLayout());
}
if (mPendingSavedState != null || mPendingScrollPosition != RecyclerView.NO_POSITION) {
if (state.getItemCount() == 0) {
removeAndRecycleAllViews(recycler);
return;
}
}
if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) {
mPendingScrollPosition = mPendingSavedState.mAnchorPosition;
}
//创建一个LayoutState对象,这是一个Layout流程标志位统一管理对象
ensureLayoutState();
mLayoutState.mRecycle = false;
// resolve layout direction
//判断当前设置的布局方向,与构造函数中的reverseLayout参数有关
resolveShouldLayoutReverse();
final View focused = getFocusedChild();
//第一次进来时 mAnchorInfo.mValid 是false
if (!mAnchorInfo.mValid || mPendingScrollPosition != RecyclerView.NO_POSITION
|| mPendingSavedState != null) {
mAnchorInfo.reset();
//mStackFromEnd的取值是根据自定义属性stackFromEnd的传值定的,默认是false,这里
//我们一reverseLayout为false情况为准,所以mAnchorInfo.mLayoutFromEnd最终为false,
//下面会用到
mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
// calculate anchor position and coordinate
//更新AnchorInfo,默认情况下,我们肯定都是认为布局children都是从
//RV的顶部然后然后布局到RV的地步,我们静态布局确实是这样的
//其实这里做得主要逻辑就是找到第一个没被remove的child
updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
mAnchorInfo.mValid = true;
} else if (focused != null && (mOrientationHelper.getDecoratedStart(focused)
>= mOrientationHelper.getEndAfterPadding()
|| mOrientationHelper.getDecoratedEnd(focused)
<= mOrientationHelper.getStartAfterPadding())) {
mAnchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused));
}
if (DEBUG) {
Log.d(TAG, "Anchor info:" + mAnchorInfo);
}
// LLM may decide to layout items for "extra" pixels to account for scrolling target,
// caching or predictive animations.
// 如果 mLastScrollDelta 大于等于 0,则说明当前的布局方向是向末尾(即底部)方向布局(即 LayoutState.LAYOUT_END),
// 否则就是向起始(即顶部)方向布局(即 LayoutState.LAYOUT_START)
mLayoutState.mLayoutDirection = mLayoutState.mLastScrollDelta >= 0
? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
mReusableIntPair[0] = 0;
mReusableIntPair[1] = 0;
//①计算布局开始和结束的位置
//如果是从顶部往底部布局mReusableIntPair[0] = 0,mReusableIntPair[1] = RV.height - padding
//反之mReusableIntPair[0]与mReusableIntPair[1]值互换
//后面同列出这个逻辑的详细代码
calculateExtraLayoutSpace(state, mReusableIntPair);
//在根据padding和margin计算最终的开始与结束位置
int extraForStart = Math.max(0, mReusableIntPair[0])
+ mOrientationHelper.getStartAfterPadding();
int extraForEnd = Math.max(0, mReusableIntPair[1])
+ mOrientationHelper.getEndPadding();
//这里我们讨论的是静态布局,而且在dispatchLayoutStep2中已经把isPreLayout置为false了
//所以这里的if不成立
if (state.isPreLayout() && mPendingScrollPosition != RecyclerView.NO_POSITION
&& mPendingScrollPositionOffset != INVALID_OFFSET) {
// if the child is visible and we are going to move it around, we should layout
// extra items in the opposite direction to make sure new items animate nicely
// instead of just fading in
final View existing = findViewByPosition(mPendingScrollPosition);
if (existing != null) {
final int current;
final int upcomingOffset;
if (mShouldReverseLayout) {
current = mOrientationHelper.getEndAfterPadding()
- mOrientationHelper.getDecoratedEnd(existing);
upcomingOffset = current - mPendingScrollPositionOffset;
} else {
current = mOrientationHelper.getDecoratedStart(existing)
- mOrientationHelper.getStartAfterPadding();
upcomingOffset = mPendingScrollPositionOffset - current;
}
if (upcomingOffset > 0) {
extraForStart += upcomingOffset;
} else {
extraForEnd -= upcomingOffset;
}
}
}
int startOffset;
int endOffset;
final int firstLayoutDirection;
//根据mLayoutFromEnd和mShouldReverseLayout判断第一个child布局方向
//因为mLayoutFromEnd和mShouldReverseLayout都是false,
//所以firstLayoutDirection ==LayoutState.ITEM_DIRECTION_TAIL
if (mAnchorInfo.mLayoutFromEnd) {
firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL
: LayoutState.ITEM_DIRECTION_HEAD;
} else {
firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
: LayoutState.ITEM_DIRECTION_TAIL;
}
onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);
detachAndScrapAttachedViews(recycler);
mLayoutState.mInfinite = resolveIsInfinite();
mLayoutState.mIsPreLayout = state.isPreLayout();
// noRecycleSpace not needed: recycling doesn't happen in below's fill
// invocations because mScrollingOffset is set to SCROLLING_OFFSET_NaN
mLayoutState.mNoRecycleSpace = 0;
//上面因为mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;逻辑
//所以这里是false
if (mAnchorInfo.mLayoutFromEnd) {
// fill towards start
//将mAnchorInfo 布局信息设置给mLayoutState
updateLayoutStateToFillStart(mAnchorInfo);
mLayoutState.mExtraFillSpace = extraForStart;
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
final int firstElement = mLayoutState.mCurrentPosition;
if (mLayoutState.mAvailable > 0) {
extraForEnd += mLayoutState.mAvailable;
}
// fill towards end
//再从anchorView反方向填充
updateLayoutStateToFillEnd(mAnchorInfo);
mLayoutState.mExtraFillSpace = extraForEnd;
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
if (mLayoutState.mAvailable > 0) {
// end could not consume all. add more items towards start
extraForStart = mLayoutState.mAvailable;
updateLayoutStateToFillStart(firstElement, startOffset);
mLayoutState.mExtraFillSpace = extraForStart;
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
}
} else {
// fill towards end
//② 根据AnchorInfo来更新LayoutState的内容,主要同步布局方向,
//从RV的什么位置开始布局(比如从RV y轴100的地方开始向底部布局)
//还剩多少可用空间可以用来布局
updateLayoutStateToFillEnd(mAnchorInfo);
mLayoutState.mExtraFillSpace = extraForEnd;
//③ 在这里开始真正的执行填充逻辑
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
final int lastElement = mLayoutState.mCurrentPosition;
if (mLayoutState.mAvailable > 0) {
extraForStart += mLayoutState.mAvailable;
}
// fill towards start
updateLayoutStateToFillStart(mAnchorInfo);
mLayoutState.mExtraFillSpace = extraForStart;
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
if (mLayoutState.mAvailable > 0) {
extraForEnd = mLayoutState.mAvailable;
// start could not consume all it should. add more items towards end
updateLayoutStateToFillEnd(lastElement, endOffset);
mLayoutState.mExtraFillSpace = extraForEnd;
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
}
}
...
layoutForPredictiveAnimations(recycler, state, startOffset, endOffset);
if (!state.isPreLayout()) {
mOrientationHelper.onLayoutComplete();
} else {
mAnchorInfo.reset();
}
mLastStackFromEnd = mStackFromEnd;
if (DEBUG) {
validateChildOrder();
}
}
正如官方注释所说,onLayoutChildren()
主要有以下职责:
- 参数检查并找到anchorView
- 向前填充child
- 向后填充child
- XXXXXXX,我没懂…
懵逼了有没有,只是一个lauout流程,怎么又是找AnchorView
,又从向前填充,又是向后填充!!!,正常情况我们都是一把梭,从头填充到尾然后打完收工.
在我们这个章节里其实就是找AnchorView
没什么逻辑,但是找到AnchorView
这个机制也不是为了静态布局合计的,更多的是为了Adapter
增删改设计的,但是这里还是想讲一下,所以我们先看一下是怎么找到AnchorView
,然后再说一下为什么要有AnchorView
这个设计.
LLM.updateAnchorInfoForLayout()
private void updateAnchorInfoForLayout(RecyclerView.Recycler recycler, RecyclerView.State state,
AnchorInfo anchorInfo) {
//这里应该和smoothScrooll相关,暂时不用管
if (updateAnchorFromPendingData(state, anchorInfo)) {
if (DEBUG) {
Log.d(TAG, "updated anchor info from pending information");
}
return;
}
//主要看这里①
if (updateAnchorFromChildren(recycler, state, anchorInfo)) {
if (DEBUG) {
Log.d(TAG, "updated anchor info from existing children");
}
return;
}
if (DEBUG) {
Log.d(TAG, "deciding anchor info for fresh state");
}
anchorInfo.assignCoordinateFromPadding();
anchorInfo.mPosition = mStackFromEnd ? state.getItemCount() - 1 : 0;
}
private boolean updateAnchorFromChildren(RecyclerView.Recycler recycler,
RecyclerView.State state, AnchorInfo anchorInfo) {
if (getChildCount() == 0) {
return false;
}
//这里和FocusedChild相关,也不用管
final View focused = getFocusedChild();
if (focused != null && anchorInfo.isViewValidAsAnchor(focused, state)) {
anchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused));
return true;
}
if (mLastStackFromEnd != mStackFromEnd) {
return false;
}
//① 主要看这里他是如何找到anchorView的
View referenceChild =
findReferenceChild(
recycler,
state,
anchorInfo.mLayoutFromEnd,
mStackFromEnd);
if (referenceChild != null) {
anchorInfo.assignFromView(referenceChild, getPosition(referenceChild));
// If all visible views are removed in 1 pass, reference child might be out of bounds.
// If that is the case, offset it back to 0 so that we use these pre-layout children.
//如果不是preLayout且支持Predictive动画
//就会执行以下逻辑
if (!state.isPreLayout() && supportsPredictiveItemAnimations()) {
// validate this child is at least partially visible. if not, offset it to start
final int childStart = mOrientationHelper.getDecoratedStart(referenceChild);
final int childEnd = mOrientationHelper.getDecoratedEnd(referenceChild);
final int boundsStart = mOrientationHelper.getStartAfterPadding();
final int boundsEnd = mOrientationHelper.getEndAfterPadding();
// b/148869110: usually if childStart >= boundsEnd the child is out of
// bounds, except if the child is 0 pixels!
boolean outOfBoundsBefore = childEnd <= boundsStart && childStart < boundsStart;
boolean outOfBoundsAfter = childStart >= boundsEnd && childEnd > boundsEnd;
//判断当前找到的view是否在RV的屏幕外部
if (outOfBoundsBefore || outOfBoundsAfter) {
anchorInfo.mCoordinate = anchorInfo.mLayoutFromEnd ? boundsEnd : boundsStart;
}
}
return true;
}
return false;
}
View findReferenceChild(RecyclerView.Recycler recycler, RecyclerView.State state,
boolean layoutFromEnd, boolean traverseChildrenInReverseOrder) {
ensureLayoutState();
// Determine which direction through the view children we are going iterate.
int start = 0;
int end = getChildCount();
int diff = 1;
if (traverseChildrenInReverseOrder) {
start = getChildCount() - 1;
end = -1;
diff = -1;
}
int itemCount = state.getItemCount();
final int boundsStart = mOrientationHelper.getStartAfterPadding();
final int boundsEnd = mOrientationHelper.getEndAfterPadding();
View invalidMatch = null;
View bestFirstFind = null;
View bestSecondFind = null;
//上面的代码不是很重要,都是为了这里的for循环做铺垫的,主要就是
//如果你是从顶部向底部布局,那么for循环就是从0 开始,for循环每次+diff(1)
//如果你是从底部布局,那么for循环就是从childCount-1开始,每次+diff(-1)
//重要逻辑在这个for循环中:这里面的逻辑就是找到第一个没有被remove的child
//如果没有找到就在第二优先级中寻找,然后第三优先级
for (int i = start; i != end; i += diff) {
final View view = getChildAt(i);
final int position = getPosition(view);
final int childStart = mOrientationHelper.getDecoratedStart(view);
final int childEnd = mOrientationHelper.getDecoratedEnd(view);
if (position >= 0 && position < itemCount) {
if (((RecyclerView.LayoutParams) view.getLayoutParams()).isItemRemoved()) {
if (invalidMatch == null) {
invalidMatch = view; // removed item, least preferred
}
} else {
// b/148869110: usually if childStart >= boundsEnd the child is out of
// bounds, except if the child is 0 pixels!
boolean outOfBoundsBefore = childEnd <= boundsStart && childStart < boundsStart;
boolean outOfBoundsAfter = childStart >= boundsEnd && childEnd > boundsEnd;
if (outOfBoundsBefore || outOfBoundsAfter) {
// The item is out of bounds.
// We want to find the items closest to the in bounds items and because we
// are always going through the items linearly, the 2 items we want are the
// last out of bounds item on the side we start searching on, and the first
// out of bounds item on the side we are ending on. The side that we are
// ending on ultimately takes priority because we want items later in the
// layout to move forward if no in bounds anchors are found.
if (layoutFromEnd) {
if (outOfBoundsAfter) {
bestFirstFind = view;
} else if (bestSecondFind == null) {
bestSecondFind = view;
}
} else {
if (outOfBoundsBefore) {
bestFirstFind = view;
} else if (bestSecondFind == null) {
bestSecondFind = view;
}
}
} else {
// We found an in bounds item, greedily return it.
return view;
}
}
}
}
// We didn't find an in bounds item so we will settle for an item in this order:
// 1\. bestSecondFind
// 2\. bestFirstFind
// 3\. invalidMatch
return bestSecondFind != null ? bestSecondFind :
(bestFirstFind != null ? bestFirstFind : invalidMatch);
}
简单概括就是下面这张图:
为什么要找到找AnchorView
呢?我们假设一个场景:
在一个高度为100dp的RV中,他的每个item的高度是20dp,现在因为增/删操作我我们的anchorView的坐标在(0,40)
现在有了AnchorView
的坐标了,那Recyclerview是怎么使用的呢?他将layout流程分为两步,第一步:从AnchorView
向顶部开始布局,第二步:从AnchorView
从底部开始布局,一上一下就把整个屏幕布局满了.
LLM.calculateExtraLayoutSpace()
protected void calculateExtraLayoutSpace(@NonNull RecyclerView.State state,
@NonNull int[] extraLayoutSpace) {
int extraLayoutSpaceStart = 0;
int extraLayoutSpaceEnd = 0;
// If calculateExtraLayoutSpace is not overridden, call the
// deprecated getExtraLayoutSpace for backwards compatibility
//①获取总共可布局空间的大小
@SuppressWarnings("deprecation")
int extraScrollSpace = getExtraLayoutSpace(state);
if (mLayoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
//如果是向上布局,extraLayoutSpaceStart的值就是extraScrollSpace
extraLayoutSpaceStart = extraScrollSpace;
} else {
extraLayoutSpaceEnd = extraScrollSpace;
}
extraLayoutSpace[0] = extraLayoutSpaceStart;
extraLayoutSpace[1] = extraLayoutSpaceEnd;
}
//获取RV总共可布局空间大小
protected int getExtraLayoutSpace(RecyclerView.State state) {
if (state.hasTargetScrollPosition()) {
//① 这里我们以LLM 的垂直布局为例
return mOrientationHelper.getTotalSpace();
} else {
return 0;
}
}
@Override
public int getTotalSpace() {
//通过RV的高度减去paddingVertical
return mLayoutManager.getHeight() - mLayoutManager.getPaddingTop()
- mLayoutManager.getPaddingBottom();
}
从上面的代码可以看出calculateExtraLayoutSpace()
他真的只是计算可布局的空间大小.其实最重要的就是layout流程,但是要layout就必须要找到AnchorView
,现在我们有AnchorView
我们就可以开始看layout流程了,所有layout流程都是从fill()
开始的.
LLM.fill()
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
// max offset we should set is mFastScroll + available
final int start = layoutState.mAvailable;
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
// TODO ugly bug fix. should not happen
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
recycleByLayoutState(recycler, layoutState);
}
//计算总共有多少空间可以用来摆放child
int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
//一个用来保存每次布局一个child的结果类,比如一个child消费了多少空间
//是否应该真实的计算这个child消费的空间(预布局的时候有些child虽然消费了空间,
// 但是不应该不参与真正的空间剩余空间的计算)
LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
//只要还有空间和item就进行布局layoutchunk
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
//重置上一次布局child的结果
layoutChunkResult.resetInternal();
if (RecyclerView.VERBOSE_TRACING) {
TraceCompat.beginSection("LLM LayoutChunk");
}
//①这里是真正layout child的逻辑
layoutChunk(recycler, state, layoutState, layoutChunkResult);
if (RecyclerView.VERBOSE_TRACING) {
TraceCompat.endSection();
}
if (layoutChunkResult.mFinished) {
break;
}
//layoutState.mLayoutDirection的值是 1或者-1 所以这里是 乘法
//如果是从顶部往底部填充,当前填充的是第三个child 且每个高度是10dp,那么layoutState.mOffset的值
//就是上次填充时的偏移量 + 这次填充child的高度
//如果是从底部往顶部填充,那就是次填充时的偏移量 - 这次填充child的高度
layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
/**
* Consume the available space if:
* * layoutChunk did not request to be ignored
* * OR we are laying out scrap children
* * OR we are not doing pre-layout
*/
//判断是否要真正的消费当前child参与布局所消费的高度
//从判断条件中可以看到预布局和这个有关,不过预布局等后面几章会详细说的
//这里就是同步目前还剩多少空间可以用来布局
if (!layoutChunkResult.mIgnoreConsumed || layoutState.mScrapList != null
|| !state.isPreLayout()) {
layoutState.mAvailable -= layoutChunkResult.mConsumed;
// we keep a separate remaining space because mAvailable is important for recycling
remainingSpace -= layoutChunkResult.mConsumed;
}
//如果产生了滑动,因为目前我们是静态布局,所以不用管这里
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
//执行回收相关逻辑
recycleByLayoutState(recycler, layoutState);
}
if (stopOnFocusable && layoutChunkResult.mFocusable) {
break;
}
}
if (DEBUG) {
validateChildOrder();
}
return start - layoutState.mAvailable;
}
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
//这里创建一个childView(暂时理解过创建一个child,里面涉及从缓存中获取child)
//但是我们这张不考虑缓存的逻辑,所以这里直接认定为创建了一个child,后面章节会
//详细的介绍这里的
View view = layoutState.next(recycler);
if (view == null) {
if (DEBUG && layoutState.mScrapList == null) {
throw new RuntimeException("received null view when unexpected");
}
// if we are laying out views in scrap, this may return null which means there is
// no more items to layout.
result.mFinished = true;
return;
}
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
//这里就是判断是把这个child添加到头部还是尾部(由LLM构造函数参数决定)
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);
}
}
//测量view宽高,这里的测量是LLM封装的一个测量宽高并加上margin的结果
measureChildWithMargins(view, 0, 0);
//记录当前child 消费了多少空间
result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
int left, top, right, bottom;
//从这里看到定义了left, top, right, bottom,就知道肯定是
//在计算当前child应该摆放在屏幕的什么坐标上了
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;
}
}
// We calculate everything with View's bounding box (which includes decor and margins)
// To calculate correct layout position, we subtract margins.
//进行布局
layoutDecoratedWithMargins(view, left, top, right, bottom);
if (DEBUG) {
Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:"
+ (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:"
+ (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin));
}
// Consume the available space if the view is not removed OR changed
if (params.isItemRemoved() || params.isItemChanged()) {
result.mIgnoreConsumed = true;
}
result.mFocusable = view.hasFocusable();
}
用伪代码来概括fill()
就是
fun fill(){
var couldLayoutSpace = layoutState.mAvailable + layoutState.mExtraFillSpace
while (couldLayoutSpace >0 && curLayoutChildPosition < adapter.itemCount - 1){
val layoutChunkResult = layoutChunk()
couldLayoutSpace -= layoutChunkResult.consumed
}
}
fun layoutChunk(){
val childView = getChildView()
measureChild(childView)
layoutChild(child)
}
这篇文章我尽可能的做到每行代码都讲解,但是有很多代码是涉及后面篇章需要介绍的,所以这里就没有过多的介绍,我的对阅读源码的理解是不能贪多,最好的方式是先理解了概念一,再理解概念二,如果在概念一还没完全理解的情况,就尝试理解概念二,概念二要是理解通了还好,要是没理解好,还会打击理解概念一的信心.如果上面有什么不对的地方烦请大佬们能够指出,如果有没介绍清楚的地方,可以提出来,只有我把Recyclerview
完全讲通了,才代表我真正的消化了.
Android 学习笔录
Android 性能优化篇:https://qr18.cn/FVlo89
Android 车载篇:https://qr18.cn/F05ZCM
Android Framework底层原理篇:https://qr18.cn/AQpN4J
Android 音视频篇:https://qr18.cn/Ei3VPD
Jetpack全家桶篇(内含Compose):https://qr18.cn/A0gajp
Kotlin 篇:https://qr18.cn/CdjtAF
Gradle 篇:https://qr18.cn/DzrmMB
OkHttp 源码解析笔记:https://qr18.cn/Cw0pBD
Flutter 篇:https://qr18.cn/DIvKma
Android 八大知识体:https://qr18.cn/CyxarU
Android 核心笔记:https://qr21.cn/CaZQLo
Android 往年面试题锦:https://qr18.cn/CKV8OZ
2023年最新Android 面试题集https://qr18.cn/CgxrRy
Android 车载开发岗位面试习题:https://qr18.cn/FTlyCJ
音视频面试题锦:https://qr18.cn/AcV6Ap