閱讀本文您大概需要4.33分鐘。
相關(guān)系列文章
在上一篇文章中主要講解了RecyclerView內(nèi)部的大體設(shè)計(jì)結(jié)構(gòu)。因?yàn)槭菑慕Y(jié)構(gòu)出發(fā),所以多少對(duì)細(xì)節(jié)講的云里霧里。那么從這一篇開始要落實(shí)到代碼細(xì)節(jié)中了。
既然RecyclerView是一個(gè)GroupView,那么也就是一個(gè)View。那么View的繪制過程是measure到layout到draw的一個(gè)順序。然而一個(gè)GroupView的目的是盛放其它View的,那么最主要的還是其measure和layout過程。那么我們今天就來看看RecyclerView的measure過程。
PS:源碼版本為24.1.1,如果下面與你的源碼有出入,請(qǐng)核實(shí)版本是否相同。
RecyclerView的Measure過程
如果你這個(gè)時(shí)候也打開了源碼,你應(yīng)該會(huì)發(fā)現(xiàn)RecyclerView的onMeasure方法很長(zhǎng)。那么我們先將其分情況討論。
- 沒有LayoutManager的情況
- 有LayoutManager并開啟了自動(dòng)測(cè)量
- 有LayoutManager但沒有開啟自動(dòng)測(cè)量
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
if (mLayout == null) {
// 1. 沒有LayoutManager的情況
defaultOnMeasure(widthSpec, heightSpec);
return;
}
if (mLayout.mAutoMeasure) {
// 2. 有LayoutManager并開啟了自動(dòng)測(cè)量
……
} else {
// 3. 有LayoutManager但沒有開啟自動(dòng)測(cè)量
……
}
}
第一種情況十分簡(jiǎn)單,就是執(zhí)行了defaultOnMeasure方法。里面就是計(jì)算并設(shè)置了RecyclerView的長(zhǎng)寬值。下來我們介紹另外兩種情況。
RecyclerView的自動(dòng)測(cè)量過程
在RecyclerView的早期版本,當(dāng)你為其設(shè)置了wrap_content值,但在其中的內(nèi)容改變的時(shí)候,RecyclerView并不能改變其大小來適應(yīng)內(nèi)部的內(nèi)容。因此后來加入了自動(dòng)測(cè)量機(jī)制,來解決這個(gè)問題。而且現(xiàn)在我們常用的三個(gè)LayoutManager,在其構(gòu)造函數(shù)中,均已經(jīng)設(shè)置了開啟自動(dòng)測(cè)量。所以我們現(xiàn)在可以放心大膽的為RecyclerView設(shè)置wrap_content。
那么我們來看一下RecyclerView的自動(dòng)測(cè)繪過程:
protected void onMeasure(int widthSpec, int heightSpec) {
……
if (mLayout.mAutoMeasure) {
// 第一部分:
// 1) 首先執(zhí)行LayoutManager的onMeasure方法。
// 2) 檢查如果width和height都已經(jīng)是精確值,那么就不用再根據(jù)內(nèi)容進(jìn)行計(jì)算所
// 需要的width和height,那么跳過之后的步驟。如果有其中任何一個(gè)值不是精確值,
// 則進(jìn)入到下面計(jì)算所需長(zhǎng)寬的步驟。
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;
}
// 第二部分:
// 1) 開啟布局流程計(jì)算出所有Child的邊界
// 2) 然后根據(jù)計(jì)算出的Child的邊界計(jì)算出RecyclerView的所需width和height
// 3) 檢查是否需要再次測(cè)量
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
}
mLayout.setMeasureSpecs(widthSpec, heightSpec);
mState.mIsMeasuring = true;
dispatchLayoutStep2();
// 布局過程結(jié)束,根據(jù)Children中的邊界信息計(jì)算并設(shè)置RecyclerView長(zhǎng)寬的測(cè)量值
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
// 檢查是否需要再此測(cè)量。如果RecyclerView仍然有非精確的寬和高,或者這里還有至
// 少一個(gè)Child還有非精確的寬和高,我們就需要在此測(cè)量。
if (mLayout.shouldMeasureTwice()) {
mLayout.setMeasureSpecs(
MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
mState.mIsMeasuring = true;
dispatchLayoutStep2();
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
}
} else {
……
}
}
自動(dòng)測(cè)繪過程可以分為兩部分:
第一部分:
- 首先執(zhí)行LayoutManager的onMeasure方法。
- 檢查如果width和height都已經(jīng)是精確值,那么就不用再根據(jù)內(nèi)容進(jìn)行計(jì)算所需要的width和height,那么跳過之后的步驟。如果有其中任何一個(gè)值不是精確值,則進(jìn)入到下面計(jì)算所需長(zhǎng)寬的步驟。
第二部分:
- 開啟布局流程,計(jì)算出所有Child的邊界。
- 然后根據(jù)計(jì)算出的Child的邊界計(jì)算出RecyclerView的所需width和height,并設(shè)置。
- 檢查是否需要再次測(cè)量,如果需要?jiǎng)t在此進(jìn)行測(cè)量。
注意:
因?yàn)樽詣?dòng)繪制過程中執(zhí)行了布局流程,那么在之后布局的時(shí)候會(huì)檢查是否已經(jīng)進(jìn)行過布局流程,如果已經(jīng)進(jìn)行過布局流程,則會(huì)跳過進(jìn)行過的布局流程,不會(huì)造成重復(fù)操作。(布局流程有三步)
RecyclerView的非自動(dòng)測(cè)繪流程
當(dāng)我們使用系統(tǒng)提供的那三個(gè)LayoutManager的時(shí)候,默認(rèn)是開啟自動(dòng)測(cè)繪的。除非,你在初始化LayoutManager之后,自己通過setAutoMeasureEnabled(false)方法設(shè)置成false。不然不會(huì)走到非自動(dòng)測(cè)繪流程的。但是如果我們是使用的自己自定義的LayoutManager,而且我們自定義的LayoutManager又沒有在初始化的時(shí)候開啟自動(dòng)測(cè)繪,那么默認(rèn)將會(huì)是不開啟自動(dòng)測(cè)繪。這個(gè)時(shí)候會(huì)走到非自動(dòng)測(cè)繪流程。
那么接下來看一看非自動(dòng)測(cè)繪流程。
protected void onMeasure(int widthSpec, int heightSpec) {
……
if (mLayout.mAutoMeasure) {
……
} else {
// 第一部分:如果RecyclerView已經(jīng)設(shè)置了Size固定,則執(zhí)行LayoutManager的onMeasure方法
if (mHasFixedSize) {
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
return;
}
// 第二部分:
// 1) 如果在測(cè)量的過程中有數(shù)據(jù)有更新,則先處理更新的數(shù)據(jù)
// 2) 執(zhí)行自定義測(cè)量流程,這需要自定義的LayoutManager實(shí)現(xiàn)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);
}
// 處理完新更新的數(shù)據(jù),然后執(zhí)行自定義測(cè)量操作。
if (mAdapter != null) {
mState.mItemCount = mAdapter.getItemCount();
} else {
mState.mItemCount = 0;
}
eatRequestLayout();
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
resumeRequestLayout(false);
mState.mInPreLayout = false; // clear
}
}
RecyclerView的非自動(dòng)化測(cè)繪流程也可以分為兩部分:
第一部分:
在固定尺寸模式下,直接執(zhí)行LayoutManager的onMeasure方法。
第二部分:
不在固定尺寸模式下
- 如果在測(cè)量的過程中有數(shù)據(jù)有更新,則先處理更新的數(shù)據(jù)
- 執(zhí)行自定義測(cè)量流程,這需要自定義的LayoutManager實(shí)現(xiàn)onMeasure方法。
總結(jié)
一般情況下,進(jìn)入的都是自動(dòng)測(cè)量模式。
最不常見的是沒有設(shè)置LayoutManager的模式。
最后需要注意的是:我們自定義LayoutManager的時(shí)候要根據(jù)自己的需求,決定是否要開啟自動(dòng)測(cè)量。如果需要開啟,則要主動(dòng)調(diào)用setAutoMeasureEnabled(true),否則默認(rèn)是不開啟的。
感謝您的閱讀,如果您覺得本文對(duì)你有所幫助,請(qǐng)不要吝嗇您的喜歡,您的喜歡是我寫作的動(dòng)力。