注意:本文基于25.4.0源码
RecyclerView的源码非常复杂,仅仅RecyclerView.java一个文件就有一万多行,阅读起来十分困难。不过RecyclerView作为一个View,再复杂也得遵循View的基本法:三大流程。所以我们从View绘制的三大流程入手就会轻松许多。
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
//LayoutManager为空
if (mLayout == null) {
//设置默认宽高
defaultOnMeasure(widthSpec, heightSpec);
return;
}
//默认自动测量
if (mLayout.mAutoMeasure) {
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
&& heightMode == MeasureSpec.EXACTLY;
//通过LayoutManger计算宽高
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
if (skipMeasure || mAdapter == null) {
return;
}
if (mState.mLayoutStep == State.STEP_START) {
/**
* 处理adpter更新
* 决定是否要执行动画
* 保存动画信息
* 如果有必要的话,进行预布局
*/
dispatchLayoutStep1();
}
// set dimensions in 2nd step. Pre-layout should happen with old dimensions for
// consistency
mLayout.setMeasureSpecs(widthSpec, heightSpec);
mState.mIsMeasuring = true;
//进行真正的测量和布局
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 {
//非自动测量
}
}
先进行整体描述一下measure
流程
LayoutManger
,设置默认宽高,结束,否则继续向下LayoutManger
初步计算宽高(一般使用默认宽高计算方式),如果RecyclerView的宽高都是EXACTLY
的,则测量结束,否则继续测量dispatchLayoutStep1
处理adpter更新,决定是否要执行动画,保存动画信息,处理预布局dispatchLayoutStep2
进行真正测量布局,对子view(itemView)进行measure
和layout
,确定子view的宽高和位置下面进行关键点梳理
mLayout
就是recyclerView.setLayoutManager(layoutManager)
中设置的layoutManager
。当mLayout
为null
的时候,使用默认测量方法,这个时候RecyclerView空白什么都不会显示
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);
}
默认测量时,我们可以看到会使用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);
}
}
很简单,这里不做过多介绍
当mLayout
不为null
的时候,会进行判断是否进行自动测量。mLayout.mAutoMeasure
默认为true
,表示自动测量,例如LinearLayoutManager
,除非你自定义LayoutManager或者调用setAutoMeasureEnabled(false)
。
public LinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
setOrientation(orientation);
setReverseLayout(reverseLayout);
setAutoMeasureEnabled(true);
}
自动测量时,先调用mLayout.onMeasure
,委托给mLayout
进行测量。
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
方法默认使用RecyclerView的默认测量,和上面一样。
如果RecyclerView的宽高都是固定值或者adapter
为空,此时测量结束。否则调用dispatchLayoutStep1
和dispatchLayoutStep2
继续进行测量。
下面继续看dispatchLayoutStep1
和dispatchLayoutStep2
,其实onLayout
中还有一个dispatchLayoutStep3
,这三个方法共同组成了RecyclerView的绘制布局过程。
dispatchLayoutStep1
处理adpter更新,决定是否要执行动画,保存动画信息,如果有必要的话,进行预布局。方法结束状态置为State.STEP_LAYOUT
dispatchLayoutStep1
进行真正的测量和布局操作。方法结束状态置为State.STEP_ANIMATIONS
dispatchLayoutStep1
触发动画并进行任何必要的清理。方法结束状态重置为State.STEP_START
dispatchLayoutStep1
方法主要和动画和预布局相关,这里暂时先略过,直接看dispatchLayoutStep2
。
private void dispatchLayoutStep2() {
...
// Step 2: Run layout
mState.mInPreLayout = false;
mLayout.onLayoutChildren(mRecycler, mState);
...
}
我们可以看到View的测量和布局委托给mLayout
进行处理,从这里可以看出RecyclerView的灵活性,只要替换不同的LayoutManger
就能够实现不同的布局,相当灵活。onLayoutChildren
方法默认为空,需要各个实现类去实现。onLayoutChildren
主要用来对RecyclerView的ItemView进行measure
和layout
,后面再进行详细介绍。
dispatchLayoutStep2
成功之后,我们已经完成对RecyclerView的子View的测量和布局,下面就可以根据子view的宽高来计算自己的宽高了。这里比较简单就不做具体介绍了,主要需要注意的是DecoratedBounds,即recyclerView.addItemDecoration(itemDecoration)
中的itemDecoration
所需占的空间。
是否需要二次测量和具体LayoutManger
有关,由LayoutManger
来具体实现,以LinearLayoutManager
举例
@Override
boolean shouldMeasureTwice() {
return getHeightMode() != View.MeasureSpec.EXACTLY
&& getWidthMode() != View.MeasureSpec.EXACTLY
&& hasFlexibleChildInBothOrientations();
}
果RecyclerView仍然有非精确的宽和高,或者这里还有至少一个Child还有非精确的宽和高,我们就需要再次测量。
下面我们具体介绍一下LinearLayoutManager的onLayoutChildren实现,来看一下LinearLayoutManager是怎么对子View进行布局的。
@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
//查找锚点,锚点可以看做是布局的一个起始点,以这个点为基点,分别向上和向下进行测量布局
updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
mAnchorInfo.mValid = true;
}
...
//将屏幕上显示的Item移除,并将对应viewholder暂存起来
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 {
//与上面布局方法类似,只是方向相反
...
}
...
}
updateAnchorInfoForLayout
查找锚点。布局方向用来确定Item是从上往下顺序显示还是从下往上顺序显示;锚点用来确认布局起始点detachAndScrapAttachedViews
如果屏幕上有Item显示,则将它们全部移除,并且暂存起来fill()
方法用来做具体添加child操作,并对child进行测量和布局 @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
非常简单,主要通过dispatchLayout
实现。
void dispatchLayout() {
...
mState.mIsMeasuring = false;
if (mState.mLayoutStep == State.STEP_START) {
//measure阶段未对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.
//执行过布局但size有改变
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else {
// always make sure we sync them (to ensure mode is exact)
//执行过布局且布局未发生变化
mLayout.setExactMeasureSpecsFrom(this);
}
dispatchLayoutStep3();
}
这个方法很简单,主要保证RecyclerView必须经历三个过程–dispatchLayoutStep1、dispatchLayoutStep2、dispatchLayoutStep3。如果开启自动测量就会在measure
阶段对children进行布局,如果未开启自动测量layout
阶段就会对children进行布局。
private void dispatchLayoutStep3() {
mState.mLayoutStep = State.STEP_START;
if (mState.mRunSimpleAnimations) {
执行动画
...
}
清除状态和无用信息
...
}
Draw流程主要处理的就是ItemDecoration的一些绘制操作,类似分割线、悬浮title之类。
@Override
public void draw(Canvas c) {
super.draw(c);
final int count = mItemDecorations.size();
for (int i = 0; i
}
...
}
是不是很熟悉,这里就是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
}
}
这里绘制的是ItemDecoration
的onDraw
方法。
转:https://www.jianshu.com/p/4026e12881e4