RecyclerView的绘制三大流程

作者: isLJli | 来源:发表于2020-11-10 19:42 被阅读0次

    源码的开启之旅,从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的绘制三大流程

        本文链接:https://www.haomeiwen.com/subject/moyjvktx.html