View|工作流程

2020-10-02 11:00:33

目錄

目錄

一、基礎知識

在學習View的工作原理之前,需要先學習一些基本的概念。

2.1 ViewRoot

Q1: ViewRoot是什麼?

  • 對應ViewRootImpl類。
  • 連線WindowManagerServiceDecorView的紐帶。

Q2:ViewRoot在View繪製中有什麼作用?

  • View的三大流程(測量(measure),佈局(layout),繪製(draw))均通過ViewRoot來完成。

注意:ViewRoot並不屬於View樹的一份子。從原始碼實現上來看,它既非View的子類,也非View的父類別,但是,它實現了ViewParent介面,這讓它可以作為View的名義上的父檢視

View的繪製流程從ViewRootperformTraversals開始,如圖。

onMeasure方法會對所有子元素進行measure過程,在measure方法中又會呼叫onMeasure方法,如此反覆最終完成整個View樹的遍歷,layoutdraw方法同理。

performTraversals大致流程

2.2 DecorView

Q1:DecorView是什麼?

  • 包括兩部分,標題列和內容欄,如圖。

  • DecorViewFrameLayout的子類,它可以被認為是Android檢視樹的根節點檢視。setContentView所佈置的檔案是被加入內容欄的。

Q: DecorView在View繪製中有什麼作用?

View層的事件都需先經過DecorView,再傳遞給View,分發的過程在View體系詳解有講到。

DecorView組成

2.3 ViewGroup.LayoutParams

此部分參考自:自定義View Measure過程 - 最易懂的自定義View原理系列(2)

Q1:怎麼理解ViewGroup.LayoutParams

ViewGroup 的子類(RelativeLayout、LinearLayout)有其對應的 ViewGroup.LayoutParams 子類

如:RelativeLayoutViewGroup.LayoutParams子類 = RelativeLayoutParams

Q2: 這個類有什麼作用?

指定檢視View 的高度(height) 和 寬度(width)等佈局引數。

Q3:怎麼使用?

引數解釋
具體值dp / px
fill_parent強制性使子檢視的大小擴充套件至與父檢視大小相等(不含 padding )
match_parent與fill_parent相同,用於Android 2.3 & 之後版本
wrap_content自適應大小,強制性地使檢視擴充套件以便顯示其全部內容(含 padding )

2.4 MeasureSpec

  • 定義:測量規格類。

  • 組成:測量規格(MeasureSpec) = 測量模式(mode)(高2位) + 測量大小(size)(低30位)。

  • 作用:通過寬測量值widthMeasureSpec和高測量值heightMeasureSpec決定View的大小。

Q1:為什麼說是很大程度決定了View的尺寸規格?

答:View的尺寸規格還受父容器影響,因為父容器影響View的MeasureSpec的建立過程。

Q2:MeasureSpec有幾種模式?

測量模式(Mode)的型別有3種:UNSPECIFIED、EXACTLY 和
AT_MOST。

MeasureSpec的三種模式

2.4.1 MeasureSpec值的計算

  • View: 取決於View的佈局引數(LayoutParams)和父容器的MeasureSpec值。

  • 頂級View: 取決於自身佈局引數 和視窗尺寸。

View

頂級View

計算總結

二、View的工作流程

以下流程圖的方法為原始碼中的方法,感興趣的讀者可以自行檢視原始碼,強推Carson_Ho部落格,內有詳細解讀。

2.1 measure

作用:測量View的寬/高。

注意:某些情況需要多次measure才能確定View的寬高,此時測試的結果不準確,建議在layout過程中onLayout()獲取最終寬/高。

measure測量有兩種情況:

  • 單一View
  • ViewGroup

measure測量的兩種情況

2.1.1 View

說明:

Measure過程中,主要目的就是為了測量出View的寬/高,在measure()入口方法中呼叫了onMeasure(),而在onMeasure方法中,呼叫了getDefaultSize()得出測量後View的寬/高,再呼叫setMeasureDimension()儲存測量後的寬高。測量過程到此結束,結果是儲存了一個測量後的寬高。

Q1:measure流程最後使用的是getDefaultSize()得出的寬/高,那麼這個寬/高是什麼?

  • AT_MOST和EXACTLY:

    getDefaultSize()返回的大小是measureSpec中的specSize,這個specSize就是最終的測量結果。

  • UNSPECIFIED:

    • 有背景

      寬/高為android:minWidth屬性所指定的值,若無指定,則為0。

    • 無背景

      View的寬/高度為android:minWidth/android:minHeight屬性所指定的值和mBackground.getMinimumWidth()/mBackground.getMinimumHeight()中的最大值。

    測量結果

2.1.2 ViewGroup

  • ViewGroup除了完成自己的measure過程之外,還會遍歷所有子View的measure方法。

  • ViewGroup是一個抽象類,沒有重寫View的onMeasure方法(自定義View時需要自己實現)。

  • 提供了一個measureChildren方法。

說明:在ViewGroupmeasure過程中,先在入口measure()內呼叫onMeasure(),與View中的onMeasure不同,ViewGroup中沒有實現這個方法,因為不同的ViewGroup子類(LinearLayoutRelativeLayout / 自定義ViewGroup子類等)具備不同的佈局特性,這導致他們子View的測量方法各有不同,故需自己重寫。

在這個方法中包含了三個方法

  • measureChildren()

系統方法,遍歷子View 並且呼叫measureChild()進行下一步測量。

  • measureCarson()

需要自己重寫,合併所有子View的尺寸大小,最終得到ViewGroup父檢視的測量值。

  • setMeasureDimension()

與單一View一樣,儲存測量後的資料。

2.2 layout

作用:確定View的位置。

兩種情況

2.2.1 View

View的layout過程

說明:

  • 由於單一View是沒有子View的,故onLayout()是一個空實現。

  • 由於在layout()中已經對自身View進行了位置計算,所以單一View的layout過程在layout()後就已完成了。

2.2.2 ViewGroup

ViewGroup的layout過程

說明:ViewgrouponLayout方法中遍歷了子View,呼叫child.layout(),計算每個子View的位置,一開始計算ViewGroup位置時,呼叫的是ViewGrouplayout()onLayout(),當遍歷子View計運算元View位置時,呼叫的是子View的layout()onLayout()

2.3 draw

作用:將View繪製到螢幕上面。

2.3.1 View

說明:所有的檢視最終都是呼叫 View 的 draw ()繪製檢視。

2.3.1 ViewGroup

ViewGroup的draw過程

三、自定義View

3.1 自定義View的分類

  • 繼承View重寫onDraw方法
    • 主要用於實現一些不規則的效果。
    • 重寫onDraw方法,需要支援wrap_content並且需要自己處理padding。
  • 繼承ViewGoup派生特殊的Layout(除了系統佈局,重新定義一種新佈局)
    • 實現自定義佈局。
    • 需要合適的處理ViewGroup的測量,佈局兩個過程,並同時處理子元素的測量和佈局過程。
  • 繼承特定的View
    • 擴充套件某些已有的View的功能。
    • 容易實現,不需要自己支援wrap_content和padding。
  • 繼承特定的ViewGroup
    • 常見,第二種情況實現的也能用這種方式實現。
    • 與第二種的區別就是不用自己處理ViewGroup的測量和佈局,第二種更接近底層。

3.2 自定義View須知

  • 讓View支援wrap_content
  • 讓View支援padding
  • 不要在View中使用Handler
    • View內部本身有post系列的方法,可以完全替代Handler的作用。
  • View中如果有執行緒或動畫,需要及時停止
    • 與生命週期同步,不然會造成記憶體漏失
  • 處理滑動衝突

參考自: