源码的开启之旅,从ViewGroup绘制的三大流程开始看起,然后再逐渐了解RecyclerView各个模块
1.从onMeasure()开始看起
public class RecyclerView extends ViewGroup implements ScrollingView,
NestedScrollingChild2, NestedScrollingChild3
RecyclerView
:是一个很厉害的控件,但是它依然继承ViewGroup
控件,那么它就会执行三大绘制流程:测量、布局、绘制。所以我们从onMeasure()
测量开始看起。
1.1 在onMeasure中完成了测量和布局
RecyclerView
的绘制并不像其他ViewGroup中一样中规中矩,它把测量交给了LayoutManager
,并且在onMeasure()
方法中就完成了测量和布局
两步操作。
- mState.mLayoutStep的三个值所在阶段说明:
mState.mLayoutStep | 阶段 |
---|---|
State.STEP_START | 未执行dispatchLayoutStep1方法,执行完成dispatchLayoutStep1方法后变成State.STEP_LAYOUT |
State.STEP_LAYOUT | 执行完dispatchLayoutStep1方法,但未执行完dispatchLayoutStep2方法。执行完dispatchLayoutStep2方法后变成 State.STEP_ANIMATIONS |
State.STEP_ANIMATIONS | 执行完dispatchLayoutStep2方法,但未执行完dispatchLayoutStep3方法。执行完dispatchLayoutStep3方法后重新变为 State.STEP_START |
- RecyclerView中三个dispatchLayoutStep系列方法说明:
方法 | 作用 |
---|---|
dispatchLayoutStep1 | 1.更新adapter 2.决定哪个动画应该运行 3.保存当前的View信息 4.如有必要,运行预测性布局并保存其信息 |
dispatchLayoutStep2 | 调用LayoutManager完成测量和布局 |
dispatchLayoutStep3 | 保存动画信息,执行动画。在layout()方法最后中调用 |
onMeasure()
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
if (mLayout == null) {
// 第一种情况
defaultOnMeasure(widthSpec, heightSpec);
return;
}
if (mLayout.isAutoMeasureEnabled()) {
// 第二种情况
}else{
// 第三种情况
}
}
这里的mLayout
就是LayoutManager
, onMeasure()
方法大致分为三种情况 :
- Rv没有设置LayoutManager:
- Rv设置的LayoutManager支持自动测量
- Rv设置的LayoutManager不支持自动测试
1.2Rv没有设置LayoutManager
结果:
不显示内容
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
return;
}
执行了defaultOnMeasure()
就返回了。defaultOnMeasure()
就是根据RecyclerView的模式得到没有数据源的确定最初的宽高
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);
}
来看看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);
}
}
如果没有设置LayoutManager就只能测量而不能布局item
,在onLayout
方法中执行的 dispatchLayout()
会先判断Rv是否设置了LayoutManager和Adapter
,没有设置就返回。
@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;
}
void dispatchLayout() {
if (mAdapter == null) {
Log.w(TAG, "No adapter attached; skipping layout");
return;
}
if (mLayout == null) {
Log.e(TAG, "No layout manager attached; skipping layout");
return;
}
···
}
所以没有设置LayoutManager只完成了最简单的测量,并没有布局item,所以也就没显示内容了。
1.3LayoutManager支持自动测量
结果:
一般情况下执行dispatchLayoutStep1()、 dispatchLayoutStep2()
完成测量和布局
在官方给定的
三个LayoutManager中都是默认支持自定测量
的,所以我们自定义的LayoutManager不要设置为false。
@Override
public boolean isAutoMeasureEnabled() {
return true;
}
if (mLayout.isAutoMeasureEnabled()) {
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
// 先给LayoutManager的onMeasure()测量
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
// 确定模式和没有adapter时,返回
mLastAutoMeasureSkippedDueToExact =
widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
if (mLastAutoMeasureSkippedDueToExact || mAdapter == null) {
return;
}
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1(); //跟新adapter,保存item
}
mLayout.setMeasureSpecs(widthSpec, heightSpec);
mState.mIsMeasuring = true;
dispatchLayoutStep2(); //测量和布局
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
// 可能还会进行二次测量
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);
}
mLastAutoMeasureNonExactMeasuredWidth = getMeasuredWidth();
mLastAutoMeasureNonExactMeasuredHeight = getMeasuredHeight();
}
- 首先调用了
LayoutManager
的onMeasure
方法。- 如果
Rv的宽高是确定模式或者adapter == null
,则return返回- 如果
mState.mLayoutStep == State.STEP_START
则执行dispatchLayoutStep1()
,然后执行dispatchLayoutStep2()
。- 如有需要,会进行第二次的
dispatchLayoutStep2()
mLayout.onMeasure
public void onMeasure(@NonNull Recycler recycler, @NonNull State state, int widthSpec,
int heightSpec) {
mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
}
LayoutManager实例类中并没有重写onMeasure方法。然后发现它是调用
Rv的defaultOnMeasure()
方法,defaultOnMeasure
在上面已经说过了,只是一个简单测量。
dispatchLayoutStep1()
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
}
mLayout.setMeasureSpecs(widthSpec, heightSpec);
mState.mIsMeasuring = true;
dispatchLayoutStep2(); //测量和布局
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
在mState.mLayoutStep == State.STEP_START时执行
dispatchLayoutStep1()
方法,mState.mLayoutStep在上面也介绍过,记录着测量和布局处于什么的阶段。
private void dispatchLayoutStep1() {
mState.assertLayoutStep(State.STEP_START);
fillRemainingScrollValues(mState);
mState.mIsMeasuring = false;
startInterceptRequestLayout();
mViewInfoStore.clear();
onEnterLayoutOrScroll();
// 确定值
processAdapterUpdatesAndSetAnimationFlags();
saveFocusInfo();
mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;
mItemsAddedOrRemoved = mItemsChanged = false;
mState.mInPreLayout = mState.mRunPredictiveAnimations;
mState.mItemCount = mAdapter.getItemCount();
findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);
if (mState.mRunSimpleAnimations) {
// 找到没有被remove的ItemView,保存OldViewHolder信息,准备预布局
}
if (mState.mRunPredictiveAnimations) {
// 进行预布局
} else {
clearOldPositions();
}
onExitLayoutOrScroll();
stopInterceptRequestLayout(false);
// 执行完,更换状态值
mState.mLayoutStep = State.STEP_LAYOUT;
}
dispatchLayoutStep1()
:1. 主要更新adapter 2. 保存了ItemAnimator
信息。不过这里先不理这些,先看processAdapterUpdatesAndSetAnimationFlags()
方法,因为这个方法定义了mRunSimpleAnimations
和mRunPredictiveAnimations
值。
private void processAdapterUpdatesAndSetAnimationFlags() {
if (mDataSetHasChangedAfterLayout) {
// Processing these items have no value since data set changed unexpectedly.
// Instead, we just reset it.
mAdapterHelper.reset();
if (mDispatchItemsChangedEvent) {
mLayout.onItemsChanged(this);
}
}
// simple animations are a subset of advanced animations (which will cause a
// pre-layout step)
// If layout supports predictive animations, pre-process to decide if we want to run them
if (predictiveItemAnimationsEnabled()) {
mAdapterHelper.preProcess();
} else {
mAdapterHelper.consumeUpdatesInOnePass();
}
boolean animationTypeSupported = mItemsAddedOrRemoved || mItemsChanged;
//mRunSimpleAnimations的赋值
mState.mRunSimpleAnimations = mFirstLayoutComplete
&& mItemAnimator != null
&& (mDataSetHasChangedAfterLayout
|| animationTypeSupported
|| mLayout.mRequestedSimpleAnimations)
&& (!mDataSetHasChangedAfterLayout
|| mAdapter.hasStableIds());
//mRunPredictiveAnimations的赋值
mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations
&& animationTypeSupported
&& !mDataSetHasChangedAfterLayout
&& predictiveItemAnimationsEnabled();
}
可以看到mState.mRunSimpleAnimations
与mFirstLayoutComplete
值有关。而mRunPredictiveAnimations
的值又与mRunSimpleAnimations
。mFirstLayoutComplete
这个值要在onLayout
执行完之后才为true
。接下来看dispatchLayoutStep2()
方法
dispatchLayoutStep2()
private void dispatchLayoutStep2() {
startInterceptRequestLayout();
onEnterLayoutOrScroll();
mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
mAdapterHelper.consumeUpdatesInOnePass();
mState.mItemCount = mAdapter.getItemCount();
mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
if (mPendingSavedState != null && mAdapter.canRestoreState()) {
if (mPendingSavedState.mLayoutState != null) {
mLayout.onRestoreInstanceState(mPendingSavedState.mLayoutState);
}
mPendingSavedState = null;
}
// Step 2: Run layout
mState.mInPreLayout = false;
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = false;
// onLayoutChildren may have caused client code to disable item animations; re-check
mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
mState.mLayoutStep = State.STEP_ANIMATIONS;
onExitLayoutOrScroll();
stopInterceptRequestLayout(false);
}
这里重点是执行 mLayout.onLayoutChildren(mRecycler, mState)
调用LayoutManager
的onLayoutChildren()
方法去自由的测量和布局。这里不转开讲。所以我们自定义LayoutManager
主要就是重写onLayoutChildren去测量和布局
。
1.4LayoutManager不支持自动测量
来看看代码:
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
时,调用mLayout.onMeasure
测量,然后return返回- 如果有数据跟新,先处理数据更新,然后调用
mLayout.onMeasure
测量。
2. Layout布局
measure
的分析差不多了,我们来看第二个流程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; // 表示第一次布局完成
}
onLayout
并没有做什么事,主要是执行dispatchLayout()
方法,这个方法主要是确定LayoutManager和adapter设置了,还有确保dspatchLayoutStep1
、ispatchLayoutStep2
、ispatchLayoutStep3
方法走一遍。
void dispatchLayout() {
if (mAdapter == null) {
Log.w(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 the last time we measured children in onMeasure, we skipped the measurement and layout
// of RV children because the MeasureSpec in both dimensions was EXACTLY, and current
// dimensions of the RV are not equal to the last measured dimensions of RV, we need to
// measure and layout children one last time.
boolean needsRemeasureDueToExactSkip = mLastAutoMeasureSkippedDueToExact
&& (mLastAutoMeasureNonExactMeasuredWidth != getWidth()
|| mLastAutoMeasureNonExactMeasuredHeight != getHeight());
mLastAutoMeasureNonExactMeasuredWidth = 0;
mLastAutoMeasureNonExactMeasuredHeight = 0;
mLastAutoMeasureSkippedDueToExact = false;
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else if (mAdapterHelper.hasUpdates()
|| needsRemeasureDueToExactSkip
|| 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.
// TODO(shepshapard): Worth a note that I believe
// "mLayout.getWidth() != getWidth() || mLayout.getHeight() != getHeight()" above is
// not actually correct, causes unnecessary work to be done, and should be
// removed. Removing causes many tests to fail and I didn't have the time to
// investigate. Just a note for the a future reader or bug fixer.
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else {
// always make sure we sync them (to ensure mode is exact)
mLayout.setExactMeasureSpecsFrom(this);
}
dispatchLayoutStep3();
}
就是确保dispatchLayoutSte123
都走一遍, dispatchLayoutStep3()主要是触发动画的。这里先不分析。
private void dispatchLayoutStep3() {
// ······
mState.mLayoutStep = State.STEP_START;
// ······
}
3. draw
draw主要分为三步
- 调用
super.draw(c)
,分发item绘制;调用itemDecoration
的onDraw
绘制- 调用
itemDecoration
的onDrawOver
绘制- 如果RecyclerView调用了setClipToPadding,会实现一种特殊的滑动效果--每个ItemView可以滑动到padding区域。
@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);
}
// 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.
//第三步
}
通过回调执行 onDraw()
@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);
}
}