RecyclerView本身是一个展示大量数据的控件,相比较ListView,RecyclerView的4级缓存表现的非常出色,在性能方面相比于ListView提升了不少。同时由于LayoutManager的存在,让RecyclerView不仅有ListView的特点,同时兼有GridView的特点。
RecyclerView在设计方面上也是非常的灵活,不同的部分承担着不同的职责。其中Adapter负责提供数据,包括创建ViewHolder和绑定数据,LayoutManager负责ItemView的测量和布局,ItemAnimator负责每个ItemView的动画,ItemDecoration负责每个ItemView的间隙。这种插拔式的架构使得RecyclerView变得非常的灵活,每一个人都可以根据自身的需求来定义不同的部分。
1.measure
首先分析measure
过程,分析RecyclerView
的onMeasure
方法
mLayout
即LayoutManager
的对象。当RecyclerView
的LayoutManager
为空时,RecyclerView
不能显示任何的数据;第二种情况是LayoutManager
开启了自动测量,有可能会测量两次。第三种情况就是没有开启自动测量,这种情况比较少,因为为了RecyclerView
支持warp_content
属性,系统提供的LayoutManager
都开启自动测量的
protected void onMeasure(int widthSpec, int heightSpec) {
if (mLayout == null) {
// 第一种情况
}
if (mLayout.isAutoMeasureEnabled()) {
// 第二种情况
} else {
// 第三种情况
}
}
1.1 LayoutManager为空
源码
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
return;
}
直接调了defaultOnMeasure方法
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);
}
在defaultOnMeasure
方法里面,先是通过LayoutManager
的chooseSize
方法来计算值,然后就是setMeasuredDimension
方法来设置宽高
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
为空时,不会显示任何数据,因为当RecyclerView
处于onLayout
阶段,会调用dispatchLayout
方法。而在dispatchLayout
方法里面有这么一行代码:
if (mLayout == null) {
Log.e(TAG, "No layout manager attached; skipping layout");
// leave the state in START
return;
}
1.2 当LayoutManager开启了自动测量
RecyclerView
的测量分为两步,分别调用dispatchLayoutStep1
和dispatchLayoutStep2
。而dispatchLayoutStep3
方法的调用在RecyclerView
的onLayout
方法里面。在这种情况下,只会调用dispatchLayoutStep1
和dispatchLayoutStep2
这两个方法。
然后接触一个变量mState.mLayoutStep
取值 | 含义 |
---|---|
State.STEP_START | mState.mLayoutStep 的默认值,表示RecyclerView 还未经历dispatchLayoutStep1 ,dispatchLayoutStep1 调用之后该值会变为State.STEP_LAYOUT。 |
State.STEP_LAYOUT | 表示此时处于layout 阶段,这个阶段会调用dispatchLayoutStep2 方法。调用dispatchLayoutStep2 方法之后,该值变为了State.STEP_ANIMATIONS 。 |
State.STEP_ANIMATIONS | 该值表示RecyclerView 处于第三个阶段,也就是执行动画的阶段,也就是调用dispatchLayoutStep3 方法。当dispatchLayoutStep3 方法执行完毕之后,该值又变为了State.STEP_START |
接下来是源码分析:
if (mLayout.isAutoMeasureEnabled()) {
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
final boolean measureSpecModeIsExactly =
widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
if (measureSpecModeIsExactly || mAdapter == null) {
return;
}
if (mState.mLayoutStep == State.STEP_START) {
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);
}
}
1.调用LayoutManager
的onMeasure
方法进行测量。默认是调用RecyclerView
的defaultOnMeasure
2.如果mState.mLayoutStep
为State.STEP_START
的话,那么就会执行dispatchLayoutStep1
方法,然后会执行dispatchLayoutStep2
方法。
3.如果需要第二次测量的话,会再一次调用dispatchLayoutStep2
方法。
方法名 | 作用 |
---|---|
dispatchLayoutStep1 | 三大dispatchLayoutStep 方法第一步。本方法的作用主要有三点:1.处理Adapter 更新;2.决定是否执行ItemAnimator ;3.保存ItemView 的动画信息。本方法也被称为preLayout(预布局),当Adapter更新了,这个方法会保存每个ItemView的旧信息(oldViewHolderInfo) |
dispatchLayoutStep2 | 第二步。在这个方法里面真正进行children 的测量和布局 |
dispatchLayoutStep3 | 第三步。这个方法的作用执行在dispatchLayoutStep1 方法里面保存的动画信息 |
在dispatchLayoutStep1
源码可以得出结论,RecyclerView
第一次加载数据时,是不会执行的动画。dispatchLayoutStep2
方法中,一方面Adapter
的getItemCount
方法被调用,二是调用了LayoutManager
的onLayoutChildren
方法,这个方法里面进行对children的测量和布局。系统的LayoutManager
的onLayoutChildren
方法是一个空方法,所以需要LayoutManager
的子类自己来实现。从而来决定RecyclerView
在该LayoutManager
的策略下,应该怎么布局。
1.3 没有开启自动测量
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) {
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
方法),将直接调用LayoutManager
的onMeasure
方法进行测量。
如果mHasFixedSize
为false,同时此时如果有数据更新,先处理数据更新的事务,然后调用LayoutManager
的onMeasure
方法进行测量
2.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
方法里面。
void dispatchLayout() {
if (mAdapter == null) {
Log.e(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 (mState.mLayoutStep == State.STEP_START) {
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.
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else {
// always make sure we sync them (to ensure mode is exact)
mLayout.setExactMeasureSpecsFrom(this);
}
dispatchLayoutStep3();
}
dispatchLayout
方法保证RecyclerView
必须经历三个过程–dispatchLayoutStep1、dispatchLayoutStep2、dispatchLayoutStep3
。也就是说,在RecyclerView
经历一次完整的dispatchLayout
之后,后续如果参数有所变化时,可能只会经历最后的1步或者2步。
private void dispatchLayoutStep3() {
// ······
mState.mLayoutStep = State.STEP_START;
// ······
}
这个方法主要是做Item的动画,也就是ItemAnimator的执行。在这里,我们需要关注dispatchLayoutStep3
方法的是,它将mLayoutStep
重置为了State.STEP_START
。也就是说如果下一次重新开始dispatchLayout
的话,那么肯定会经历dispatchLayoutStep1、dispatchLayoutStep2、dispatchLayoutStep3
三个方法。
RecyclerView
跟其他ViewGroup
不同的地方在于,如果开启了自动测量,在measure
阶段,已经将Children布局完成了;如果没有开启自动测量,则在layout
阶段才布局Children。
3.draw
RecyclerView
的draw
分为三步:
- 调用super.draw方法。这里主要做了两件事:
- 将Children的绘制分发给ViewGroup;
- 将分割线的绘制分发给ItemDecoration。
- 如果需要的话,调用ItemDecoration的onDrawOver方法。通过这个方法,我们在每个ItemView上面画上很多东西。
- 如果RecyclerView调用了setClipToPadding,会实现一种特殊的滑动效果–每个ItemView可以滑动到padding区域。
源码分析:
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
方法中,关于children的绘制和ItemDecoration的绘制
,是在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);
}
}
调用super.onDraw
方法将Children
的绘制分发给ViewGroup
执行;然后将ItemDecoration
的绘制分发到ItemDecoration
的onDraw
方法里面去。
4.总结
- RecyclerView的measure过程分为三种情况,每种情况都有执行过程。通常来说,我们都会走自动测量的过程。
- 自动测量里面需要分清楚mState.mLayoutStep状态值,因为根据不同的状态值调用不同的dispatchLayoutStep方法。
- layout过程也根据mState.mLayoutStep状态来调用不同的dispatchLayoutStep方法
- draw过程主要做了四件事:
- 绘制ItemDecoration的onDraw部分;
- 绘制Children;
- 绘制ItemDecoration的drawOver部分;
- 根据mClipToPadding的值来判断是否进行特殊绘制。