前言
上一篇,說了RecyclerView
的回收複用,這一篇,我們來說說RecyclerView
的繪製流程。
- 【Android進階】RecyclerView之ItemDecoration(一)
- 【Android進階】RecyclerView之快取(二)
- 【Android進階】RecyclerView之繪製流程(三)
onMeasure
我們先看看RecyclerView#onMeasure()
方法
protected void onMeasure(int widthSpec, int heightSpec) {
if (this.mLayout == null) {
this.defaultOnMeasure(widthSpec, heightSpec);
} else {
if (!this.mLayout.isAutoMeasureEnabled()) {
if (this.mHasFixedSize) {
this.mLayout.onMeasure(this.mRecycler, this.mState, widthSpec, heightSpec);
return;
}
if (this.mAdapterUpdateDuringMeasure) {
this.startInterceptRequestLayout();
this.onEnterLayoutOrScroll();
this.processAdapterUpdatesAndSetAnimationFlags();
this.onExitLayoutOrScroll();
if (this.mState.mRunPredictiveAnimations) {
this.mState.mInPreLayout = true;
} else {
this.mAdapterHelper.consumeUpdatesInOnePass();
this.mState.mInPreLayout = false;
}
this.mAdapterUpdateDuringMeasure = false;
this.stopInterceptRequestLayout(false);
} else if (this.mState.mRunPredictiveAnimations) {
this.setMeasuredDimension(this.getMeasuredWidth(), this.getMeasuredHeight());
return;
}
if (this.mAdapter != null) {
this.mState.mItemCount = this.mAdapter.getItemCount();
} else {
this.mState.mItemCount = 0;
}
this.startInterceptRequestLayout();
this.mLayout.onMeasure(this.mRecycler, this.mState, widthSpec, heightSpec);
this.stopInterceptRequestLayout(false);
this.mState.mInPreLayout = false;
} else {
int widthMode = MeasureSpec.getMode(widthSpec);
int heightMode = MeasureSpec.getMode(heightSpec);
this.mLayout.onMeasure(this.mRecycler, this.mState, widthSpec, heightSpec);
boolean measureSpecModeIsExactly = widthMode == 1073741824 && heightMode == 1073741824;
if (measureSpecModeIsExactly || this.mAdapter == null) {
return;
}
if (this.mState.mLayoutStep == 1) {
this.dispatchLayoutStep1();
}
this.mLayout.setMeasureSpecs(widthSpec, heightSpec);
this.mState.mIsMeasuring = true;
this.dispatchLayoutStep2();
this.mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
if (this.mLayout.shouldMeasureTwice()) {
this.mLayout.setMeasureSpecs(MeasureSpec.makeMeasureSpec(this.getMeasuredWidth(), 1073741824), MeasureSpec.makeMeasureSpec(this.getMeasuredHeight(), 1073741824));
this.mState.mIsMeasuring = true;
this.dispatchLayoutStep2();
this.mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
}
}
}
}
複製程式碼
我們從上往下看,首先,mLayout
即為LayoutManager
,如果其為null
會執行defaultOnMeasure
方法
void defaultOnMeasure(int widthSpec, int heightSpec) {
int width = RecyclerView.LayoutManager.chooseSize(widthSpec, this.getPaddingLeft() + this.getPaddingRight(), ViewCompat.getMinimumWidth(this));
int height = RecyclerView.LayoutManager.chooseSize(heightSpec, this.getPaddingTop() + this.getPaddingBottom(), ViewCompat.getMinimumHeight(this));
this.setMeasuredDimension(width, height);
}
複製程式碼
可以看到,這裡沒有測量item
的高度就直接呼叫setMeasuredDimension
方法設定寬高了
接著,是根據isAutoMeasureEnabled
,會走2套邏輯,通過檢視原始碼可以發現,isAutoMeasureEnabled
即mAutoMeasure
在LayoutManager
中,預設為false
,但在LinearLayoutManager
中為true
public boolean isAutoMeasureEnabled() {
return true;
}
複製程式碼
而onMeasure
的主要邏輯也是在isAutoMeasureEnabled
為true時,我們接著往下看
int widthMode = MeasureSpec.getMode(widthSpec);
int heightMode = MeasureSpec.getMode(heightSpec);
this.mLayout.onMeasure(this.mRecycler, this.mState, widthSpec, heightSpec);
boolean measureSpecModeIsExactly = widthMode == 1073741824 && heightMode == 1073741824;
if (measureSpecModeIsExactly || this.mAdapter == null) {
return;
}
複製程式碼
如果寬和高的測量值是絕對值時,直接跳過onMeasure方法。
if (this.mState.mLayoutStep == 1) {
this.dispatchLayoutStep1();
}
複製程式碼
mLayoutStep
預設值是 State.STEP_START
即為1,關於dispatchLayoutStep1
方法,其實沒有必要過多分析,因為分析原始碼主要是對於繪製思想的理解,如果過多的糾結於每一行程式碼的含義,那麼會陷入很大的困擾中。執行完之後,是this.mState.mLayoutStep = 2;
即STEP_LAYOUT
狀態。
接下來,是真正執行LayoutManager
繪製的地方dispatchLayoutStep2
。
private void dispatchLayoutStep2() {
this.startInterceptRequestLayout();
this.onEnterLayoutOrScroll();
this.mState.assertLayoutStep(6);
this.mAdapterHelper.consumeUpdatesInOnePass();
this.mState.mItemCount = this.mAdapter.getItemCount();
this.mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
this.mState.mInPreLayout = false;
this.mLayout.onLayoutChildren(this.mRecycler, this.mState);
this.mState.mStructureChanged = false;
this.mPendingSavedState = null;
this.mState.mRunSimpleAnimations = this.mState.mRunSimpleAnimations && this.mItemAnimator != null;
this.mState.mLayoutStep = 4;
this.onExitLayoutOrScroll();
this.stopInterceptRequestLayout(false);
}
複製程式碼
可以看到,RecyclerView
將item
的繪製交給了LayoutManager
,即mLayout.onLayoutChildren(this.mRecycler, this.mState);
,關於LayoutManager
將會在下一篇中詳細介紹。
這裡執行完之後,是this.mState.mLayoutStep = 4;
即STEP_ANIMATIONS
狀態。
之前也說過,onMeasure
的主要邏輯在isAutoMeasureEnabled
為true
時,那麼為什麼LayoutManager
中預設值為false
,如果isAutoMeasureEnabled
為false
,item能正常繪製嗎?讓我們做個嘗試
重新isAutoMeasureEnabled
方法
class MyLinLayoutManager extends LinearLayoutManager {
public MyLinLayoutManager(Context context) {
super(context);
}
@Override
public boolean isAutoMeasureEnabled() {
return false;
}
}
複製程式碼
然後將其設定給RecyclerView
,執行時,會發現item
還能正常顯示,這是為什麼?這裡就要說是onLayout
方法
onLayout
protected void onLayout(boolean changed, int l, int t, int r, int b) {
TraceCompat.beginSection("RV OnLayout");
this.dispatchLayout();
TraceCompat.endSection();
this.mFirstLayoutComplete = true;
}
複製程式碼
這裡的就比較簡單了,來看看dispatchLayout
方法
void dispatchLayout() {
if (this.mAdapter == null) {
Log.e("RecyclerView", "No adapter attached; skipping layout");
} else if (this.mLayout == null) {
Log.e("RecyclerView", "No layout manager attached; skipping layout");
} else {
this.mState.mIsMeasuring = false;
if (this.mState.mLayoutStep == 1) {
this.dispatchLayoutStep1();
this.mLayout.setExactMeasureSpecsFrom(this);
this.dispatchLayoutStep2();
} else if (!this.mAdapterHelper.hasUpdates() && this.mLayout.getWidth() == this.getWidth() && this.mLayout.getHeight() == this.getHeight()) {
this.mLayout.setExactMeasureSpecsFrom(this);
} else {
this.mLayout.setExactMeasureSpecsFrom(this);
this.dispatchLayoutStep2();
}
this.dispatchLayoutStep3();
}
}
複製程式碼
可以看到,這裡將onMeasure
的主要邏輯重新執行了一遍,也解釋了之前,當我們給RecyclerView
設定固定的寬高的時候,onMeasure
是直接跳過了執行,而子view仍能顯示出來的原因。