RecyclerView (一)-- 绘制流程

作者:opLW
参考:RecyclerView全面的源码解析
最近用RecyclerView用的很多,打算写一系列相关的文章,做一个总结,加深理解。? 有什么不对还望指出。(RV代表RecyclerViewLM代表LayoutManager)

目录

1.onMeasure方法
2.onLayout方法
3.onDraw方法

前言: 提到绘制流程,那么离不开三个方法onMeasure,onLayout,onDraw。下面按照这个思路整理一下RecyclerView的绘制流程。不熟悉Android view绘制机制的可以自行百度或者查阅《Android开发艺术探索》第四章。

1.onMeasure方法
  • 1)大体流程:

    //RecyclerView
      @Override
      protected void onMeasure(int widthSpec, int heightSpec) {
      	if (mLayout == null) {
      		// 1 没有设置LayoutManager
          }
    		if (mLayout.isAutoMeasureEnabled()) {
      		// 2 mAutoMeasure为true
          } else {
              // 3 mAutoMeasure为false
          }
      }
    //RecyclerView#LayoutManager
      public boolean isAutoMeasureEnabled() {
              return mAutoMeasure;
      }
    
  • 2)没有设置LayoutManager

       @Override
      protected void onMeasure(int widthSpec, int heightSpec) {
      	if (mLayout == null) {
      		defaultOnMeasure(widthSpec, heightSpec);  // 4
            return;
          }
    		....
      }
      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);
      }
    
    • 4 在没有设置LayoutManager的情况下,RV会调用defaultOnMeasure测量并设置自身的大小,然后return,跳出onMeasure方法。
    • 感觉好像有点不对,测量完自身之后不是应该测量子view????。这点与我们所认知的不同,所以LayoutManager是RV的必要成分体现出来了,没有LayoutManager的话一切免谈。
  • 3)mAutoMeasure为true

    • A 什么是AutoMeasure 根据RV源码注释简单说下,AutoMeasure是RV的一种机制,RV会在调用onMeasure时,同时调用LayoutManageronLayoutChildren方法,来获取子view的大小和位置并在测量好子view之后再来设置RV的尺寸,以此来更好的支持RV的动画效果。当AutoMeasure取值为true时,会使用这个机制。系统自带的三个LayoutManager默认设置为true,当我们需要自定义测量工作时,需要将AutoMeasure设置为false,并且重写LayoutManageronMeasure方法。

    • B mState.mLayoutStep的三种取值以及三个dispatchLayoutStep 顾名思义,这个变量记录了当前执行到的步骤,下面看看它的取值:

      取值 含义
      State.STEP_START 代表尚未执行dispatchLayoutStep1()
      State.STEP_LAYOUT 代表执行了dispatchLayoutStep1(),尚未执行dispatchLayoutStep2()
      State.STEP_ANIMATIONS 代表执行了1和2,尚未执行dispatchLayoutStep3()
      dispatchLayoutStep1() 源代码很多,这里根据注释简要说明这一步做了什么:1.处理Adapter的更新;2.决定做哪些动画;3.保存一些相关的信息;4.必要的话,执行预布局并保存信息。执行之后更新mState.mLayoutStep为State.STEP_LAYOUT
      dispatchLayoutStep2() 最重要的步骤,这个方法真正的执行了对子View的测量和布局工作。执行之后更新mState.mLayoutStep为State.STEP_ANIMATIONS
      dispatchLayoutStep3() 这个步骤根据之前保存的动画信息,触发相应的动画效果。
    • C AutoMeasure为true时所作的工作

    @Override
      protected void onMeasure(int widthSpec, int heightSpec) {
      	if (mLayout == null) {
      		// 1 没有设置LayoutManager
        }
    	if (mLayout.isAutoMeasureEnabled()) {
      		final int widthMode = MeasureSpec.getMode(widthSpec);
            final int heightMode = MeasureSpec.getMode(heightSpec);
              //省略注释
              mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); // 5
    
              final boolean measureSpecModeIsExactly =
                      widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
              if (measureSpecModeIsExactly || mAdapter == null) {
                  return; // 6
              }
    
              if (mState.mLayoutStep == State.STEP_START) {
                  dispatchLayoutStep1(); // 7
              }
             
              mLayout.setMeasureSpecs(widthSpec, heightSpec);
              mState.mIsMeasuring = true;
              dispatchLayoutStep2(); // 8
              mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec); // 9
              
              if (mLayout.shouldMeasureTwice()) {
                  mLayout.setMeasureSpecs(
                          MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
                          MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
                  mState.mIsMeasuring = true;
                  dispatchLayoutStep2();
                  mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
              }
          } else {
              // 3 mAutoMeasure为false
          }
      }
    
    • D 5 看到标识5处,疑惑来了???不是说设置AutoMeasure为false时,才会调用LM的onMeasure方法吗?
      /**
      * This specific call should be considered deprecated and replaced with
      * {@link #defaultOnMeasure(int, int)}. It can't actually be replaced as it could
      * break existing third party code but all documentation directs developers to not
      * override {@link LayoutManager#onMeasure(int, int)} when
      * {@link LayoutManager#isAutoMeasureEnabled()} returns true.*/
      
      上面是源码中对5的注释,提到了本来应该用前面提到的RV的defaultOnMeasure方法代替的,但是可能会影响到一些第三方代码,所以没有。而且官文文档已经很好的引导了开发者,在AutoMeasure为true时不要重写LM的onLayout方法,我们看下面的源码,可以知道LM的onMeasure方法的默认实现是调用的RV的defaultOnMeasure。所以最终调用的还是RV的defaultOnMeasure

      这里有点不明白,如何影响到第三方代码,如有知道还望留言赐教。?

      //RecyclerView#LayoutManager
      public void onMeasure(@NonNull Recycler recycler, 
          @NonNull State state, int widthSpec,  int heightSpec) {
          mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
      }
      
    • E 6 下面是标识6的代码,可以看出当RV的大小可以确定时,直接返回不再根据子View的大小来设置自身的尺寸。那什么时候RV的大小可以确定呢?就是当RV的大小设置为明确数字或者match_parent时。
      final boolean measureSpecModeIsExactly =
              widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
      if (measureSpecModeIsExactly || mAdapter == null) {
           return; // 6
      }
      
    • F 7 在mState.mLayoutStep为State.STEP_START时执行了dispatchLayoutStep1。 通过前面的表格对三个步骤有了大体的了解。这里为了不脱离主线和简单,只重点介绍dispatchLayoutStep2
    • G 8 调用dispatchLayoutStep2
         private void dispatchLayoutStep2() {
         startInterceptRequestLayout();
         onEnterLayoutOrScroll();
         mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
         mAdapterHelper.consumeUpdatesInOnePass();
         mState.mItemCount = mAdapter.getItemCount(); // 8.1
         mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
      
         // Step 2: Run layout
         mState.mInPreLayout = false; // 8.2
         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);
      }
      
      • startInterceptRequestLayout()stopInterceptRequestLayout(false)成对出现,主要作用是防止在layout的过程中,某一个子View触发了onRequestLayout方法,从而导致多余的layout操作。
      • 这段代码主要获取了子View的个数(8.1)以及调用LM的onLayoutChildren(8.2)对子View进行测量和布局。此处也体现了RV的强大,将布局抽离出来交给LM管理,从而使得可以灵活的实现各种布局。后面会有文章介绍自定义LM。这种思想值得学习,应用到实际的代码编写中。?
    • H 9 根据最后的测量结果设置RV的大小。
      //RV#onMeasure
      mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec); // 9
      //RV#LM#setMeasuredDimensionFromChildren
      void setMeasuredDimensionFromChildren(int widthSpec, int heightSpec) {
              final int count = getChildCount();
              if (count == 0) {
                  mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
                  return;
              }
              // 9.1
              mRecyclerView.mTempRect.set(minX, minY, maxX, maxY);
              setMeasuredDimension(mRecyclerView.mTempRect, widthSpec, heightSpec); // 9.2
          }
      
      • (9.1) 省略了代码,主要是根据前面对子View的测量和布局计算RV的大小(9.2);
    • I 总结 自此mAutoMeasure为true的情况结束。主要是判断RV的尺寸是否为EXACTLY,是则直接设置,否则调用dispatchLayoutStep1dispatchLayoutStep2进行信息的初始化计算,对子View的测量和布局以及根据计算的结果设置RV的大小。下面看看mAutoMeasure为false的情况。
  • 4)mAutoMeasure为false

    @Override
    protected void onMeasure(int widthSpec, int heightSpec) {
     if (mLayout == null) {
     	// 1 没有设置LayoutManager
     }
     if (mLayout.isAutoMeasureEnabled()) {
     	// 2 mAutoMeasure为true
     } else {
           if (mHasFixedSize) { // 10
                 mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
                 return; 
             }
             // 省略部分更新操作
             if (mAdapter != null) {
                 mState.mItemCount = mAdapter.getItemCount();
             } else {
                 mState.mItemCount = 0;
             }
             startInterceptRequestLayout();
             mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); // 11
             stopInterceptRequestLayout(false);
             mState.mInPreLayout = false; // clear
     }
    }
    
    • 10 是RV留给开发者的一个优化点,可以根据需要将此值设为true,来减少不必要的测量工作从而达到优化,感兴趣的同学可自行百度。
    • 11 此处调用LMonMeasure进行测量,可以是自定义的也可以是默认的方法。默认的方法会调用RV的defaultOnMeasure
  • 5)总结onMeasure mAutoMeasure为true时,会涉及到子View的测量和布局(在LM的onLayoutChildren方法里)。而为false时,单纯的测量并没有布局。那么就有布局和没布局的区别了,如何解决呢?RV在onLayout方法里处理了这个问题,接着往下看。

