Recyclerview
在日常开发中所使用的控件中绝对是顶流一般的存在,想嚼它这个主意一次两次了。在网上也看了许多关于Recyclerview
源码解析的文章,大佬们写的都很深入,可是关于像咱们这种储备常识不足的小白读者来说,那感觉就像外地人坐上了黑车,你说咋走就咋走。
从标题上能看出来,一篇文章肯定是写不完的,我准备分为以下几章来逐渐介绍:
- Recyclerview源码剖析:一、静态时怎样布局
- Recyclerview源码剖析:二、滑动时怎样布局
- Recyclerview源码剖析:三、ViewHolder三级缓存
- Recyclerview源码剖析:四、Adapter的增修改布局更新
- Recyclerview源码剖析:五、增修改的动画原理
咱们把这5章给串起来便是Recyclerview
完好使用下来的整个流程解析。
-
第一章,由于
Recyclerview
即便他再强壮他终究也是一个View,是ViewGroup你就肯定就要初始化,初始化便是指在没有任何交互场景下走的流程,所以也叫静态布局。 -
第二章,由于
Recyclerview
是一个可滑动的控件,在静态布局完成后,他就要担起滑动的责任了,所以第二章介绍滑动时代码逻辑是怎样履行的。 -
第三章,
Recyclerview
滑动逻辑了解清楚了之后,咱们就能够开端着手探究Recyclerview
他的child是从哪里来的,又要到哪儿去?这就涉及到Recyclerview的缓存逻辑了。 -
第四章,咱们前面三章把其实都是与探究
android
中ViewGroup
的onlayout
流程,然后咱们就能够探究notifyItemInserted(int)
、notifyItemRemoved(int)
、notifyItemChanged(int)
等引起布局变化的逻辑了。 -
第五章,当咱们有了第四章的基础之后咱们再去探究他们引起布局变化时布局动画又是怎样履行的了。
丈量流程
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还没丈量出清晰的尺度,就需要第2次丈量
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
彻底讲通了,才代表我真实的消化了.