前言
2021-01-05 修订版,源码基于 implementation 'androidx.recyclerview:recyclerview:1.1.0'
上一篇,说了RecyclerView
的回收复用,这一篇,我们来说说RecyclerView
的绘制流程。
- 【Android进阶】RecyclerView之ItemDecoration(一)
- 【Android进阶】RecyclerView之缓存(二)
- 【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
为true
或false
,会走2套逻辑,通过查看源码可以发现,isAutoMeasureEnabled
即mAutoMeasure
在LayoutManager
中,默认为false
,但在LinearLayoutManager
中为true
isAutoMeasureEnabled 为true
LinearLayoutManager
相关代码
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
状态。
isAutoMeasureEnabled 为false
if (mHasFixedSize) {
//不需要重新测量
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
return;
}
// custom onMeasure
if (mAdapterUpdateDuringMeasure) {
startInterceptRequestLayout();
onEnterLayoutOrScroll();
processAdapterUpdatesAndSetAnimationFlags();
onExitLayoutOrScroll();
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;
stopInterceptRequestLayout(false);
} else if (mState.mRunPredictiveAnimations) {
// If mAdapterUpdateDuringMeasure is false and mRunPredictiveAnimations is true:
// this means there is already an onMeasure() call performed to handle the pending
// adapter change, two onMeasure() calls can happen if RV is a child of LinearLayout
// with layout_width=MATCH_PARENT. RV cannot call LM.onMeasure() second time
// because getViewForPosition() will crash when LM uses a child to measure.
setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight());
return;
}
if (mAdapter != null) {
mState.mItemCount = mAdapter.getItemCount();
} else {
mState.mItemCount = 0;
}
startInterceptRequestLayout();
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
stopInterceptRequestLayout(false);
mState.mInPreLayout = false; // clear
首先,当mHasFixedSize
为true时不重新测量,其由setHasFixedSize
方法设置
/**
* RecyclerView can perform several optimizations if it can know in advance that RecyclerView's
* size is not affected by the adapter contents. RecyclerView can still change its size based
* on other factors (e.g. its parent's size) but this size calculation cannot depend on the
* size of its children or contents of its adapter (except the number of items in the adapter).
* <p>
* If your use of RecyclerView falls into this category, set this to {@code true}. It will allow
* RecyclerView to avoid invalidating the whole layout when its adapter contents change.
*
* @param hasFixedSize true if adapter changes cannot affect the size of the RecyclerView.
*/
public void setHasFixedSize(boolean hasFixedSize) {
mHasFixedSize = hasFixedSize;
}
当Adapter内Item的改变不会影响RecyclerView宽高的时候,可以设置为true让RecyclerView避免重新计算大小。
这也是RecyclerView的内存优化方法之一
当mAdapterUpdateDuringMeasure
为true时,需要做测量,而mAdapterUpdateDuringMeasure
是在triggerUpdateProcessor
方法中设置为true的
void triggerUpdateProcessor() {
if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
} else {
mAdapterUpdateDuringMeasure = true;
requestLayout();
}
}
triggerUpdateProcessor
方法又被一下方法调用,
onItemRangeChanged
onItemRangeInserted
onItemRangeRemoved
onItemRangeMoved
即当调用Adapter的增删改插方法,最后就会根据mHasFixedSize这个值来判断需要不需要requestLayout()
之前也说过,onMeasure
的主要逻辑在isAutoMeasureEnabled
为true
时,那么为什么LayoutManager
中默认值为false
?
如果isAutoMeasureEnabled
为false
,item
能正常绘制吗?让我们做个尝试
我们重写isAutoMeasureEnabled
方法,返回false
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仍能显示出来的原因。