RecyclerView的绘制三大流程

源码的开启之旅,从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()方法大致分为三种情况 :

  1. Rv没有设置LayoutManager:
  2. Rv设置的LayoutManager支持自动测量
  3. 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();
      }
  1. 首先调用了 LayoutManageronMeasure方法。
  2. 如果Rv的宽高是确定模式或者adapter == null ,则return返回
  3. 如果mState.mLayoutStep == State.STEP_START则执行 dispatchLayoutStep1(),然后执行dispatchLayoutStep2()
  4. 如有需要,会进行第二次的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()方法,因为这个方法定义了mRunSimpleAnimationsmRunPredictiveAnimations值。

  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.mRunSimpleAnimationsmFirstLayoutComplete值有关。而mRunPredictiveAnimations的值又与mRunSimpleAnimationsmFirstLayoutComplete这个值要在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)调用LayoutManageronLayoutChildren()方法去自由的测量和布局。这里不转开讲。所以我们自定义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
      }

大致分为两种情况:

  1. mHasFixedSizetrue时,调用mLayout.onMeasure测量,然后return返回
  2. 如果有数据跟新,先处理数据更新,然后调用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设置了,还有确保dspatchLayoutStep1ispatchLayoutStep2ispatchLayoutStep3方法走一遍。

  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主要分为三步

  1. 调用super.draw(c),分发item绘制;调用itemDecorationonDraw绘制
  2. 调用itemDecorationonDrawOver绘制
  3. 如果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);
      }
  }

你可能感兴趣的:(RecyclerView的绘制三大流程)