measure機制流程理解

初始入門,沒有看具體的源碼,只是用到時結合幾篇文章得出自己目前的理解。有很多錯誤的地方,以後在學習研究內核剖析時在深入研究


參考:


(1)最外層根視圖得到MeasureSpec的流程

對於每一個View,包括DecorView,都持有一個MeasureSpec,而該MeasureSpec則保存了該View的尺寸規格。那麼,對於DecorView來說,它已經是頂層view了,沒有父容器,那麼它的MeasureSpec怎麼來的呢?

步驟:

  1. View系統的繪製流程會從ViewRoot的performTraversals()方法中開始
    ——>

  2. 調用getRootMeasureSpec(int windowSize, int rootDimension)方法去獲取widthMeasureSpec和heightMeasureSpec的值(是根視圖的MeasureSpec,詳細實現於3),用於傳遞給measure()的參數

    • windowSize→賦值:desiredWindowWidth或desiredWindowHeight。是屏幕的尺寸,相當於specSize都是等於windowSize
    • rootDimension→賦值:lp.width或lp.height,相當於MATCH_PARENT(根視圖全屏)

    ——>

  3. getRootMeasureSpec()裏使用MeasureSpec.makeMeasureSpec()方法來組裝一個MeasureSpec。具體分類:

    • rootDimension:MATCH_PARENT→MeasureSpec的specMode:EXACTLY
    • rootDimension:WRAP_CONTENT→MeasureSpec的specMode:AT_MOST
    • 並且MATCH_PARENT和WRAP_CONTENT時的specSize都是等於windowSize的

    ——>

    以上就是得到了一份DecorView根視圖的MeasureSpec:childWidthMeasureSpec和childHeightMeasureSpec

  4. 接着就執行performMeasure裏的 performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec)方法,調用其中的mView.measure(mView就是DecorView)也就是說,從頂級View開始了測量流程,那麼我們直接進入measure流程。

注:由於DecorView繼承自FrameLayout,是PhoneWindow的一個內部類,而FrameLayout沒有measure方法,因此調用的是父類View的measure方法。
我們知道,整棵View樹的根節點是DecorView,它是一個FrameLayout,所以它是一個ViewGroup,所以整棵View樹的測量是從一個ViewGroup對象的measure方法開始的。
(有疑問)


measure流程可以分爲兩類:

  • ViewGroup的測量流程
  • View的測量流程

我們先看View類中measure和onMeasure函數:

(2)View的測量流程

  1. view類中的measure()方法,無法在子類中去重寫這個方法
    public final void measure(int widthMeasureSpec, int heightMeasureSpec);
    ——>

  2. onMeasure()方法回調,纔是真正去測量並設置View大小的地方。可以默認實現,也可以自定義實現

    • 2.1. onMeasure的默認實現很簡單,源碼如下:
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
    /*
    onMeasure()默認會調用getDefaultSize(int size, int measureSpec)方法來獲取視圖的大小
    1、系統默認行爲
    2、MeasureSpec.UNSPECIFIED時,返回size(理解:具體佈局數值,則measure()沒有必要了)
    3、specMode等於AT_MOST或EXACTLY時,就返回specSize(specSize = MeasureSpec.getSize(measureSpec))
    */
    • 2.2.當然也可以重載onMeasure,並調用setMeasuredDimension來設置任意大小的佈局,但一般不這麼做,因爲這種做法太“專政”。

3.之後會在onMeasure()方法中調用setMeasuredDimension()方法來設定測量出的大小,即把測量結果保存起來,具體保存在mMeasuredWidth和mMeasuredHeight中。結束一次measure過程。//


(3)ViewGroup的測量流程

  1. ViewGroup中定義了一個measureChildren()方法來去測量(遍歷所有)子視圖的大小
    protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec);

    ——>

  2. 逐個調用measureChild()方法來測量相應子視圖的大小。 protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec)
    protected void measureChildWithMargins(View child,
    int parentWidthMeasureSpec, int widthUsed,
    int parentHeightMeasureSpec, int heightUsed)

    對於ViewGroup的子類而言(非ViewGroup的View而言,通過調用上面默認的measure——>onMeasure,即可完成View的測量),以上一次遍歷就是對父視圖提供的measureSpec參數進行了調整(結合自身的LayoutParams參數),具體通過函數getChildMeasureSpec來進行參數調整,然後再來調用child.measure()函數。
    ——>

  3. measureChildren過程中最困難的一部分,爲child計算MeasureSpec。該方法爲每個child的每個維度(寬、高)計算正確的MeasureSpec。目標就是把當前viewgroup的MeasureSpec和child的LayoutParams結合起來,生成最合理的結果。
    public static int getChildMeasureSpec(int spec, int padding, int childDimension)
    具體實現:通過父視圖的measureSpec、自身的lp參數(layout_width和layout_height中定義的,childDimension包括:具體數值、LayoutParams.MATCH_PARENT、LayoutParams.WRAP_CONTENT)和size(自身大小 int size = Math.max(0, specSize - padding); )來計算自身的measureSpec
    ——>
  4. 接着循環到4,直到遍歷完所有子視圖。測量始於DecorView,通過不斷的遍歷子View的measure方法,根據ViewGroup的MeasureSpec及子View的LayoutParams來決定子View的MeasureSpec,進一步獲取子View的測量寬高,然後逐層返回,不斷保存ViewGroup的測量寬高。

  • 總之:視圖大小的控制是由父視圖、佈局文件、以及視圖本身共同完成的,父視圖會提供給子視圖參考的大小,而開發人員可以在XML文件中指定視圖的大小,然後視圖本身會對最終的大小進行拍板。
  • 探究:EXACTLY、AT_MOST與MATCH_PARENT和WRAP_CONTENT的關係??
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章