本文參考了【進(jìn)階】RecyclerView源碼解析(一)——繪制流程,有不少地方我是直接照搬過來的,侵權(quán)立刪。
源碼版本基于com.android.support:recyclerview-v7:27.1.1
RecyclerView的源碼非常復(fù)雜,再加上一些輔助類其代碼量超過了2W行,這也導(dǎo)致很多人望而卻步,但是作為Android體系中一個(gè)非常重要的組件,學(xué)習(xí)其源碼對于我們了解RecyclerView是如何運(yùn)行的有很大的幫助。由于RecyclerView的源碼實(shí)在太多,所以我們就從主要的流程開始,畢竟RecyclerView也是直接繼承自ViewGroup,我們都知道自定義ViewGroup其主要流程在于重寫onMeasure,onLayout以及onDraw三個(gè)方法,我們也先從這三個(gè)流程來入手。
onMesure
protected void onMeasure(int widthSpec, int heightSpec) {
//沒有設(shè)置LayoutManager走默認(rèn)的measure流程
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
return;
}
//這里L(fēng)ayoutManager的幾個(gè)子類isAutoMeasureEnabled()方法默認(rèn)為true
if (mLayout.isAutoMeasureEnabled()) {
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
//代碼省略......
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
//如果測量模式是精確模式,直接返回,不繼續(xù)測量
final boolean measureSpecModeIsExactly =
widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
if (measureSpecModeIsExactly || mAdapter == null) {
return;
}
//mState.mLayoutStep默認(rèn)值是State.STEP_START
if (mState.mLayoutStep == State.STEP_START) {
//measure前的一些準(zhǔn)備工作
dispatchLayoutStep1();
//走完dispatchLayoutStep1()后mState.mLayoutStep是State.STEP_LAYOUT
}
// set dimensions in 2nd step. Pre-layout should happen with old dimensions for
// consistency
mLayout.setMeasureSpecs(widthSpec, heightSpec);
mState.mIsMeasuring = true;
//實(shí)際measure發(fā)生的地方,這里執(zhí)行完后mState.mLayoutStep變?yōu)镾tate.STEP_ANIMATIONS
dispatchLayoutStep2();
// now we can get the width and height from the children.
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
// if RecyclerView has non-exact width and height and if there is at least one child
// which also has non-exact width & height, we have to re-measure.
//判斷是否需要執(zhí)行二次測量,如果需要?jiǎng)t再執(zhí)行一次 dispatchLayoutStep2()
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);
}
} else {
if (mHasFixedSize) {
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
return;
}
//代碼省略......
startInterceptRequestLayout();
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
stopInterceptRequestLayout(false);
mState.mInPreLayout = false; // clear
}
}
整個(gè)measure的流程大致如上,中間刪掉了一些代碼,其實(shí)看源碼我們沒有必要去深究代碼細(xì)節(jié),只需要關(guān)注其整體流程即可。
我們從上往下逐個(gè)分:
//沒有設(shè)置LayoutManager走默認(rèn)的measure流程
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
return;
}
mLayout就是我們?yōu)镽ecyclerView設(shè)置的LayoutManager,這里判斷當(dāng)我們沒有設(shè)置LayoutManager時(shí),走默認(rèn)的測量。
void defaultOnMeasure(int widthSpec, int heightSpec) {
// calling LayoutManager here is not pretty but that API is already public and it is better
// than creating another method since this is internal.
final int width = LayoutManager.chooseSize(widthSpec,
getPaddingLeft() + getPaddingRight(),
ViewCompat.getMinimumWidth(this));
final int height = LayoutManager.chooseSize(heightSpec,
getPaddingTop() + getPaddingBottom(),
ViewCompat.getMinimumHeight(this));
setMeasuredDimension(width, height);
}
//LayoutManager.chooseSize方法
public static int chooseSize(int spec, int desired, int min) {
final int mode = View.MeasureSpec.getMode(spec);
final int size = View.MeasureSpec.getSize(spec);
switch (mode) {
case View.MeasureSpec.EXACTLY:
return size;
case View.MeasureSpec.AT_MOST:
return Math.min(size, Math.max(desired, min));
case View.MeasureSpec.UNSPECIFIED:
default:
return Math.max(desired, min);
}
}
chooseSize方法就是根據(jù)自身的測量模式(mode)來獲取其相應(yīng)的自身寬高,然后直接調(diào)用setMeasuredDimension方法設(shè)置自身寬高。由于這里沒有進(jìn)行child的測量,因此不難理解當(dāng)我們沒有設(shè)置LayoutManager時(shí)界面也就會(huì)出現(xiàn)什么都沒有的情況。
然后判斷mLayout.isAutoMeasureEnabled()。
//LinearLayoutManager
public boolean isAutoMeasureEnabled() {
return true;
}
這里默認(rèn)為true(GridLayoutManager和StaggerLayoutManager與此類似),進(jìn)入內(nèi)部流程。
接下來判斷測量模式,如果是精確測量模式則什么也不做,直接返回,否則進(jìn)入下一步
final boolean measureSpecModeIsExactly = widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
if (measureSpecModeIsExactly || mAdapter == null) {
return;
}
判斷當(dāng)前繪制步驟狀態(tài)(默認(rèn)是State.STEP_START,RecyclerView內(nèi)部維護(hù)了一個(gè)叫State的類,便于管理RecyclerView相應(yīng)的狀態(tài)信息,這個(gè)后面再分析),執(zhí)行相應(yīng)操作。
//mState.mLayoutStep默認(rèn)值是State.STEP_START
if (mState.mLayoutStep == State.STEP_START) {
//measure前的一些準(zhǔn)備工作
dispatchLayoutStep1();
//走完dispatchLayoutStep1()后mState.mLayoutStep是State.STEP_LAYOUT
}
//RecyclerView的一個(gè)內(nèi)部類,管理RecyclerView的狀態(tài)信息
public static class State {
static final int STEP_START = 1;
static final int STEP_LAYOUT = 1 << 1;
static final int STEP_ANIMATIONS = 1 << 2;
......
@LayoutState
int mLayoutStep = STEP_START;
......
}
執(zhí)行dispatchLayoutStep1()方法,從這個(gè)方法的注釋就可以看到,dispatchLayoutStep1()僅僅只是做一些準(zhǔn)備工作,包括一些狀態(tài)的清理及初始化,具體的細(xì)節(jié)就不分析了。
/**
* 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.assertLayoutStep(State.STEP_START);
fillRemainingScrollValues(mState);
mState.mIsMeasuring = false;
startInterceptRequestLayout();
mViewInfoStore.clear();
onEnterLayoutOrScroll();
processAdapterUpdatesAndSetAnimationFlags();
saveFocusInfo();
mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;
mItemsAddedOrRemoved = mItemsChanged = false;
mState.mInPreLayout = mState.mRunPredictiveAnimations;
mState.mItemCount = mAdapter.getItemCount();
findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);
if (mState.mRunSimpleAnimations) {
// Step 0: Find out where all non-removed items are, pre-layout
int count = mChildHelper.getChildCount();
......
}
if (mState.mRunPredictiveAnimations) {
// Step 1: run prelayout: This will use the old positions of items. The layout manager
// is expected to layout everything, even removed items (though not to add removed
// items back to the container). This gives the pre-layout position of APPEARING views
// which come into existence as part of the real layout.
// Save old positions so that LayoutManager can run its mapping logic.
......
// we don't process disappearing list because they may re-appear in post layout pass.
clearOldPositions();
} else {
clearOldPositions();
}
onExitLayoutOrScroll();
stopInterceptRequestLayout(false);
//方法執(zhí)行完后,將mState.mLayoutStep設(shè)置為State.STEP_LAYOUT,便于下一步操作
mState.mLayoutStep = State.STEP_LAYOUT;
}
執(zhí)行dispatchLayoutStep2(),這里也是真正執(zhí)行子view布局的地方,可以看到RecyclerView將layout工作轉(zhuǎn)交給了LayoutManager。
/**
* The second layout step where we do the actual layout of the views for the final state.
* This step might be run multiple times if necessary (e.g. measure).
*/
private void dispatchLayoutStep2() {
startInterceptRequestLayout();
onEnterLayoutOrScroll();
mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
mAdapterHelper.consumeUpdatesInOnePass();
//獲取子view的數(shù)量,這里調(diào)用了adapter的getItemCount()方法。
mState.mItemCount = mAdapter.getItemCount();
mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
// Step 2: Run layout
mState.mInPreLayout = false;
//實(shí)際執(zhí)行子view布局的地方,這里是將measure工作轉(zhuǎn)交給LayoutManager。
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = false;
mPendingSavedState = null;
// onLayoutChildren may have caused client code to disable item animations; re-check
mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
//測量工作完成后,修改State的layoutStep為State.STEP_ANIMATIONS,
//為后續(xù)子view顯示或退出顯示動(dòng)畫做準(zhǔn)備
mState.mLayoutStep = State.STEP_ANIMATIONS;
onExitLayoutOrScroll();
stopInterceptRequestLayout(false);
}
可以看到,實(shí)際執(zhí)行子元素布局的工作轉(zhuǎn)交給了LayoutManager,其具體實(shí)現(xiàn)在LayoutManager的子類中,這里以LinearLayoutManager為例來分析。
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
// layout algorithm:
//尋找錨點(diǎn)
// 1) by checking children and other variables, find an anchor coordinate and an anchor
// item position.
//填充子view,從錨點(diǎn)往上和從錨點(diǎn)往下
// 2) fill towards start, stacking from bottom
// 3) fill towards end, stacking from top
//RecyclerView滑動(dòng)時(shí)填充需要的布局
// 4) scroll to fulfill requirements like stack from bottom.
// create layout state
//代碼省略......
ensureLayoutState();
mLayoutState.mRecycle = false;
//判斷繪制方向,默認(rèn)是垂直布局或者LayoutDirection是從左往右
// resolve layout direction
resolveShouldLayoutReverse();
final View focused = getFocusedChild();
//如果錨點(diǎn)信息無效則重置錨點(diǎn)信息
if (!mAnchorInfo.mValid || mPendingScrollPosition != NO_POSITION
|| mPendingSavedState != null) {
mAnchorInfo.reset();
mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
// calculate anchor position and coordinate
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));
}
//代碼省略......
final int firstLayoutDirection;
//判斷布局方向,mAnchorInfo.mLayoutFromEnd默認(rèn)為false
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();
if (mAnchorInfo.mLayoutFromEnd) {
//如果是倒著布局的話
// fill towards start
//從錨點(diǎn)往上布局
updateLayoutStateToFillStart(mAnchorInfo);
mLayoutState.mExtra = extraForStart;
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
final int firstElement = mLayoutState.mCurrentPosition;
if (mLayoutState.mAvailable > 0) {
extraForEnd += mLayoutState.mAvailable;
}
// fill towards end
//從錨點(diǎn)往下布局
updateLayoutStateToFillEnd(mAnchorInfo);
mLayoutState.mExtra = extraForEnd;
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
//如果還有多余的填充像素?cái)?shù),再次運(yùn)行fill方法
if (mLayoutState.mAvailable > 0) {
// end could not consume all. add more items towards start
extraForStart = mLayoutState.mAvailable;
updateLayoutStateToFillStart(firstElement, startOffset);
mLayoutState.mExtra = extraForStart;
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
}
} else {
//默認(rèn)正常布局,先從錨點(diǎn)往下布局,在往上布局
// fill towards end
updateLayoutStateToFillEnd(mAnchorInfo);
mLayoutState.mExtra = 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.mExtra = extraForStart;
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
//與上面類似,如果有多余的繪制,則再次調(diào)用fill方法
if (mLayoutState.mAvailable > 0) {
extraForEnd = mLayoutState.mAvailable;
// start could not consume all it should. add more items towards end
updateLayoutStateToFillEnd(lastElement, endOffset);
mLayoutState.mExtra = extraForEnd;
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
}
}
// changes may cause gaps on the UI, try to fix them.
// TODO we can probably avoid this if neither stackFromEnd/reverseLayout/RTL values have
// changed
//這里是修復(fù)由于子元素滑動(dòng)可能會(huì)導(dǎo)致出現(xiàn)的一些空白
if (getChildCount() > 0) {
// because layout from end may be changed by scroll to position
// we re-calculate it.
// find which side we should check for gaps.
if (mShouldReverseLayout ^ mStackFromEnd) {
int fixOffset = fixLayoutEndGap(endOffset, recycler, state, true);
startOffset += fixOffset;
endOffset += fixOffset;
fixOffset = fixLayoutStartGap(startOffset, recycler, state, false);
startOffset += fixOffset;
endOffset += fixOffset;
} else {
int fixOffset = fixLayoutStartGap(startOffset, recycler, state, true);
startOffset += fixOffset;
endOffset += fixOffset;
fixOffset = fixLayoutEndGap(endOffset, recycler, state, false);
startOffset += fixOffset;
endOffset += fixOffset;
}
}
//完成后重置相關(guān)的一些參數(shù)
layoutForPredictiveAnimations(recycler, state, startOffset, endOffset);
if (!state.isPreLayout()) {
mOrientationHelper.onLayoutComplete();
} else {
mAnchorInfo.reset();
}
mLastStackFromEnd = mStackFromEnd;
//代碼省略......
}
子元素具體的布局是在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);
}
int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
layoutChunkResult.resetInternal();
if (VERBOSE_TRACING) {
TraceCompat.beginSection("LLM LayoutChunk");
}
layoutChunk(recycler, state, layoutState, layoutChunkResult);
//代碼省略......
}
//代碼省略......
return start - layoutState.mAvailable;
}
具體的邏輯都在layoutChunk(recycler, state, layoutState, layoutChunkResult)方法里面。
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
//獲取下一個(gè)子view
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;
}
LayoutParams params = (LayoutParams) view.getLayoutParams();
//根據(jù)view的狀態(tài)執(zhí)行對應(yīng)的addview方法
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);
}
}
//測量從LayoutState中取出的子view
measureChildWithMargins(view, 0, 0);
result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
//計(jì)算left, top, right, bottom;
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;
}
}
// We calculate everything with View's bounding box (which includes decor and margins)
// To calculate correct layout position, we subtract margins.
//完成子view的布局
layoutDecoratedWithMargins(view, left, top, right, bottom);
//代碼省略......
}
首先看下這行代碼View view = layoutState.next(recycler);,這里非常重要。
View next(RecyclerView.Recycler recycler) {
//在第一次加載RecyclerView的時(shí)候mScrapList為null,所以暫時(shí)不考慮
if (mScrapList != null) {
return nextViewFromScrapList();
}
//獲取view,這里使用了緩存策略
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
繼續(xù)看recycler.getViewForPosition(mCurrentPosition);這個(gè)方法。
public View getViewForPosition(int position) {
return getViewForPosition(position, false);
}
View getViewForPosition(int position, boolean dryRun) {
return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}
ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) {
//代碼省略......
ViewHolder holder = null;
//省略掉從緩存中獲取view的代碼......
//代碼省略......
if (holder == null) {
//代碼省略......
holder = mAdapter.createViewHolder(RecyclerView.this, type);
//代碼省略......
}
}
//代碼省略......
return holder;
}
這里獲取view最終調(diào)用ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs)方法,并在里面進(jìn)行了一堆判斷,主要是判斷scrap、cache以及RecyclerViewPool中是否有緩存,如果這三個(gè)緩存中都沒有緩存的view,最后才調(diào)用adapter的createViewHolder方法創(chuàng)建一個(gè)viewholder,在createViewHolder方法中最終會(huì)調(diào)用我們自己實(shí)現(xiàn)的onCreateViewHolder方法。注意RecyclerView這里緩存的是ViewHolder,這也是RecyclerView與ListView不同的地方之一。
對View view = layoutState.next(recycler);的分析暫時(shí)告一段落,沿著layoutChunk方法繼續(xù)往下分析。
LayoutParams params = (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);
}
}
這里就是將我們通過layoutState.next(recycler)獲取的view添加到RecyclerView中,繼續(xù)往下。
//測量獲取的子view
//LinearLayoutManager->layoutChunk
measureChildWithMargins(view, 0, 0);
//RecyclerView->LayoutManager->measureChildWithMargins
public void measureChildWithMargins(View child, int widthUsed, int heightUsed) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
//這里獲取Itemdecoration里面設(shè)置的水平方向和豎直方向的偏移量
final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
widthUsed += insets.left + insets.right;
heightUsed += insets.top + insets.bottom;
//獲取子view的measureSpec
final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(),
getPaddingLeft() + getPaddingRight()
+ lp.leftMargin + lp.rightMargin + widthUsed, lp.width,
canScrollHorizontally());
final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(),
getPaddingTop() + getPaddingBottom()
+ lp.topMargin + lp.bottomMargin + heightUsed, lp.height,
canScrollVertically());
if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
//調(diào)用child.measure方法測量子view自身寬高
child.measure(widthSpec, heightSpec);
}
}
這個(gè)方法主要是獲取childView上下左右的偏移量以及完成childView自身的測量。這里分析下mRecyclerView.getItemDecorInsetsForChild(child);方法。
Rect getItemDecorInsetsForChild(View child) {
//代碼省略......
final Rect insets = lp.mDecorInsets;
insets.set(0, 0, 0, 0);
//mItemDecorations是保存ItemDecoration的集合,
//我們給RecyclerView繪制分割線等諸多view的裝飾效果都是通過繼承ItemDecoration來實(shí)現(xiàn)的
final int decorCount = mItemDecorations.size();
for (int i = 0; i < decorCount; i++) {
mTempRect.set(0, 0, 0, 0);
//調(diào)用ItemDecoration的getItemOffsets方法來獲取相應(yīng)的view偏移量
mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);
insets.left += mTempRect.left;
insets.top += mTempRect.top;
insets.right += mTempRect.right;
insets.bottom += mTempRect.bottom;
}
//代碼省略......
return insets;
}
這個(gè)方法里面循環(huán)調(diào)用了ItemDecoration的getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)方法(RecyclerView可以設(shè)置多個(gè)ItemDecoration),并將我們設(shè)置的left,top,right,bottom偏移量設(shè)置到outRect中去,最后得到RecyclerView設(shè)置的所有ItemDecoration的left,top,right,bottom偏移量。這個(gè)方法我們在繼承ItemDecoration的時(shí)候需要我們自己去實(shí)現(xiàn),這里簡單說下偏移量,用一張圖片來表示:

分析完了measureChildWithMargins后,繼續(xù)往下。
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;
}
}
這里是設(shè)置RecyclerView中各個(gè)子view應(yīng)該布局的位置(left,top,right,bottom),設(shè)置完了后調(diào)用layoutDecoratedWithMargins(view, left, top, right, bottom);開始布局子view。
LinearLayoutManager->layoutChunk->layoutDecoratedWithMargins
layoutDecoratedWithMargins(view, left, top, right, bottom);
RecyclerView->LayoutManager->layoutDecoratedWithMargins
public void layoutDecoratedWithMargins(View child, int left, int top, int right, int bottom) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final Rect insets = lp.mDecorInsets;
child.layout(left + insets.left + lp.leftMargin, top + insets.top + lp.topMargin,
right - insets.right - lp.rightMargin,
bottom - insets.bottom - lp.bottomMargin);
}
可以看到,在layoutDecoratedWithMargins方法中,子view的布局位置是由left,top,right,bottom和ItemDecoration設(shè)置的偏移量共同決定的。至此onMeasure的流程就分析完了,接下來看下onLayout方法。
onLayout
在分析onMeasure的時(shí)候有幾個(gè)問題:
- 1、為什么當(dāng)不設(shè)置LayoutManager時(shí),界面什么都沒有?
答:因?yàn)镽ecyclerView把子view的測量以及布局交給了LayoutManager來完成,當(dāng)我們不設(shè)置LayoutManager時(shí),RecyclerView無法完成子view的測量和布局,自然也就無法顯示到界面上。 - 2、當(dāng)我們給RecyclerView設(shè)置精確寬高度時(shí),onMeasure不也是直接就返回,沒有經(jīng)過LayoutManager的測量和布局嗎,為什么最后能顯示子view呢?
答:當(dāng)給RecyclerView設(shè)置精確寬高值時(shí),LayoutManager對子view的測量和布局工作是在onLayout方法中完成的。這也是接下來將要分析的部分。 - 3、在執(zhí)行完
dispatchLayoutStep2();方法后,設(shè)置了mState.mLayoutStep = State.STEP_ANIMATIONS;,這個(gè)STEP_ANIMATIONS又是在哪里調(diào)用的呢?
protected void onLayout(boolean changed, int l, int t, int r, int b) {
TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
dispatchLayout();
TraceCompat.endSection();
mFirstLayoutComplete = true;
}
void dispatchLayout() {
//代碼省略......
mState.mIsMeasuring = false;
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} 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();
} else {
// always make sure we sync them (to ensure mode is exact)
mLayout.setExactMeasureSpecsFrom(this);
}
dispatchLayoutStep3();
}
可以看到在dispatchLayout方法中,仍然會(huì)判斷State的mLayoutStep這個(gè)變量,當(dāng)給RecyclerView設(shè)置精確寬高值是,在onMeasure方法中直接返回,跳過了接下來的步驟,因此mLayoutStep仍然是其初始值,也就是State.STEP_START,因此條件成立并執(zhí)行dispatchLayoutStep1();和dispatchLayoutStep2();完成子view的測量和布局。
這里還有一個(gè)判斷,也就是當(dāng)我們在布局的時(shí)候改變了RecyclerView的寬高值,也會(huì)調(diào)用dispatchLayoutStep2();方法重新測量和布局。
在dispatchLayout()方法的最后調(diào)用了dispatchLayoutStep3();,這里就可以回答上面的第三個(gè)問題,也就是STEP_ANIMATIONS就是在這個(gè)方法中判斷的。根據(jù)注釋,我們可以看出,這個(gè)方法主要是保存子view的一些動(dòng)畫以及做一些相關(guān)的清理工作。
private void dispatchLayoutStep3() {
//這里有一個(gè)前提就是mLayoutStep必須是State.STEP_ANIMATIONS,
//也就是執(zhí)行完了dispatchLayoutStep2()后該方法才能繼續(xù)往下走
mState.assertLayoutStep(State.STEP_ANIMATIONS);
startInterceptRequestLayout();
onEnterLayoutOrScroll();
//這里將mLayoutStep又重置為了State.STEP_START
mState.mLayoutStep = State.STEP_START;
//如果設(shè)置了ItemAnimator,這里判斷成立,RecyclerView預(yù)置了默認(rèn)的ItemAnimator
if (mState.mRunSimpleAnimations) {
// Step 3: Find out where things are now, and process change animations.
// traverse list in reverse because we may call animateChange in the loop which may
// remove the target view holder.
//循環(huán)保存ItemAnimator中設(shè)置的動(dòng)畫信息,如果有設(shè)置的話,RecyclerView中預(yù)置了默認(rèn)動(dòng)畫
for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {
ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
//代碼省略......
final ItemHolderInfo animationInfo = mItemAnimator
.recordPostLayoutInformation(mState, holder);
ViewHolder oldChangeViewHolder = mViewInfoStore.getFromOldChangeHolders(key);
if (oldChangeViewHolder != null && !oldChangeViewHolder.shouldIgnore()) {
final boolean oldDisappearing = mViewInfoStore.isDisappearing(
oldChangeViewHolder);
final boolean newDisappearing = mViewInfoStore.isDisappearing(holder);
if (oldDisappearing && oldChangeViewHolder == holder) {
// run disappear animation instead of change
mViewInfoStore.addToPostLayout(holder, animationInfo);
} else {
final ItemHolderInfo preInfo = mViewInfoStore.popFromPreLayout(
oldChangeViewHolder);
// we add and remove so that any post info is merged.
mViewInfoStore.addToPostLayout(holder, animationInfo);
ItemHolderInfo postInfo = mViewInfoStore.popFromPostLayout(holder);
if (preInfo == null) {
handleMissingPreInfoForChangeError(key, holder, oldChangeViewHolder);
} else {
animateChange(oldChangeViewHolder, holder, preInfo, postInfo,
oldDisappearing, newDisappearing);
}
}
} else {
mViewInfoStore.addToPostLayout(holder, animationInfo);
}
}
// Step 4: Process view info lists and trigger animations
//執(zhí)行ItemAnimator中設(shè)置的動(dòng)畫
mViewInfoStore.process(mViewInfoProcessCallback);
}
//layout完成后,執(zhí)行一些清理工作
mLayout.removeAndRecycleScrapInt(mRecycler);
mState.mPreviousLayoutItemCount = mState.mItemCount;
mDataSetHasChangedAfterLayout = false;
mDispatchItemsChangedEvent = false;
mState.mRunSimpleAnimations = false;
//代碼省略......
}
這里看下mViewInfoStore.process(mViewInfoProcessCallback);這個(gè)方法。
mViewInfoStore.process(mViewInfoProcessCallback);
/**
* The callback to convert view info diffs into animations.
*/
private final ViewInfoStore.ProcessCallback mViewInfoProcessCallback =
new ViewInfoStore.ProcessCallback() {
//代碼省略......
@Override
public void processAppeared(ViewHolder viewHolder,
ItemHolderInfo preInfo, ItemHolderInfo info) {
animateAppearance(viewHolder, preInfo, info);
}
//代碼省略......
};
void animateAppearance(@NonNull ViewHolder itemHolder,
@Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) {
itemHolder.setIsRecyclable(false);
//判斷是否設(shè)置了動(dòng)畫,如果設(shè)置了,則執(zhí)行動(dòng)畫
if (mItemAnimator.animateAppearance(itemHolder, preLayoutInfo, postLayoutInfo)) {
postAnimationRunner();
}
}
void postAnimationRunner() {
if (!mPostedAnimatorRunner && mIsAttached) {
//post動(dòng)畫執(zhí)行
ViewCompat.postOnAnimation(this, mItemAnimatorRunner);
mPostedAnimatorRunner = true;
}
}
private Runnable mItemAnimatorRunner = new Runnable() {
@Override
public void run() {
if (mItemAnimator != null) {
//調(diào)用ItemAnimator的runPendingAnimations()方法執(zhí)行動(dòng)畫,該方法需要我們自己實(shí)現(xiàn)
mItemAnimator.runPendingAnimations();
}
mPostedAnimatorRunner = false;
}
};
可以看到,所有動(dòng)畫邏輯都封裝在了mViewInfoProcessCallback中,這個(gè)callback是ViewInfoStore類的一個(gè)內(nèi)部接口,里面封裝了各種動(dòng)畫行為。
interface ProcessCallback {
void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo preInfo,
@Nullable ItemHolderInfo postInfo);
void processAppeared(ViewHolder viewHolder, @Nullable ItemHolderInfo preInfo,
ItemHolderInfo postInfo);
void processPersistent(ViewHolder viewHolder, @NonNull ItemHolderInfo preInfo,
@NonNull ItemHolderInfo postInfo);
void unused(ViewHolder holder);
}
void process(ProcessCallback callback) {
for (int index = mLayoutHolderMap.size() - 1; index >= 0; index--) {
final ViewHolder viewHolder = mLayoutHolderMap.keyAt(index);
final InfoRecord record = mLayoutHolderMap.removeAt(index);
if ((record.flags & FLAG_APPEAR_AND_DISAPPEAR) == FLAG_APPEAR_AND_DISAPPEAR) {
// Appeared then disappeared. Not useful for animations.
callback.unused(viewHolder);
} else if ((record.flags & FLAG_DISAPPEARED) != 0) {
// Set as "disappeared" by the LayoutManager (addDisappearingView)
if (record.preInfo == null) {
// similar to appear disappear but happened between different layout passes.
// this can happen when the layout manager is using auto-measure
callback.unused(viewHolder);
} else {
callback.processDisappeared(viewHolder, record.preInfo, record.postInfo);
}
} else if ((record.flags & FLAG_APPEAR_PRE_AND_POST) == FLAG_APPEAR_PRE_AND_POST) {
// Appeared in the layout but not in the adapter (e.g. entered the viewport)
callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
} else if ((record.flags & FLAG_PRE_AND_POST) == FLAG_PRE_AND_POST) {
// Persistent in both passes. Animate persistence
callback.processPersistent(viewHolder, record.preInfo, record.postInfo);
} else if ((record.flags & FLAG_PRE) != 0) {
// Was in pre-layout, never been added to post layout
callback.processDisappeared(viewHolder, record.preInfo, null);
} else if ((record.flags & FLAG_POST) != 0) {
// Was not in pre-layout, been added to post layout
callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
} else if ((record.flags & FLAG_APPEAR) != 0) {
// Scrap view. RecyclerView will handle removing/recycling this.
} else if (DEBUG) {
throw new IllegalStateException("record without any reasonable flag combination:/");
}
InfoRecord.recycle(record);
}
}
mViewInfoStore.process(mViewInfoProcessCallback);方法內(nèi)部就是各種判斷,并調(diào)用相應(yīng)的動(dòng)畫執(zhí)行。至此,onLayout方法的分析也告一段落,我們接著分析onDraw方法。
onDraw
在分析onDraw之前,首先了解下View的繪制流程。View的draw方法太長了,這里就不拿出來分析,我們只了解下流程,通過其源碼我們可以得出大致的流程:
draw -> drawbackground(繪制背景,不能重寫) -> onDraw(繪制自身) -> dispatchDraw(繪制子view)
-> onDrawForeground(繪制前景) -> drawDefaultFocusHighlight(繪制其他)
RecyclerView繼承了ViewGroup,內(nèi)部重寫了draw,onDraw這兩個(gè)方法,我們就來分析下:
public void draw(Canvas c) {
//調(diào)用父類的draw方法繪制自身
//這里會(huì)依次調(diào)用上面所說的流程,完成繪制
super.draw(c);
final int count = mItemDecorations.size();
for (int i = 0; i < count; i++) {
//繪制懸浮效果
mItemDecorations.get(i).onDrawOver(c, this, mState);
}
// TODO If padding is not 0 and clipChildrenToPadding is false, to draw glows properly, we
// need find children closest to edges. Not sure if it is worth the effort.
//判斷是否需要重繪
boolean needsInvalidate = false;
if (mLeftGlow != null && !mLeftGlow.isFinished()) {
//......
needsInvalidate = mLeftGlow != null && mLeftGlow.draw(c);
c.restoreToCount(restore);
}
if (mTopGlow != null && !mTopGlow.isFinished()) {
//......
}
if (mRightGlow != null && !mRightGlow.isFinished()) {
//......
}
if (mBottomGlow != null && !mBottomGlow.isFinished()) {
//......
}
// If some views are animating, ItemDecorators are likely to move/change with them.
// Invalidate RecyclerView to re-draw decorators. This is still efficient because children's
// display lists are not invalidated.
//如果有子view的動(dòng)畫還在執(zhí)行,則需要重繪
if (!needsInvalidate && mItemAnimator != null && mItemDecorations.size() > 0
&& mItemAnimator.isRunning()) {
needsInvalidate = true;
}
//如果需要重繪,發(fā)送重繪信息
if (needsInvalidate) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
public void onDraw(Canvas c) {
super.onDraw(c);
final int count = mItemDecorations.size();
for (int i = 0; i < count; i++) {
mItemDecorations.get(i).onDraw(c, this, mState);
}
}
先看下draw方法,首先調(diào)用super.draw(canvas c),完成自身的繪制(這里會(huì)調(diào)用onDraw方法,完成divider的繪制);然后調(diào)用ItemDecoration的onDrawOver(需要我們自己實(shí)現(xiàn))來繪制子view的懸浮效果,最后進(jìn)行一些列判斷,判斷是否需要重繪。
接著看onDraw方法,這里首先調(diào)用super.onDraw(c);來繪制。然后調(diào)用ItemDecoration的onDraw方法來繪制分割線等效果,這個(gè)onDraw也是需要我們自己去實(shí)現(xiàn)的。
至此RecyclerView的整個(gè)繪制流程就分析完了,由于這里只側(cè)重整體的流程,有很多詳細(xì)的地方?jīng)]有分析到,如有興趣可自行分析細(xì)節(jié)??偨Y(jié)下幾個(gè)關(guān)鍵點(diǎn):
1、RecyclerView將子view的measure工作交給了LayoutManager來完成的,如果沒有設(shè)置LayoutManager,則列表不會(huì)有任何顯示。
2、RecyclerView的子view繪制分為正序繪制和倒序繪制。首先是確定錨點(diǎn),然后由錨點(diǎn)分別向兩邊繪制,因此fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable)方法至少會(huì)執(zhí)行兩次,如果繪制完了還有剩余空間,則繼續(xù)執(zhí)行fill方法。
3、fill方法的核心邏輯在于layoutChunk方法中,layoutChunk方法中首先會(huì)通過layoutState.next(recycler)獲取view,首先是從scrap、cache以及RecyclerViewPool等緩存中獲取,如果這些緩存中都沒有,則調(diào)用adapter的createViewHolder方法創(chuàng)建一個(gè)ViewHolder
4、如果沒有為RecyclerView設(shè)置精確寬高值,則onMeasure方法會(huì)完成子view的測量以及布局(如有設(shè)置ItemDecoration,則會(huì)將ItemDecoration的getItemOffsets方法中設(shè)置的left,top,right,bottom偏移量也算在內(nèi)),在onLayout方法中僅僅只需要完成一些清理工作以及子view進(jìn)出動(dòng)畫的處理工作;如果設(shè)置了精確的寬高值,則對子view的測量以及布局工作會(huì)在onLayout中完成。
5、RecyclerView重寫了draw和onDraw方法,如果設(shè)置了ItemDecoration,并重寫了onDrawOver以及onDraw方法,則在draw方法中會(huì)調(diào)用ItemDecoration的onDrawOver繪制懸浮效果;在onDraw方法中調(diào)用ItemDecoration的onDraw方法繪制子view之間的分割線以及其他裝飾效果。