步步前行(RecyclerView拆分解析)-ViewGroup篇

步步前行(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就分析结束了,

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章