2.onLayout方法
  •  @Override
      protected void onLayout(boolean changed, int l, int t, int r, int b) {
          TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
          dispatchLayout(); // 12
          TraceCompat.endSection();
          mFirstLayoutComplete = true;
      }
      void dispatchLayout() {
          if (mAdapter == null) { // 13
              Log.e(TAG, "No adapter attached; skipping layout");
              // leave the state in START
              return;
          }
          if (mLayout == null) { // 14
              Log.e(TAG, "No layout manager attached; skipping layout");
              // leave the state in START
              return;
          }
          mState.mIsMeasuring = false;
          if (mState.mLayoutStep == State.STEP_START) { // 15
              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(); // 16
      }
    
    • 12 可以看见最终调用dispatchLayout进行布局工作。
    • 13,14 再次检查adapter和LM是否为空。
    • 15 上面提到onLayout会解决布局和没布局的区别,此处会进行判断,从而调用dispatchLayoutStep1()dispatchLayoutStep2()中的一个或两个进行布局工作。忘记dispatchLayoutStep2()是干嘛的,点击传送门
    • 16 最后统一调用dispatchLayoutStep3() 进行动画相关的工作。
    • 在onLayout方法里,会统一保证相关的layout方法得到调用。

3.onDraw方法
  • public void draw(Canvas c) {
      super.draw(c); // 17
    
      final int count = mItemDecorations.size(); // 18
      for (int i = 0; i < count; i++) {
          mItemDecorations.get(i).onDrawOver(c, this, mState);
      }
    }
    @Override
      public void onDraw(Canvas c) {
          super.onDraw(c);
    
          final int count = mItemDecorations.size(); // 18
          for (int i = 0; i < count; i++) {
              mItemDecorations.get(i).onDraw(c, this, mState);
          }
      }
    
    • 17 RV重写了View.draw方法,在17处调用Viewdraw方法进行子View绘制的分发。
    • 18 两处18看出在RV在draw的过程中,调用了ItemDecoration的两个draw相关的方法,此处也是RV的一个亮点,抽离出了ItemDecoration,方便制作更加漂亮的界面。详细ItemDecoration自行查看相关文章。
总结

RV对绘制相关的内容进行了完整编写的,同时也留下许多方法供我们自定义,很是灵活。但也很复杂,值得深入理解,后面还会有其他相关的文章介绍。RecyclerView (二) – 缓存复用机制(上)

万水千山总是情,麻烦手下别留情。
如若讲得有不妥,文末留言告知我,
如若觉得还可以,收藏点赞要一起。

opLW原创七言律诗,转载请注明出处

发布了21 篇原创文章 · 获赞 28 · 访问量 7325

猜你喜欢

转载自blog.csdn.net/qq_36518248/article/details/90082584