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原創七言律詩,轉載請註明出處

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