系列文章:
前言
RecyclerView是Google在2014年的IO大會(huì)中提出來(lái)的,可以認(rèn)為是用來(lái)代替ListView的,其是support-v7包中的組件,但隨著AndroidX(谷歌對(duì) android.support.xxx 包的整理產(chǎn)物,因?yàn)橹皊upport包的管理較為混亂,所以谷歌推出了AndroidX)的出現(xiàn),我們需要將項(xiàng)目中對(duì)其的使用遷移到 AndroidX 上。RecyclerView的繼承關(guān)系如下圖所示:

相比ListView的兩級(jí)緩存,RecyclerView做到了四級(jí)緩存,而且整體上的架構(gòu)做到了解耦,每個(gè)模塊分別負(fù)責(zé)不同的功能實(shí)現(xiàn)。其中Adapter負(fù)責(zé)提供數(shù)據(jù),包括創(chuàng)建ViewHolder和綁定數(shù)據(jù),LayoutManager負(fù)責(zé)ItemView的測(cè)量和布局,ItemAnimator負(fù)責(zé)每個(gè)ItemView的動(dòng)畫(huà),這樣功能性的解耦讓RecyclerView使用十分方便,也方便了開(kāi)發(fā)者擴(kuò)展。這里需要提一下ViewHolder,對(duì)于Adapter來(lái)說(shuō),一個(gè)ViewHolder就對(duì)應(yīng)一個(gè)data。
有關(guān)RecyclerView的使用這里也不做過(guò)多分析,具體可以參考Android RecyclerView 使用完全解析。
繪制過(guò)程
類似ListView,RecyclerView本質(zhì)上還是一個(gè)View,因此在繪制的過(guò)程中還是分為以下三步:onMeasure、onLayout、onDraw,下面先看下其onMeasure方法:
繪制第一步:onMeasure方法
protected void onMeasure(int widthSpec, int heightSpec) {
if (mLayout == null) {
// 情況1
}
if (mLayout.mAutoMeasure) {
// 情況2
} else {
// 情況3
}
onMeasure方法中主要是分了三種情況,情況1是mLayout(LayoutManager對(duì)象)為null,LayoutManager負(fù)責(zé)控制 RecyclerView 中 item 的測(cè)量和布局,當(dāng)LayoutManager為空時(shí),RecyclerView是不能顯示任何數(shù)據(jù)的(原因后續(xù)解釋)。
另外兩種情況是mLayout對(duì)象不為空,第二種情況是LayoutManager開(kāi)啟了自動(dòng)測(cè)量,第三種情況是LayoutManager沒(méi)有開(kāi)啟自動(dòng)測(cè)量。
onMeasure情況1:LayoutManager為null
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
return;
}
void defaultOnMeasure(int widthSpec, int heightSpec) {
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);
}
這種情況下直接調(diào)用了defaultOnMeasure方法,該方法中通過(guò)LayoutManager.choose()方法來(lái)計(jì)算寬高值,然后調(diào)用setMeasuredDimension()設(shè)置寬高:
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方法就是通過(guò)RecyclerView的不同測(cè)量模式來(lái)選取不同的值。
onMeasure情況2:LayoutManager開(kāi)啟了自動(dòng)測(cè)量
if (mLayout.mAutoMeasure) {
// 先獲取測(cè)量模式
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
// 是否可以跳過(guò)測(cè)量
final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
&& heightMode == MeasureSpec.EXACTLY;
// 調(diào)用LayoutManager.onMeasure方法測(cè)量
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
// 如果可以跳過(guò)測(cè)量或者adapter為null,則直接返回
if (skipMeasure || mAdapter == null) {
return;
}
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
}
mLayout.setMeasureSpecs(widthSpec, heightSpec);
mState.mIsMeasuring = true;
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.
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);
}
}
這種情況下,首先調(diào)用LayoutManager.onMeasure方法測(cè)量,但是Android官方的三種LayoutManager(LinearLayoutManager、GridLayoutManager、StaggeredGridLayoutManager)都沒(méi)有復(fù)寫(xiě)此方法,此方法源碼:
public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) {
mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
}
可以看到LayoutManager.onMeasure方法默認(rèn)是調(diào)用的RecyclerView的defaultOnMeasure方法,前面已經(jīng)介紹過(guò)此方法了。
接下來(lái)判斷mState.mLayoutStep這個(gè)變量,即當(dāng)前繪制狀態(tài),如果為State.STEP_START,那么便會(huì)執(zhí)行dispatchLayoutStep1方法,隨后又調(diào)用了dispatchLayoutStep2方法,最后如果需要二次測(cè)量的話,那么會(huì)再調(diào)用一次dispatchLayoutStep2方法。
ViewGroup的onMeasure方法的作用通常來(lái)說(shuō)有兩個(gè):一是測(cè)量自身的寬高,從RecyclerView來(lái)看,它將自己的測(cè)量工作托管給了LayoutManager的onMeasure方法。所以,我們?cè)谧远xLayoutManager時(shí),需要注意onMeasure方法的存在,不過(guò)官方提供的幾個(gè)LayoutManager,都沒(méi)有重寫(xiě)這個(gè)方法。二是測(cè)量子View的寬高,不過(guò)到目前為止我們還沒(méi)有看到具體的實(shí)現(xiàn)。
前面提到了mState.mLayoutStep這個(gè)變量,mState是RecyclerView中State類的對(duì)象,mLayoutStep有三個(gè)狀態(tài),其三個(gè)狀態(tài)正好和三個(gè)dispatchLayoutStep方法(還有一個(gè)dispatchLayoutStep3)一一對(duì)應(yīng):
| State.mLayoutStep | dispatchLayoutStep | 含義說(shuō)明 |
|---|---|---|
| STEP_START | dispatchLayoutStep1 |
STEP_START是State.mLayoutStep的默認(rèn)值,執(zhí)行完dispatchLayoutStep1后會(huì)將該狀態(tài)置為STEP_LAYOUT
|
| STEP_LAYOUT | dispatchLayoutStep2 | 表明處于layout階段,調(diào)用dispatchLayoutStep2對(duì)RecyclerView的子view進(jìn)行l(wèi)ayout,執(zhí)行完之后會(huì)將該狀態(tài)置為STEP_ANIMATIONS
|
| STEP_ANIMATIONS | dispatchLayoutStep3 | 表明處于執(zhí)行動(dòng)畫(huà)階段,調(diào)用dispatchLayoutStep3之后會(huì)將該狀態(tài)置再次變?yōu)?code>STEP_START
|
dispatchLayoutStep1方法
RecyclerView處于State.STEP_START狀態(tài)時(shí),會(huì)調(diào)用dispatchLayoutStep1方法,其源碼如下:
private void dispatchLayoutStep1() {
...
processAdapterUpdatesAndSetAnimationFlags();
...
if (mState.mRunSimpleAnimations) {
// Step 0:找出所有未移除的ItemView,進(jìn)行預(yù)布局
}
if (mState.mRunPredictiveAnimations) {
// Step 1:預(yù)布局
} else {
clearOldPositions();
}
onExitLayoutOrScroll();
resumeRequestLayout(false);
mState.mLayoutStep = State.STEP_LAYOUT;
}
可以看到dispatchLayoutStep1方法主要是根據(jù)mState.mRunSimpleAnimations和mState.mRunPredictiveAnimations兩個(gè)值做出相應(yīng)邏輯處理,而processAdapterUpdatesAndSetAnimationFlags()方法中計(jì)算了這兩個(gè)值:
private void processAdapterUpdatesAndSetAnimationFlags() {
...
mState.mRunSimpleAnimations = mFirstLayoutComplete && mItemAnimator != null &&
(mDataSetHasChangedAfterLayout || animationTypeSupported ||
mLayout.mRequestedSimpleAnimations) &&
(!mDataSetHasChangedAfterLayout || mAdapter.hasStableIds());
mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations &&
animationTypeSupported && !mDataSetHasChangedAfterLayout &&
predictiveItemAnimationsEnabled();
}
此方法中我們注意下mFirstLayoutComplete變量,mRunSimpleAnimations和mFirstLayoutComplete有關(guān),而mRunPredictiveAnimations 又和mRunSimpleAnimations有關(guān),第一次繪制流程還未完成,mFirstLayoutComplete 為false,因此mRunSimpleAnimations 和mRunPredictiveAnimations都為false,所以不會(huì)加載動(dòng)畫(huà),這也是很明顯的道理,布局還未加載完成,怎么會(huì)進(jìn)行加載動(dòng)畫(huà)呢。
dispatchLayoutStep1方法的最后一句是mState.mLayoutStep = State.STEP_LAYOUT,可見(jiàn)執(zhí)行完之后其改變了當(dāng)前繪制狀態(tài)。
dispatchLayoutStep1的其余具體邏輯和ItemAnimator有關(guān),后續(xù)分析ItemAnimator再詳細(xì)說(shuō)明。
dispatchLayoutStep2方法
onMeasure方法執(zhí)行完dispatchLayoutStep1后,接著執(zhí)行dispatchLayoutStep2方法,其中對(duì)RecyclerView的子View進(jìn)行了layout:
private void dispatchLayoutStep2() {
...
mState.mItemCount = mAdapter.getItemCount();
mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
// Step 2: Run layout
mState.mInPreLayout = false;
mLayout.onLayoutChildren(mRecycler, mState);
...
mState.mLayoutStep = State.STEP_ANIMATIONS;
...
}
此方法我們重點(diǎn)關(guān)注mLayout.onLayoutChildren(mRecycler, mState)這句話,onLayoutChildren 這個(gè)函數(shù)由 LayoutManager的子類實(shí)現(xiàn),主要是決定了子View的布局方式,具體的相應(yīng)代碼邏輯可以查看Android官方LayoutManager之一LinearLayoutManager,后續(xù)我們?cè)僭敿?xì)說(shuō)明,此方法最后又將mState.mLayoutStep置為State.STEP_ANIMATIONS。
onMeasure情況3:LayoutManager沒(méi)有開(kāi)啟自動(dòng)測(cè)量
最后再來(lái)看看LayoutManager沒(méi)有開(kāi)啟自動(dòng)測(cè)量的情況:
if (mHasFixedSize) {
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
return;
}
// custom onMeasure
if (mAdapterUpdateDuringMeasure) {
eatRequestLayout();
processAdapterUpdatesAndSetAnimationFlags();
if (mState.mRunPredictiveAnimations) {
mState.mInPreLayout = true;
} else {
// consume remaining updates to provide a consistent state with the layout pass.
mAdapterHelper.consumeUpdatesInOnePass();
mState.mInPreLayout = false;
}
mAdapterUpdateDuringMeasure = false;
resumeRequestLayout(false);
}
if (mAdapter != null) {
mState.mItemCount = mAdapter.getItemCount();
} else {
mState.mItemCount = 0;
}
eatRequestLayout();
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
resumeRequestLayout(false);
mState.mInPreLayout = false; // clear
如果mHasFixedSize為true(setHasFixedSize方法可以設(shè)置此變量),就直接調(diào)用LayoutManager.onMeasure方法進(jìn)行測(cè)量;如果mHasFixedSize為false,則先判斷是否有數(shù)據(jù)更新(mAdapterUpdateDuringMeasure變量),有的話先處理數(shù)據(jù)更新,再調(diào)用LayoutManager.onMeasure方法進(jìn)行測(cè)量。
繪制第二步:onLayout方法
測(cè)量過(guò)程之后便是layout過(guò)程,onLayout方法比較簡(jiǎn)單,只有下面幾行,最重要的邏輯都在dispatchLayout方法中了:
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方法
void dispatchLayout() {
if (mAdapter == null) {
Log.e(TAG, "No adapter attached; skipping layout");
// leave the state in START
return;
}
if (mLayout == null) {
Log.e(TAG, "No layout manager attached; skipping layout");
// leave the state in START
return;
}
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()方法也簡(jiǎn)潔明了,首先分別判斷了mAdapter和mLayout,只要其中一個(gè)為null,則直接返回;這里需要注意到當(dāng)mLayout為null時(shí),即RecyclerView沒(méi)有設(shè)置LayoutManager時(shí),dispatchLayout方法直接返回了,因此不會(huì)處理layout過(guò)程,自然也解釋了為什么不設(shè)置LayoutManager,RecyclerView就不會(huì)加載數(shù)據(jù)。
dispatchLayout()方法中保證了RecyclerView必須經(jīng)歷三個(gè)過(guò)程,分別是dispatchLayoutStep1、dispatchLayoutStep2、dispatchLayoutStep3,前兩個(gè)方法我們?cè)趏nMeasure過(guò)程中已經(jīng)提到了,下面看下dispatchLayoutStep3源碼:
private void dispatchLayoutStep3() {
...
mState.mLayoutStep = State.STEP_START;
...
}
它重新將mState.mLayoutStep的狀態(tài)置為State.STEP_START,保證了第二次layout時(shí)仍會(huì)執(zhí)行dispatchLayoutStep1、dispatchLayoutStep2、dispatchLayoutStep3三個(gè)方法,剩下的工作主要是和Item動(dòng)畫(huà)相關(guān)的,和ItemAnimator有關(guān)。
如果在RecyclerView中如果開(kāi)啟了自動(dòng)測(cè)量,在measure階段就已經(jīng)將子View布局完成了,如果沒(méi)有開(kāi)啟自動(dòng)測(cè)量,那么會(huì)在layout階段再布局子View。
繪制第三步:draw方法
public void draw(Canvas c) {
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.
......
}
draw大概可以分為三步:
- 調(diào)用super.draw,將子View的繪制分發(fā)給View類;在View類draw方法中又會(huì)回調(diào)RecyclerView的onDraw方法:
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);
}
}
此方法中又將分割線的繪制分發(fā)給ItemDecoration的onDraw方法。
- 調(diào)用ItemDecoration的onDrawOver方法。通過(guò)這個(gè)方法,我們?cè)诿總€(gè)Item上自定義一些裝飾。
- 如果RecyclerView調(diào)用了setClipToPadding,會(huì)實(shí)現(xiàn)一種特殊的滑動(dòng)效果--每個(gè)ItemView可以滑動(dòng)到padding區(qū)域。
繪制流程補(bǔ)充舉例:LinearLayoutManager.onLayoutChildren方法
在講解ListView的繪制過(guò)程中,我們的重心就是layoutChildren方法,講解了怎么對(duì)子View布局,到現(xiàn)在為止我們還沒(méi)有進(jìn)入RecyclerView對(duì)子View布局的講解,前面描述dispatchLayoutStep2過(guò)程中,我們提到了onLayoutChildren 這個(gè)函數(shù)由LayoutManager的子類實(shí)現(xiàn),那么下面我們就以LayoutManager的子類LinearLayoutManager為例,介紹下onLayoutChildren的工作過(guò)程:
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
...
// 第一步
resolveShouldLayoutReverse();
if (!mAnchorInfo.mValid || mPendingScrollPosition != NO_POSITION ||
mPendingSavedState != null) {
mAnchorInfo.reset();
mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
// 計(jì)算錨點(diǎn)位置和坐標(biāo)
updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
mAnchorInfo.mValid = true;
}
...
// 第二步
onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);
detachAndScrapAttachedViews(recycler);
mLayoutState.mInfinite = resolveIsInfinite();
mLayoutState.mIsPreLayout = state.isPreLayout();
// 第三步
if (mAnchorInfo.mLayoutFromEnd) {
// fill towards start
updateLayoutStateToFillStart(mAnchorInfo);
mLayoutState.mExtra = extraForStart;
fill(recycler, mLayoutState, state, false);
...
// fill towards end
updateLayoutStateToFillEnd(mAnchorInfo);
mLayoutState.mExtra = extraForEnd;
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
fill(recycler, mLayoutState, state, false);
...
} else {
// fill towards end
updateLayoutStateToFillEnd(mAnchorInfo);
mLayoutState.mExtra = extraForEnd;
fill(recycler, mLayoutState, state, false);
...
// fill towards start
updateLayoutStateToFillStart(mAnchorInfo);
mLayoutState.mExtra = extraForStart;
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
fill(recycler, mLayoutState, state, false);
...
}
...
}
子方法的開(kāi)頭注釋說(shuō)了整個(gè)大概的流程:
- 1、確定錨點(diǎn)信息,找到一個(gè)錨點(diǎn)坐標(biāo)與錨點(diǎn)(如果是在線性布局中,相當(dāng)于找到當(dāng)前界面內(nèi)第一個(gè)View,與第一個(gè)view的坐標(biāo)點(diǎn))
- 2、根據(jù)錨點(diǎn)信息,進(jìn)行填充
- 3、填充完后,如果還有剩余的可填充大小,再填充一次
第一步:確定錨點(diǎn)信息
onLayoutChildren方法中大概做了三件事,第一步是確定錨點(diǎn)信息,首先執(zhí)行resolveShouldLayoutReverse()方法判斷是否需要倒著繪制:
private void resolveShouldLayoutReverse() {
if (mOrientation == VERTICAL || !isLayoutRTL()) {
mShouldReverseLayout = mReverseLayout;
} else {
mShouldReverseLayout = !mReverseLayout;
}
}
默認(rèn)情況下mReverseLayout為false,是不會(huì)倒著繪制的。手動(dòng)調(diào)用setReverseLayout()方法,可以改變mReverseLayout的值:
public void setReverseLayout(boolean reverseLayout) {
assertNotInLayoutOrScroll(null);
if (reverseLayout == mReverseLayout) {
return;
}
mReverseLayout = reverseLayout;
requestLayout();
}
接下來(lái)便是通過(guò)updateAnchorInfoForLayout()方法來(lái)計(jì)算錨點(diǎn)信息,這里對(duì)錨點(diǎn)做一些解釋,mAnchorInfo(AnchorInfo類對(duì)象)就是我們要的錨點(diǎn):
AnchorInfo(錨點(diǎn))
class AnchorInfo {
int mPosition;
int mCoordinate;
boolean mLayoutFromEnd;
boolean mValid;
...
}
AnchorInfo中有四個(gè)重要的成員變量,mPositionm和mCoordinate易懂;mValid的默認(rèn)值是false,一次測(cè)量之后設(shè)為true,onLayout完成后會(huì)回調(diào)執(zhí)行reset方法,又變?yōu)閒alse;mLayoutFromEnd變量在計(jì)算錨點(diǎn)過(guò)程中有如下賦值:
mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd
mShouldReverseLayout默認(rèn)是false,mStackFromEnd默認(rèn)是false,除非手動(dòng)調(diào)用setStackFromEnd()方法,兩個(gè)變量都是false,異或運(yùn)算之后還是false。
接下來(lái)又調(diào)用了updateAnchorInfoForLayout()方法,此方法用來(lái)更新錨點(diǎn)信息,一共有三種計(jì)算方法:
private void updateAnchorInfoForLayout(RecyclerView.Recycler recycler, RecyclerView.State state,
AnchorInfo anchorInfo) {
// 第一種計(jì)算方法
if (updateAnchorFromPendingData(state, anchorInfo)) {
return;
}
// 第二種計(jì)算方法
if (updateAnchorFromChildren(recycler, state, anchorInfo)) {
return;
}
// 第三種計(jì)算方法
anchorInfo.assignCoordinateFromPadding();
anchorInfo.mPosition = mStackFromEnd ? state.getItemCount() - 1 : 0;
}
- 第一種計(jì)算方法:如果存在未決的滾動(dòng)位置或保存的狀態(tài),從該數(shù)據(jù)更新錨點(diǎn)信息并返回true
- 第二種計(jì)算方法:根據(jù)子View來(lái)更新錨點(diǎn)信息,如果一個(gè)子View有焦點(diǎn),則根據(jù)其來(lái)計(jì)算錨點(diǎn)信息;如有一個(gè)子View沒(méi)有錨點(diǎn),則根據(jù)布局方向選取第一個(gè)View或最后一個(gè)View
- 第三種計(jì)算方法:前兩種都未采用,則采取默認(rèn)的第三種計(jì)算方式
第二步:回收子View
第二步是調(diào)用detachAndScrapAttachedViews()方法對(duì)所有的ItemView進(jìn)行回收,這部分的內(nèi)容屬于RecyclerView緩存機(jī)制的部分,后面解釋緩存的時(shí)候再說(shuō)。
第三步:子View填充(fill)
第三步是便是子View的內(nèi)容填充了,首先是根據(jù)mAnchorInfo.mLayoutFromEnd來(lái)判斷是否逆向填充,無(wú)論是正向還是逆向,都調(diào)用了至少兩次fill()方法來(lái)進(jìn)行填充;如果是正向填充的話先向下填充,再向上填充;逆向的話和正向相反,兩次fill之后,如果還有剩余空間還會(huì)再調(diào)用一次fill進(jìn)行補(bǔ)充。我們來(lái)結(jié)合一張圖了解下錨點(diǎn)和fill之間的關(guān)系:
下面我們來(lái)看看fill方法:
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
...
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
...
layoutChunk(recycler, state, layoutState, layoutChunkResult);
...
}
...
}
fill中真正填充子View的方法是layoutChunk(),再來(lái)看看layoutChunk()方法,源碼如下:
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
// 第一步
View view = layoutState.next(recycler);
...
// 第二步
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);
...
// 第四步
layoutDecoratedWithMargins(view, left, top, right, bottom);
...
}
layoutChunk方法中的主要工作可以分為以下幾步:
- 1.調(diào)用LayoutState的next方法獲得一個(gè)ItemView,next方法的參數(shù)是Recycler對(duì)象,Recycler正是RecyclerView的緩存核心實(shí)現(xiàn),可見(jiàn)RecyclerView中的緩存機(jī)制從此處開(kāi)始,后續(xù)分析緩存時(shí)再來(lái)具體查看
- 2.如果RecyclerView是第一次布局children(
layoutState.mScrapList == null),會(huì)調(diào)用addView()方法將View添加到RecyclerView中 - 3.調(diào)用
measureChildWithMargins方法,測(cè)量每個(gè)ItemView的寬高,這里考慮了margin屬性和ItemDecoration的offset - 4.調(diào)用了
layoutDecoratedWithMargins對(duì)子View完成了布局
其中有很多細(xì)節(jié)并未展開(kāi)描述,后續(xù)分析緩存機(jī)制時(shí)再進(jìn)行講解。
總結(jié)
- RecyclerView的measure過(guò)程分為三種情況,每種情況都有執(zhí)行過(guò)程,一般情況下會(huì)走自動(dòng)測(cè)量流程
- 自動(dòng)測(cè)量根據(jù)
mState.mLayoutStep狀態(tài)值,調(diào)用不同的dispatchLayoutStep方法 - layout過(guò)程也根據(jù)
mState.mLayoutStep狀態(tài)來(lái)調(diào)用不同的dispatchLayoutStep方法 - draw過(guò)程大概可以分為三步
- 布局子View先獲取錨點(diǎn)信息,再根據(jù)錨點(diǎn)信息和布局方向進(jìn)行子View填充
至此基本完成了RecyclerView三大繪制流程,還有最重要的緩存機(jī)制沒(méi)有講解,見(jiàn)后續(xù)。