1. 程式人生 > >Android View 的工作原理(包含對 DecorView 和 ViewRoot 的簡單介紹)

Android View 的工作原理(包含對 DecorView 和 ViewRoot 的簡單介紹)

    什麼是 View ?

      View 是 Android 中所有控制元件的基類,View 可以是單個控制元件,也可以是由多個控制元件組成的一組控制元件。ViewGroup 裡面可以有子 View,子 View 裡面也可以有 ViewGroup。

    什麼是 ViewRoot、DecorView ?

      View 有三大流程,measure、layout、draw,瞭解並熟悉其三大流程對於我們進行 Android 開發有著極其重要的作用。在熟悉三大流程之前,先介紹一下 ViewRoot 和 DecorView 的概念。

      Activity 內部 是組合了一個 Window 物件,Activity 所有的事件都是交給 Window 來處理的,但是 Window 是一個抽象類,處理事務具體的操作就只能交給它的實現類,也就是 PhoneWindow,PhoneWindow 就會把事件傳給這個 DecorWindow,這個 DecorWinow 是應用視窗的根容器,也就是個頂級 View,其實是個 FrameLayout。這個根容器一般情況下都只有唯一一個子 View ,就是一個垂直的 LinearLayout,上下兩部分分別是標題欄和內容欄。而 ViewRoot 是 View 樹的管理者,它的成員變數 mView 對應的就是一個佈局的根 View,ViewRoot 是 View 和 WindowManger 的橋樑,也就是 DecorView 和 WindowManger 的紐帶,對應的是 ViewRootImpl 類。當 Activity 建立完成後,就會建立 ViewRootImpl 物件,將 DecorView 新增到 Window 中,並將 DecorView 和 ViewRootImpl 建立關聯。

      View 的三大流程就是從 ViewRoot 的 performTraversals 方法開始的。該方法會依次呼叫 performMeasure、performLayout、performDraw 三個方法,來完成頂級 View 的三大流程。三個方法又會分別呼叫 onMeasure、onLayout、onDraw 方法來完成對子 View 的 measure、layout、draw。接著子元素會重複父容器的 measure、layout、draw。來完成整個 View 樹的三大流程。measure 決定 View 的 寬高、layout 確定四個頂點的位置、draw 則會將內容呈現在螢幕上。

    什麼是 MeasureSpec ?

      為了更好的瞭解 View 的測量過程,我們還需要了解 MeasureSpec。MeasureSpec 在很大程度上決定了一個 View 的尺寸規格。如果對於 DecorView,其 View 的 MeasureSpec 就是由視窗的大小和自身的LayoutParams 所決定的,但是如果是一個子 View,其 MeasureSpec 則是由父容器的 MeasureSpec 和自身的 LayoutParams 所共同決定的。

      MeasureSpec 是一個32位 int 值,高兩位代表的是測量模式 SpecMode,低30位代表的是某種測量模式下的規格大小 SpecSize。

      SpecMode 有三類:UNSPECIFIED、EXACTLY、AT_MOST。

      UNSPECIFIED:父容器不對子 View 做限制,想要多大就多大。

      EXACTLY:父容器檢測出 View 的精確大小,這個時候 View 的最終大小就是 SpecSize 所指定的值。對應的是 LayoutParams 中的 match_parent 或 具體數值。

      AT_MOST:父容器指定一個大小 SpecSize,子 View 的大小按照自身要求決定,但是不能超過 SpecSize,對應 LayoutParams 中的 wrap_content。

    View 的三大流程

    measure 過程:
      measure 過程要分清情況,當要測量的只是一個原始的 View 時,那麼只通過 measure 就可以完成其測量過程,但是如果是一個 ViewGroup,就需要遍歷測量所有的子 View。
      下面是 View 的 onMeasure 方法:


      它呼叫了 setMaasureDimension() 方法來設定 View 寬高的測量值,那麼 getDefaSize() 方法又是幹嘛的呢?


      它是通過測量模式來返回測量得出的值。當測量模式為 UNSPECIFIED 時,測量值就為 result,也就是 getSuggestedMinimumWidth() 或者 getSuggestedMinimumHeight() 返回的值。當測量模式為 AT_MOST 和 EXACTLY 時,返回的大小就是 MeasureSpec 中測量得到的 SpecSize。

      那麼 getSuggestedMinimumWidth() 和 getSuggestedMinimumHeight() 方法又在幹什麼呢?

 

      這兩個方法會判斷當前 View 的背景是不是為空,為空就返回 mMinWidth/mMinHeight(即為 minWidth 和 minHeight 屬性設定的值),不為空就返回 mMinWidth/mMinHeight 和 背景的 getMinimumWidth()/getMinimumHeight() 返回值中的較大的那個值。

      而 ViewGroup 的 measure 過程如下:

      對於 ViewGroup 來說,不僅需要測量自身,還需要測量所有的子 View。和 View 不同的是,ViewGroup 是一個抽象類,所以沒有重寫 View 的 onMeasure() 方法,而是提供了 measureChildren() 的方法。


     該方法會遍歷 ViewGroup 的每一個 Child,然後呼叫 measureChild() 方法。


      該方法會得到子元素的 LayoutParams,再通過 getChildMeasureSpec 來獲取子元素的 MeasureSpec,然後呼叫 View 的 measure() 方法進行測量。
    layout 過程:

      layout 過程是為了確定 View 的位置,在 ViewGroup 位置被確定後,它會遍歷並確定所有子元素的位置。

      下面是 View 的 layout() 方法:


      

      layout() 方法首先會通過 setFrame() 方法來設定四個頂點的位置,四個頂點一旦確定,View 在父容器中的位置也就確定了,接著會呼叫 onLayout() 方法。這個方法是父容器確定子元素的位置,因為佈局選擇的不同,確認的方式也會不一樣,所以在 View 和 ViewGroup 中都沒有真正實現 onLayout()。具體的 onLayout() 實現要看選擇的佈局方式。

      ViewGroup 中,會使用 onLayout() 方法遍歷所有子元素並呼叫其 layout() 方法,在 layout() 方法中又會呼叫 onLayout() 方法,直到確定完整個 View 樹的位置。
    draw 過程:

      draw 過程是將 View 繪製到螢幕上,View 原始碼中的 draw() 方法比較長,就不復制到這了,感興趣的小夥伴可以自己去看看。

      draw() 方法主要有四個部分:繪製背景、繪製自己、繪製 children、繪製裝飾。

      繪製過程的傳遞是通過 dispatchDraw() 方法來實現的,會遍歷所有子元素的 draw() 方法,一層一層繪製完整個 View 樹。

      View 還有一個特殊方法,setWillNotDraw(),如下:


      表示如果 View 不需要繪製任何東西時,會將 flags 設定為 true,然後進行相應的優化。預設情況下,View 沒有啟用這個優化標誌位,但是 ViewGroup 預設啟用了。當一個自定義 View 繼承自 ViewGroup 並且本身不需要繪製時,可以開啟這個標誌位方便後續優化,如果需要繪製,就得顯性關閉 WILL_NOT_DRAW 這個