步步前行(RecyclerView拆分解析)-ViewGroup篇
不喜勿喷,有错请留言(以下源码均来自recyclerView-27.1.1版本)
对于android开发而言,recyclerView是最熟悉常用的组件了,在很多层面上,常常替代ListView,往上分析RecyclerView的代码文章更是数不胜数,不过这一篇我想换一个角度,从拆分RecyclerView,让Recyclerview回归ViewGroup的角度来理解RecyclerView,那我们如何去拆分呢?我决定以一个模拟的方式,从RecyclerView上拆分下来的功能模块在模拟类上使用,从而理解其功能
组成
首先相信大家或多或少都使用过RecyclerView,对于最最基本的使用我相信点进来的都会,如图
我们必须要创建一个RecyclerView,然后为这个RecyclerView准备两样东西一个是LayoutManager,一个是Adapter,也就是摆放布局和数据源
LayoutManager决定了我们RecyclerView如何展示横向还是竖向等,Adapter决定了我们RecyclerView条目个数和条目样式,至此一个RecyclerView就按照我们的想法呈现在我们面前了。
接下来我们先粗略的看看RecyclerView的组成:
一目了然,RecyclerView本身继承的viewGroup,所以RecyclerView形容成ViewGroup也不为过
既然是viewGroup,那么它自身必定重写onLayout和onMeasure
onMeasure
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
return;
}
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();
}
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();
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
}
} else {
if (mHasFixedSize) {
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
return;
}
if (mAdapterUpdateDuringMeasure) {
startInterceptRequestLayout();
onEnterLayoutOrScroll();
processAdapterUpdatesAndSetAnimationFlags();
onExitLayoutOrScroll();
if (mState.mRunPredictiveAnimations) {
mState.mInPreLayout = true;
} else {
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
}
}
首先layoutManager为null的情况下会执行defaultOnMeasure方法,我们看看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);
}
**************************************************************
从该方法中我们可以看出其直接调用了setMeasuredDimension设置了宽高,并为对子view做任何的测量
接着对LayoutManager的isAutoMeasureEnabled的不同状态,做了两种情况,我们先看看isAutoMeasureEnabled,由于是layoutManager我们不能以RecyclerView的实现为准,我们参考LinearlayoutManager,对于LinearLayoutManager,其是这么实现的
@Override
public boolean isAutoMeasureEnabled() {
return true;
}
******************************************
在LinearLayoutManager中,竟恒定给了true
我们继续顺着onMeasure的true情况看看
**********onMeasure为true的部分代码*********
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;
}
*******************************************
在这里先获取mode,若为MeasureSpec.EXACTLY即精确值时,就直接return掉不再往下执行了
而接下来的代码:
若是始态,执行dispatchLayoutStep1
********************************
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
}
mLayout.setMeasureSpecs(widthSpec, heightSpec);
mState.mIsMeasuring = true;
dispatchLayoutStep2();
**************************************************************
执行完dispatchLayoutStep1和dispatchLayoutStep2摆放后,这时候我们就可以获取宽高了,下面就是获取宽高,并设置给LayoutManager,避免有不精确的宽高时,就重新测量
**************************************************************
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
if (mLayout.shouldMeasureTwice()) {
mLayout.setMeasureSpecs(
MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
mState.mIsMeasuring = true;
dispatchLayoutStep2();
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
}
至此onMeasure部分的就执行完毕了,接下来就是开始onLayout的部分了。
onLayout
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
**************************************************************
TraceCompat是support-v4提供的统计一下trace事件给系统
*************************************************************
TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
dispatchLayout();//这里是我们分析的重点
TraceCompat.endSection();
mFirstLayoutComplete = true;
}
onLayout的dispatchLayout分成了三部分,根据不同的状态执行了三步:dispatchLayoutStep1,dispatchLayoutStep2和dispatchLayoutStep3
void dispatchLayout() {
******************************************************
此处省略了对layoutManger和adapter进行判null处理...
******************************************************
mState.mIsMeasuring = false;
**************************************************************
mState是对我们循环过程状态的标记,默认是STEP_START,避免重复执行和不必要的计算,执行dispatchLayoutStep1和dispatchLayoutStep2
* STEP_START -> dispatchLayoutStep1()
* STEP_LAYOUT -> dispatchLayoutStep2()
* STEP_ANIMATIONS -> dispatchLayoutStep2(), dispatchLayoutStep3()
**************************************************************
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
|| mLayout.getHeight() != getHeight()) {//这个判断条件,我们很容易可看出当adapter或layout进行变化的时候,就进行dispatchLayoutStep2
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else {
// always make sure we sync them (to ensure mode is exact)
mLayout.setExactMeasureSpecsFrom(this);
}
*************************************
最后每次都执行dispatchLayoutStep3
*************************************
dispatchLayoutStep3();
}
那我们先从dispatchLayoutStep1开始看,其主要作用是用于动画等视图信息,我们大致分析一下简单理解
private void dispatchLayoutStep1() {
mState.assertLayoutStep(State.STEP_START);
*****************************************************
计算滑动的距离并存储进state
*****************************************************
fillRemainingScrollValues(mState);
mState.mIsMeasuring = false;
*****************************************************
startInterceptRequestLayout与stopInterceptRequestLayout联动使用
在子view可能触发requestLayout前和后调用,主要用于截断requestLayout重新布局
觉得不太明白的可以看RecyclerView的requestLayout方法再辅助viewGroup的整个requestLayout传递可了解这一步
**************************************************
startInterceptRequestLayout();
还原子view的状态
mViewInfoStore.clear();
做layout或scroll的计数器
onEnterLayoutOrScroll();
使用适配器更新并计算要运行的动画类型(Add/REMOVE/UPDATE/MOVE)并存储到state中
processAdapterUpdatesAndSetAnimationFlags();//不做深入分析
存储焦点view的信息
saveFocusInfo();
mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;
mItemsAddedOrRemoved = mItemsChanged = false;
mState.mInPreLayout = mState.mRunPredictiveAnimations;
mState.mItemCount = mAdapter.getItemCount();
根据所有子view的layoutPosition计算动画最小位置和最大位置
findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);
根据processAdapterUpdatesAndSetAnimationFlags计算的动画类型,去处理做动画
if (mState.mRunSimpleAnimations) {
...
}
if (mState.mRunPredictiveAnimations) {
...
} else {
clearOldPositions();
}
维护计数器
onExitLayoutOrScroll();
还原requestLayout之行顺序
stopInterceptRequestLayout(false);
维护mLayoutStep 准备进行layout
mState.mLayoutStep = State.STEP_LAYOUT;
}
这第一步dispatchLayoutStep1到这里就结束了
下面我们进入dispatchLayoutStep2看看其是什么样的作用,从其官方注释我们可以了解,这个方法是整个viewGroup的重中之重,是对实图的最终形态布局
private void dispatchLayoutStep2() {
老样子 与第一步很一致呀 不解释这里了
startInterceptRequestLayout();
onEnterLayoutOrScroll();
mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
mAdapterHelper.consumeUpdatesInOnePass();
mState.mItemCount = mAdapter.getItemCount();
mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
*****************************************************
进入layout详细,进入到了layoutManager的onLayoutChildren方法中,至此我们如何的摆放就通过这里传递到了LayoutManager身上,交由对方决定(此处会在拆分解析-LayoutManager篇做详细解释)
*****************************************************
mState.mInPreLayout = false;
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = false;
mPendingSavedState = null;
// 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);
}
其实到这里我们就已经完成了,布局的展示了,最后的dispatchLayoutStep3,其实是对dispatchLayoutStep1的动画进行清理等操作,这里就不做详细的解释
最后可能有小伙伴会发现还有onDraw操作,我们就简单分析下onDraw,其实对于Recyclerview而言onDraw实现是最简单的,基本上都是围绕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);
}
}
还有一个重写了View的draw方法
@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);
}
...
}
细心的朋友会发现,其调用的位置不同,一个调用的ItemDecoration的onDraw方法,一个调用的ItemDecoration的onDrawOver方法
onDraw是在画布上draw一些我们想要的东西,其绘制的内容在我们的视图之前,也就是说视图压在Decoration之上,
而onDrawOver是与onDraw一致,但其在视图之后,也就是说它压在我们的视图之上画的
如果不理解这两个方法,我用两张图来辅助大家理解下:
从上图我们可看到,代表onDraw的视图被RecyclerView视图所遮挡,而onDrawOver的视图却遮挡着RecyclerView视图
至此RecyclerView的onMeasure和onLayout就分析结束了,