View繪製(一) performTraversals

weixin_34075268發表於2016-07-12

View的繪製是一個遞迴的過程,父View繪製自己和子View,然後子view繪製自己和自己的子View。

我們知道遞迴的一般數學表達是A1=T,An=f(An-1),那麼與之對應,View繪製的A1和函式f()又是什麼呢?

答案 :View繪製的A1是DecorView,它是View繪製的起始節點,View繪製的f()函式是measure,layout,draw三大過程,通過遞迴呼叫這三個方法完成View的繪製。

在介紹View的繪製過程之前,先介紹幾點關於View的相關概念,方便大家理解。

一、View相關概念

703392-902fb6357f662fa4.jpg
螢幕佈局層次圖
  1. PhoneWindow:繼承自Window類,負責管理介面顯示以及事件響應,每個Activity 介面都包含一個PhoneWindow物件,它是Activity和整個View系統互動的介面。

  2. DecorView:是PhoneWindow中的起始節點View,繼承自View類,是作為整個檢視容器來使用的,主要負責設定視窗屬性。

  3. ViewRoot:在系統啟動一個Activty元件的同時將其建立,類似於MVC模型中的Controller,負責管理、佈局和渲染視窗UI等事務。

二、View繪製開始

703392-66afa37b64a33d3f.png
View繪製開始
  1. 系統啟動一個Activity的同時建立一個ViewRoot例項。

  2. Activity 在attach階段生成一個PhoneWindow物件,它包含一個DecorView物件。

  3. 在Activity執行onCreate中的setContentView之後,將讀入的view載入進入第一張圖的ContentViews區域。

  4. 載入完畢後觸發ViewRoot中的scheduleTraversals非同步函式,從而進入ViewRoot的performTraversals函式,View的繪製從這裡開始。

三、performTraversals

703392-8832f49b994229b5.png
performTraversals

ViewRoot中的performTraversals方法以DecorView為父容器(ViewGroup)開始自上而下的View工作流程。

View的工作流程主要是指measure、layout、draw這三大流程,即測量、佈局和繪製,其中measure確定View的測量寬和高,layout確定View的最終寬/高和四個頂點位置,而draw則將View繪製到螢幕上。

關於measure,layout,draw的具體內容可以看後面的文章,這裡不用多在意。這個函式的執行過程主要是根據之前設定的狀態,判斷是否重新計算檢視大小(measure)、是否重新放置檢視的位置(layout)、以及是否重繪 (draw),其核心也就是通過判斷來選擇順序執行這三個方法中的哪個。

private void performTraversals() {

//1 處理mAttachInfo的初始化,並根據resize、visibility改變的情況,給相應的變數賦值。
  final View host = mView;//這裡的mView即DecorView
  final View.AttachInfo attachInfo = mAttachInfo;
  final int viewVisibility = getHostVisibility();
  boolean viewVisibilityChanged = mViewVisibility != viewVisibility
          || mNewSurfaceNeeded;
  float appScale = mAttachInfo.mApplicationScale;
  WindowManager.LayoutParams params = null;
  if (mWindowAttributesChanged) {
      mWindowAttributesChanged = false;
      surfaceChanged = true;
      params = lp;
  }
  Rect frame = mWinFrame;
  if (mFirst) {
      // For the very first time, tell the view hierarchy that it
      // is attached to the window.  Note that at this point the surface
      // object is not initialized to its backing store, but soon it
      // will be (assuming the window is visible).
      attachInfo.mSurface = mSurface;
      attachInfo.mUse32BitDrawingCache = PixelFormat.formatHasAlpha(lp.format) ||
              lp.format == PixelFormat.RGBX_8888;
      attachInfo.mHasWindowFocus = false;
      attachInfo.mWindowVisibility = viewVisibility;
      ......
  } 

//2 如果mLayoutRequested判斷為true,那麼說明需要重新layout,不過在此之前那必須重新measure。
  if (mLayoutRequested) {
      // Execute enqueued actions on every layout in case a view that was detached
      // enqueued an action after being detached
      getRunQueue().executeActions(attachInfo.mHandler);
      if (mFirst) {
          ......
      } 
  }

//3 判斷是否有子檢視的屬性發生變化,ViewRoot需要獲取這些變化。
  if (attachInfo.mRecomputeGlobalAttributes) {
      ......
  }
  if (mFirst || attachInfo.mViewVisibilityChanged) {
      ......
  }


//4 根據上面得到的變數數值,確定我們的view需要多大尺寸才能裝下。於是就得measure了,有viewgroup的weight屬性還得再做些處理
           // Ask host how big it wants to be
          host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
          mLayoutRequested = true;
      }
  }


//5 measure完畢,接下來可以layout了。
  final boolean didLayout = mLayoutRequested;
  boolean triggerGlobalLayoutListener = didLayout
          || attachInfo.mRecomputeGlobalAttributes;
  if (didLayout) {
      host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight);

  }


//6 如果mFirst為true,那麼會進行view獲取焦點的動作。
  if (mFirst) {
      mRealFocusedView = mView.findFocus();
  }

  boolean cancelDraw = attachInfo.mTreeObserver.dispatchOnPreDraw();


//7 終於,來到最後一步,前面的工作可以說都是鋪墊,都是為了draw而準備的。
  if (!cancelDraw && !newSurface) {
      mFullRedrawNeeded = false;
      draw(fullRedrawNeeded)
}

相關文章