注意:本文基于25.4.0源碼
RecyclerView的源碼非常復(fù)雜玉凯,僅僅RecyclerView.java一個(gè)文件就有一萬(wàn)多行势腮,閱讀起來(lái)十分困難。不過(guò)RecyclerView作為一個(gè)View漫仆,再?gòu)?fù)雜也得遵循View的基本法:三大流程捎拯。所以我們從View繪制的三大流程入手就會(huì)輕松許多。
Measure
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
//LayoutManager為空
if (mLayout == null) {
//設(shè)置默認(rèn)寬高
defaultOnMeasure(widthSpec, heightSpec);
return;
}
//默認(rèn)自動(dòng)測(cè)量
if (mLayout.mAutoMeasure) {
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
&& heightMode == MeasureSpec.EXACTLY;
//通過(guò)LayoutManger計(jì)算寬高
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
if (skipMeasure || mAdapter == null) {
return;
}
if (mState.mLayoutStep == State.STEP_START) {
/**
* 處理adpter更新
* 決定是否要執(zhí)行動(dòng)畫(huà)
* 保存動(dòng)畫(huà)信息
* 如果有必要的話盲厌,進(jìn)行預(yù)布局
*/
dispatchLayoutStep1();
}
// set dimensions in 2nd step. Pre-layout should happen with old dimensions for
// consistency
mLayout.setMeasureSpecs(widthSpec, heightSpec);
mState.mIsMeasuring = true;
//進(jìn)行真正的測(cè)量和布局
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);
}
} else {
//非自動(dòng)測(cè)量
}
}
先進(jìn)行整體描述一下measure
流程
- 如果未設(shè)置
LayoutManger
署照,設(shè)置默認(rèn)寬高祸泪,結(jié)束,否則繼續(xù)向下 - 分為自動(dòng)測(cè)量和非自動(dòng)測(cè)量?jī)煞N情況建芙,一般情況都為自動(dòng)測(cè)量没隘,我們這里也只分析自動(dòng)測(cè)量情況
- 通過(guò)
LayoutManger
初步計(jì)算寬高(一般使用默認(rèn)寬高計(jì)算方式),如果RecyclerView的寬高都是EXACTLY
的禁荸,則測(cè)量結(jié)束右蒲,否則繼續(xù)測(cè)量 -
dispatchLayoutStep1
處理adpter更新,決定是否要執(zhí)行動(dòng)畫(huà)赶熟,保存動(dòng)畫(huà)信息瑰妄,處理預(yù)布局 -
dispatchLayoutStep2
進(jìn)行真正測(cè)量布局,對(duì)子view(itemView)進(jìn)行measure
和layout
钧大,確定子view的寬高和位置
- 如果RecyclerView仍然有非精確的寬和高翰撑,或者這里還有至少一個(gè)Child還有非精確的寬和高,再進(jìn)行一次測(cè)量
下面進(jìn)行關(guān)鍵點(diǎn)梳理
設(shè)置默認(rèn)寬高
mLayout
就是recyclerView.setLayoutManager(layoutManager)
中設(shè)置的layoutManager
啊央。當(dāng)mLayout
為null
的時(shí)候眶诈,使用默認(rèn)測(cè)量方法,這個(gè)時(shí)候RecyclerView空白什么都不會(huì)顯示
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);
}
默認(rèn)測(cè)量時(shí)瓜饥,我們可以看到會(huì)使用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);
}
}
很簡(jiǎn)單逝撬,這里不做過(guò)多介紹
自動(dòng)測(cè)量
當(dāng)mLayout
不為null
的時(shí)候,會(huì)進(jìn)行判斷是否進(jìn)行自動(dòng)測(cè)量乓土。mLayout.mAutoMeasure
默認(rèn)為true
宪潮,表示自動(dòng)測(cè)量,例如LinearLayoutManager
趣苏,除非你自定義LayoutManager或者調(diào)用setAutoMeasureEnabled(false)
狡相。
public LinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
setOrientation(orientation);
setReverseLayout(reverseLayout);
setAutoMeasureEnabled(true);
}
初步測(cè)量寬高
自動(dòng)測(cè)量時(shí),先調(diào)用mLayout.onMeasure
食磕,委托給mLayout
進(jìn)行測(cè)量尽棕。
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
&& heightMode == MeasureSpec.EXACTLY;
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
if (skipMeasure || mAdapter == null) {
return;
}
public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) {
mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
}
onMeasure
方法默認(rèn)使用RecyclerView的默認(rèn)測(cè)量,和上面一樣彬伦。
如果RecyclerView的寬高都是固定值或者adapter
為空滔悉,此時(shí)測(cè)量結(jié)束。否則調(diào)用dispatchLayoutStep1
和dispatchLayoutStep2
繼續(xù)進(jìn)行測(cè)量单绑。
下面繼續(xù)看dispatchLayoutStep1
和dispatchLayoutStep2
回官,其實(shí)onLayout
中還有一個(gè)dispatchLayoutStep3
,這三個(gè)方法共同組成了RecyclerView的繪制布局過(guò)程搂橙。
-
dispatchLayoutStep1
處理adpter更新歉提,決定是否要執(zhí)行動(dòng)畫(huà),保存動(dòng)畫(huà)信息,如果有必要的話唯袄,進(jìn)行預(yù)布局弯屈。方法結(jié)束狀態(tài)置為State.STEP_LAYOUT
-
dispatchLayoutStep1
進(jìn)行真正的測(cè)量和布局操作。方法結(jié)束狀態(tài)置為State.STEP_ANIMATIONS
-
dispatchLayoutStep1
觸發(fā)動(dòng)畫(huà)并進(jìn)行任何必要的清理恋拷。方法結(jié)束狀態(tài)重置為State.STEP_START
dispatchLayoutStep1
dispatchLayoutStep1
方法主要和動(dòng)畫(huà)和預(yù)布局相關(guān),這里暫時(shí)先略過(guò)厅缺,直接看dispatchLayoutStep2
蔬顾。
dispatchLayoutStep2
private void dispatchLayoutStep2() {
...
// Step 2: Run layout
mState.mInPreLayout = false;
mLayout.onLayoutChildren(mRecycler, mState);
...
}
我們可以看到View的測(cè)量和布局委托給mLayout
進(jìn)行處理,從這里可以看出RecyclerView的靈活性湘捎,只要替換不同的LayoutManger
就能夠?qū)崿F(xiàn)不同的布局诀豁,相當(dāng)靈活。onLayoutChildren
方法默認(rèn)為空窥妇,需要各個(gè)實(shí)現(xiàn)類(lèi)去實(shí)現(xiàn)舷胜。
onLayoutChildren
主要用來(lái)對(duì)RecyclerView的ItemView進(jìn)行measure
和layout
,后面再進(jìn)行詳細(xì)介紹活翩。
根據(jù)子View的寬高計(jì)算自身的寬高
dispatchLayoutStep2
成功之后烹骨,我們已經(jīng)完成對(duì)RecyclerView的子View的測(cè)量和布局,下面就可以根據(jù)子view的寬高來(lái)計(jì)算自己的寬高了材泄。這里比較簡(jiǎn)單就不做具體介紹了沮焕,主要需要注意的是DecoratedBounds,即recyclerView.addItemDecoration(itemDecoration)
中的itemDecoration
所需占的空間拉宗。
二次測(cè)量
是否需要二次測(cè)量和具體LayoutManger
有關(guān)峦树,由LayoutManger
來(lái)具體實(shí)現(xiàn),以LinearLayoutManager
舉例
@Override
boolean shouldMeasureTwice() {
return getHeightMode() != View.MeasureSpec.EXACTLY
&& getWidthMode() != View.MeasureSpec.EXACTLY
&& hasFlexibleChildInBothOrientations();
}
果RecyclerView仍然有非精確的寬和高旦事,或者這里還有至少一個(gè)Child還有非精確的寬和高魁巩,我們就需要再次測(cè)量。
LinearLayoutManager的onLayoutChildren
下面我們具體介紹一下LinearLayoutManager的onLayoutChildren實(shí)現(xiàn)姐浮,來(lái)看一下LinearLayoutManager是怎么對(duì)子View進(jìn)行布局的谷遂。
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
...
if (!mAnchorInfo.mValid || mPendingScrollPosition != NO_POSITION ||
mPendingSavedState != null) {
mAnchorInfo.reset();
//Item布局方向
mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
// calculate anchor position and coordinate
//查找錨點(diǎn),錨點(diǎn)可以看做是布局的一個(gè)起始點(diǎn)单料,以這個(gè)點(diǎn)為基點(diǎn)埋凯,分別向上和向下進(jìn)行測(cè)量布局
updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
mAnchorInfo.mValid = true;
}
...
//將屏幕上顯示的Item移除,并將對(duì)應(yīng)viewholder暫存起來(lái)
detachAndScrapAttachedViews(recycler);
mLayoutState.mInfinite = resolveIsInfinite();
mLayoutState.mIsPreLayout = state.isPreLayout();
if (mAnchorInfo.mLayoutFromEnd) {
//表示RecyclerView是從下往上位置為0,1,2...順序
// fill towards start
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
updateLayoutStateToFillEnd(mAnchorInfo);
mLayoutState.mExtra = 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.mExtra = extraForStart;
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
}
} else {
//與上面布局方法類(lèi)似扫尖,只是方向相反
...
}
...
}
- 首先確定布局方向白对,
updateAnchorInfoForLayout
查找錨點(diǎn)。布局方向用來(lái)確定Item是從上往下順序顯示還是從下往上順序顯示换怖;錨點(diǎn)用來(lái)確認(rèn)布局起始點(diǎn) -
detachAndScrapAttachedViews
如果屏幕上有Item顯示甩恼,則將它們?nèi)恳瞥⑶視捍嫫饋?lái) - 以錨點(diǎn)坐標(biāo)為起始點(diǎn),從錨點(diǎn)處分別向上和向下布局Item条摸。
fill()
方法用來(lái)做具體添加child操作悦污,并對(duì)child進(jìn)行測(cè)量和布局
Layout
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
dispatchLayout();
TraceCompat.endSection();
mFirstLayoutComplete = true;
}
Layout
非常簡(jiǎn)單,主要通過(guò)dispatchLayout
實(shí)現(xiàn)钉蒲。
void dispatchLayout() {
...
mState.mIsMeasuring = false;
if (mState.mLayoutStep == State.STEP_START) {
//measure階段未對(duì)children布局
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.
//執(zhí)行過(guò)布局但size有改變
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else {
// always make sure we sync them (to ensure mode is exact)
//執(zhí)行過(guò)布局且布局未發(fā)生變化
mLayout.setExactMeasureSpecsFrom(this);
}
dispatchLayoutStep3();
}
這個(gè)方法很簡(jiǎn)單切端,主要保證RecyclerView必須經(jīng)歷三個(gè)過(guò)程--dispatchLayoutStep1、dispatchLayoutStep2顷啼、dispatchLayoutStep3踏枣。如果開(kāi)啟自動(dòng)測(cè)量就會(huì)在measure
階段對(duì)children進(jìn)行布局,如果未開(kāi)啟自動(dòng)測(cè)量layout
階段就會(huì)對(duì)children進(jìn)行布局钙蒙。
private void dispatchLayoutStep3() {
mState.mLayoutStep = State.STEP_START;
if (mState.mRunSimpleAnimations) {
執(zhí)行動(dòng)畫(huà)
...
}
清除狀態(tài)和無(wú)用信息
...
}
Draw
Draw流程主要處理的就是ItemDecoration的一些繪制操作茵瀑,類(lèi)似分割線、懸浮title之類(lèi)躬厌。
@Override
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);
}
...
}
是不是很熟悉马昨,這里就是ItemDecoration
的onDrawOver
方法,children的繪制在super.draw(c)
中扛施,可以看出onDrawOver
的繪制是在最上層鸿捧。
@Override
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);
}
}
這里繪制的是ItemDecoration
的onDraw
方法。