自定義View系列教程02--onMeasure源碼詳盡分析

引言

本章內容較多,先養養眼


所侵權,刪

大家知道,自定義View有三個重要的步驟:measure,layout,draw。而measure處于該鏈條的首端,占據著極其重要的地位;然而對于measure的理解卻不是那么容易,許多問題都是一知半解,比如:

  • 為什么父View影響到了子View的MeasureSpec的生成?
  • 為什么我們自定義一個View在布局時將其寬或者高指定為wrap_content但是其實際是match_parent的效果?
  • 子View的specMode和specSize的生成依據又是什么?

這些問題以前一直困擾著我,我就去找資料看,搜了一大筐,沮喪的發現這些文章大同小異:只舉個簡單的例子,很少研究為什么;人云亦云,文章里的內容沒有去驗證和深究就發出來了;或者避重就輕直接把難點給繞過去了…….每次,看完這些文章就沒有勇氣去看layout和draw了,就算了;這可能就是《自定義View——從入門到放棄》的劇本吧。看了那么多文章依舊不能解答原來的疑惑;就像聽過了許多大道理依舊不過好這一生。連measure都沒有很好的理解又何談真正的理解layout和draw呢?要是能找到一篇文章能解開這些疑惑該有多好呀! 咦,等等。要是一直沒有找到這么一篇文章那又怎么辦呢?就真的不學習了?妙齡少婦郭大嬸不是說過么:每當你在感嘆,如果有這樣一個東西就好了的時候,請注意,其實這是你的機會。是啊,機會來了。嗯,我們來一起搞清楚這些問題,我們從源碼里來尋找答案。

MeasureSpec基礎知識
系統顯示一個View,首先需要通過測量(measure)該View來知曉其長和寬從而確定顯示該View時需要多大的空間。在測量的過程中MeasureSpec貫穿全程,發揮著不可或缺的作用。 所以,了解View的測量過程,最合適的切入點就是MeasureSpec。 我們先來瞅瞅官方文檔對于MeasureSpec 的介紹:

A MeasureSpec encapsulates the layout requirements passed from parent to child.Each MeasureSpec represents a requirement for either the width or the height.A MeasureSpec is comprised of a size and a mode.

請注意這段話所包含的重要信息點:

  1. MeasureSpec封裝了父布局傳遞給子View的布局要求。
  2. MeasureSpec可以表示寬和高
  3. MeasureSpec由size和mode組成

MeasureSpec通常翻譯為”測量規格”,它是一個32位的int數據. 其中高2位代表SpecMode即某種測量模式,低30位為SpecSize代表在該模式下的規格大小. 可以通過如下方式分別獲取這兩個值:

//獲取specMode
int specMode = MeasureSpec.getMode(measureSpec)
//獲取SpecSize
int specSize = MeasureSpec.getSize(measureSpec)

當然,也可以通過這兩個值生成新的MeasureSpec

int measureSpec=MeasureSpec.makeMeasureSpec(size, mode);

SpecMode一共有三種: MeasureSpec.EXACTLY , MeasureSpec.AT_MOST , MeasureSpec.UNSPECIFIED
嗯哼,它們已經躺在這里了,我們來挨個瞅瞅,每個SpecMode是什么意思
MeasureSpec.EXACTLY
官方文檔的描述:

The parent has determined an exact size for the child. The child is going to be given those bounds regardless of how big it wants to be.

MeasureSpec.EXACTLY模式表示:父容器已經檢測出子View所需要的精確大小。 在該模式下,View的測量大小即為SpecSize。
MeasureSpec.AT_MOST
官方文檔的描述:

The child can be as large as it wants up to the specified size.

MeasureSpec.AT_MOST模式表示:父容器未能檢測出子View所需要的精確大小,但是指定了一個可用大小即specSize 在該模式下,View的測量大小不能超過SpecSize。
MeasureSpec.UNSPECIFIED
官方文檔的描述:

The parent has not imposed any constraint on the child. It can be whatever size it wants.

父容器不對子View的大小做限制.
MeasureSpec.UNSPECIFIED這種模式一般用作Android系統內部,或者ListView和ScrollView等滑動控件,在此不做討論。
看完了這三個SpecMode的含義,我們再從源碼里看看它們是怎么形成的。
在ViewGroup中測量子View時會調用到measureChildWithMargins()方法,或者與之類似的方法。源碼如下:

/**
     * @param child
     * 子View
     * @param parentWidthMeasureSpec
     * 父容器(比如LinearLayout)的寬的MeasureSpec
     * @param widthUsed
     * 父容器(比如LinearLayout)在水平方向已經占用的空間大小
     * @param parentHeightMeasureSpec
     * 父容器(比如LinearLayout)的高的MeasureSpec
     * @param heightUsed
     * 父容器(比如LinearLayout)在垂直方向已經占用的空間大小
     */
    protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
                                           int parentHeightMeasureSpec, int heightUsed) {
15      final MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) child.getLayoutParams();
16      final int childWidthMeasureSpec =
17                getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight +
18                                    lp.leftMargin + lp.rightMargin + widthUsed, lp.width);
19        final int childHeightMeasureSpec =
20                  getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom +
21                                     lp.topMargin + lp.bottomMargin + heightUsed, lp.height);
22        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

請務必注意該方法的參數;明白這幾個參數的含義才能準確理解方法的實現。
通過這些參數看出來一些端倪,該方法要測量子View傳進來的參數卻包含了父容器的寬的MeasureSpec,父容器在水平方向已經占用的空間大小,父容器的高的MeasureSpec,父容器在垂直方向已經占用的空間大小等父View相關的信息。這在一定程度體現了:父View影響著子View的MeasureSpec的生成。

該方法主要有四步操作:
第一步: 得到子View的LayoutParams,請參見第15行代碼。
第二步: 得到子View的寬的MeasureSpec,請參見第16-18行代碼。
第三步: 得到子View的高的MeasureSpec,請參見第19-21行代碼。
第四步: 測量子View,請參見第22行代碼。

第一步,沒啥好說的;第二步和第三步都調用到了getChildMeasureSpec( ),在該方法內部又做了哪些操作呢?

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
2       int specMode = View.MeasureSpec.getMode(spec);
3       int specSize = View.MeasureSpec.getSize(spec);

5        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;

10      switch (specMode) {
11          case View.MeasureSpec.EXACTLY:
                if (childDimension >= 0) {
                    resultSize = childDimension;
                    resultMode = View.MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    resultSize = size;
                    resultMode = View.MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    resultSize = size;
                    resultMode = View.MeasureSpec.AT_MOST;
21              }
22              break;

            case View.MeasureSpec.AT_MOST:
                if (childDimension >= 0) {
                    resultSize = childDimension;
                    resultMode = View.MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    resultSize = size;
                    resultMode = View.MeasureSpec.AT_MOST;
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    resultSize = size;
                    resultMode = View.MeasureSpec.AT_MOST;
                }
                break;

            case View.MeasureSpec.UNSPECIFIED:
                if (childDimension >= 0) {
                    resultSize = childDimension;
                    resultMode = View.MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                    resultMode = View.MeasureSpec.UNSPECIFIED;
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                    resultMode = View.MeasureSpec.UNSPECIFIED;
47              }
48              break;
49      }
50      return View.MeasureSpec.makeMeasureSpec(resultSize, resultMode);
51  }

請注意該方法的參數:

spec
父容器(比如LinearLayout)的寬或高的MeasureSpec

padding
父容器(比如LinearLayout)在垂直方向或者水平方向已被占用的空間。
為什么這么說,它的依據在哪里?
請看在measureChildWithMargins()方法里調用getChildMeasureSpec()的地方,傳遞給getChildMeasureSpec()的第二個參數是如下構成:
比如:mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin +widthUsed
其中:
mPaddingLeft和mPaddingRight表示父容器左右兩內側的padding
lp.leftMargin和lp.rightMargin表示子View左右兩外側的margin
widthUsed水平方向已經使用的空間大小(如子View在水平方向的占用空間)

這四部分都不可以再利用起來布局子View。所以說這些值的和表示:父容器在水平方向已經被占用的空間
同理:mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin +heightUsed,
表示: 父容器(比如LinearLayout)在垂直方向已被占用的空間.

childDimension
通過子View的LayoutParams獲取到的子View的寬或高

所以,從getChildMeasureSpec()方法的第一個參數spec和第二個參數padding也可以看出:
父容器(如LinearLayout)的MeasureSpec和子View的LayoutParams共同決定了子View的MeasureSpec!

明白了該方法的參數,我們再來看方法的具體實現步驟。

  • 第一步:得到父容器的specMode和specSize,請參見第2-3行代碼。
  • 第二步: 得到父容器在水平方向或垂直方向可用的最大空間值,請參見第5行代碼。
  • 第三步: 確定子View的specMode和specSize,請參見第10-50行代碼。

在上面的代碼塊中出現了一個很關鍵的switch語句,該語句的判斷條件就是父View的specMode;在此根據父View的specMode的不同來決定子View的specMode和specSize.

情況1:

父容器的specMode為MeasureSpec.EXACTLY,請參見第11-22行代碼。
也請記住該先決條件,因為以下的討論都是基于此展開的。

1、我們首先看到一個if判斷
if (childDimension >= 0),或許看到這有點懵了:childDimension>=0是啥意思?難道還有小于0的情況?是的,請注意兩個系統常量:
LayoutParams.MATCH_PARENT = -1 和 LayoutParams.WRAP_CONTENT = -2

所以在此處的代碼:
if (childDimension >= 0)
表示子View的寬或高不是match_parent,也不是wrap_content而是一個具體的數值,比如100px。
那么:子View的size就是childDimension,子View的mode也為MeasureSpec.EXACTLY,即:

resultSize = childDimension; 
resultMode = MeasureSpec.EXACTLY;

2、看完這個if,我們來看第一個else if
else if (childDimension == LayoutParams.MATCH_PARENT)
表示子View的寬或高是LayoutParams.MATCH_PARENT。
那么:子View的size就是父容器在水平方向或垂直方向可用的最大空間值即size,子View的mode也為MeasureSpec.EXACTLY,即:

resultSize = size; 
resultMode = MeasureSpec.EXACTLY;

3、我們來看第二個else if
else if (childDimension == LayoutParams.WRAP_CONTENT)
表示子View的寬或高是LayoutParams.WRAP_CONTENT。
那么:子View的size就是父容器在水平方向或垂直方向可用的最大空間值即size,子View的mode為MeasureSpec.AT_MOST,即:

resultSize = size; 
resultMode = MeasureSpec.AT_MOST;

情況2:

父容器的specMode為MeasureSpec.AT_MOST,請參見第24-35行代碼。
也請記住該先決條件,因為以下的討論都是基于此展開的。

1、還是先看這個if判斷
if (childDimension >= 0)
表示子View的寬或高不是match_parent,也不是wrap_content而是一個具體的數值,比如100px。
那么:子View的size就是childDimension,子View的mode也為MeasureSpec.EXACTLY,即:

resultSize = childDimension; 
resultMode = MeasureSpec.EXACTLY;

2、繼續看第一個else if
else if (childDimension == LayoutParams.MATCH_PARENT)
表示子View的寬或高是LayoutParams.MATCH_PARENT。
那么:子View的size就是父容器在水平方向或垂直方向可用的最大空間值即size,子View的mode也為MeasureSpec.AT_MOST,即:

resultSize = size; 
resultMode = MeasureSpec.AT_MOST;

3、接著看第二個else if
else if (childDimension == LayoutParams.WRAP_CONTENT)
表示子View的寬或高是LayoutParams.WRAP_CONTENT。
那么:子View的size就是父容器在水平方向或垂直方向可用的最大空間值即size,子View的mode也為MeasureSpec.AT_MOST

resultSize = size;
resultMode = MeasureSpec.AT_MOST;

情況3:

父容器的specMode為MeasureSpec.UNSPECIFIED,請參見第37-48行代碼。
也請記住該先決條件,因為以下的討論都是基于此展開的。

1、還是先看這個if判斷
if (childDimension >= 0)
表示子View的寬或高不是match_parent,也不是wrap_content而是一個具體的數值,比如100px。
那么:子View的size就是childDimension,子View的mode也為MeasureSpec.EXACTLY,即:

resultSize = childDimension; 
resultMode = MeasureSpec.EXACTLY;

2、繼續看第一個else if
else if (childDimension == LayoutParams.MATCH_PARENT)
表示子View的寬或高是LayoutParams.MATCH_PARENT。
那么:子View的size為0,子View的mode為MeasureSpec.UNSPECIFIED,即:

resultSize = 0; 
resultMode = MeasureSpec.UNSPECIFIED;

3、接著看第二個else if
else if (childDimension == LayoutParams.WRAP_CONTENT)
表示子View的寬或高是LayoutParams.WRAP_CONTENT。
那么:子View的size為0,子View的mode為MeasureSpec.UNSPECIFIED,即:

resultSize = 0; 
resultMode = MeasureSpec.UNSPECIFIED;

至此,我們可以清楚地看到:
子View的MeasureSpec由其父容器的MeasureSpec和該子View本身的布局參數LayoutParams共同決定。
在此經過測量得出的子View的MeasureSpec是系統給出的一個期望值(參考值),我們也可摒棄系統的這個測量流程,直接調用setMeasuredDimension( )設置子View的寬和高的測量值。

對于以上的分析可用表格來規整各一下MeasureSpec的生成


View的MeasureSpace創建規則

好了,看到這個圖,感覺清晰多了。為了便于理解和記憶,我在此再用大白話再對該圖進行詳細的描述:

在哪些具體的情況下子View的SpecMode為MeasureSpec.EXACTLY? 在此,對各情況一一討論和分析:

第一種情況:

當子View的LayoutParams的寬(高)采用具體的值(如100px)時且父容器的MeasureSpec為MeasureSpec.EXACTLY或者MeasureSpec.AT_MOST或者MeasureSpec.UNSPECIFIED時:系統返回給該子View的specMode就為MeasureSpec.EXACTLY,系統返回給該子View的specSize就為子View自己指定的大小(childSize)。

通俗地理解: 子View的LayoutParams的寬(高)采用具體的值(如100px)時,那么說明該子View的大小是非常明確的,明確到了令人發指的地址,都已經到了用具體px值指定的地步了。那么此時不管父容器的specMode是什么,系統返回給該子View的specMode總是MeasureSpec.EXACTLY,并且系統返回給該子View的specSize就是子View自己指定的大小(childSize)。

第二種情況:

當子View的LayoutParams的寬(高)采用match_parent時并且父容器的MeasureSpec為MeasureSpec.EXACTLY時:系統返回給該子View的specMode就為 MeasureSpec.EXACTLY,系統返回給該子View的specSize就為該父容器剩余空間的大小(parentLeftSize)。

通俗地理解: 子View的LayoutParams的寬(高)采用match_parent并且父容器的MeasureSpec為MeasureSpec.EXACTLY。這時候說明子View的大小還是挺明確的:就是要和父容器一樣大,更加直白地說就是父容器要怎樣子View就要怎樣。所以,如果父容器MeasureSpec為MeasureSpec.EXACTLY,那么系統返回給該子View的specMode就為 MeasureSpec.EXACTLY和父容器一樣;系統返回給該子View的specSize就為該父容器剩余空間的大小(parentLeftSize),即為父容器的剩余大小.

在哪些具體的情況下子View的SpecMode為MeasureSpec.AT_MOST? 在此,對各情況一一討論和分析:

第一種情況:

當子View的LayoutParams的寬(高)采用match_parent并且父容器的MeasureSpec為MeasureSpec.AT_MOST時:系統返回給該子View的specMode就為MeasureSpec.AT_MOST,系統返回給該子View的specSize就為該父容器剩余空間的大小(parentLeftSize)

通俗地理解: 子View的LayoutParams的寬(高)采用match_parent并且父容器的MeasureSpec為MeasureSpec.AT_MOST。這時候說明子View的大小還是挺明確的:就是要和父容器一樣大,直白地說就是父容器要怎樣子View就要怎樣。但是此時父容器的大小不是很明確其MeasureSpec為MeasureSpec.AT_MOST,那么系統返回給該子View的specMode就為MeasureSpec.AT_MOST和父容器一樣;系統返回給該子View的specSize就為該父容器剩余空間的大小(parentLeftSize),即為父容器的剩余大小.

第二種情況:

當子View的LayoutParams的寬(高)采用wrap_content時并且父容器的MeasureSpec為MeasureSpec.EXACTLY時:系統返回給該子View的specMode就為 MeasureSpec.AT_MOST,系統返回給該子View的specSize就為該父容器剩余空間的大小(parentLeftSize)

通俗地理解: 子View的LayoutParams的寬(高)采用wrap_content時說明這個子View的寬高不明確,要視content而定。這時如果父容器的MeasureSpec為MeasureSpec.EXACTLY即父容器是一個精確模式。這種情況概況起來簡單地說就是:子View大小是不確定的,但父容器大小是確定的,那么系統返回給該子View的specMode也就是不確定的即為MeasureSpec.AT_MOST,系統返回給該子View的specSize就為該父容器剩余空間的大小(parentLeftSize)

第三種情況:

當子View的LayoutParams的寬(高)采用wrap_content時并且父容器的MeasureSpec為MeasureSpec.AT_MOST時:系統返回給該子View的specMode就為MeasureSpec.AT_MOST,系統返回給該子View的specSize就為該父容器剩余空間的大小(parentLeftSize)
通俗地理解: 子View的LayoutParams的寬(高)采用wrap_content,即說明這個子View的寬高不明確,要視content而定。這個時候父容器的MeasureSpec為MeasureSpec.AT_MOST。這種情況概況起來簡單地說就是:子View的寬高是不確定的,父容器的寬高也是不確定的,那么系統返回給該子View的specMode也就是不確定的即為MeasureSpec.AT_MOST,系統返回給該子View的specSize就為該父容器剩余空間的大小(parentLeftSize)

在哪些具體的情況下子View的SpecMode為MeasureSpec.UNSPECIFIED?

前面也說了該模式在實際開發中極少用到,故在此不做討論。

剛才我們討論的是: measureChildWithMargins( )調用getChildMeasureSpec( )
除此以外還有一種常見的操作: measureChild( )調用getChildMeasureSpec( )
那么,measureChildWithMargins( )和measureChild( )有什么相同點和區別呢?

  • measureChildWithMargins( )和measureChild( )均用來測量子View的大小
  • 兩者在調用getChildMeasureSpec( )均要計算父View已占空間
  • 在measureChild( )計算父View所占空間為mPaddingLeft + mPaddingRight,即父容器左右兩側的padding值之和
  • measureChildWithMargins( )計算父View所占空間為mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin+ widthUsed。此處,除了父容器左右兩側的padding值之和還包括了子View左右的margin值之和( lp.leftMargin + lp.rightMargin),因為這兩部分也是不能用來擺放子View的應算作父View已經占用的空間。這點從方法名measureChildWithMargins也可以看出來它是考慮了子View的margin所占空間的。

其實,關于measureChildWithMargins()在源碼注釋中也說了:

Ask one of the children of this view to measure itself, taking into account both the MeasureSpec requirements for this view and its padding and margins.

好了,搞懂了MeasureSpec我們才正真地進入到View的onMeasure()源碼分析。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
       setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),  
                            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));  
 } 

onMeasure( )源碼流程如下:
(1) 在onMeasure調用setMeasuredDimension( )設置View的寬和高.
(2) 在setMeasuredDimension()中調用getDefaultSize()獲取View的寬和高.
(3) 在getDefaultSize()方法中又會調用到getSuggestedMinimumWidth()或者getSuggestedMinimumHeight()獲取到View寬和高的最小值.
即這一系列的方法調用順序為:


onMeasure()方法中調用順序

為了理清這幾個方法間的調用及其作用,在此按照倒序分析每個方法的源碼。
先來看getSuggestedMinimumWidth( )

//Returns the suggested minimum width that the view should use 
protected int getSuggestedMinimumWidth() {  
    return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());  
} 

該方法返回View的寬度的最小值MinimumWidth.
在此需要注意該View是否有背景.
(1) 若該View沒有背景。 那么該MinimumWidth為View本身的最小寬度即mMinWidth。 有兩種方法可以設置該mMinWidth值: 第一種:XML布局文件中定義minWidth ;第二種:調用View的setMinimumWidth()方法為該值賦值;

(2) 若該View有背景。 那么該MinimumWidth為View本身最小寬度mMinWidth和View背景的最小寬度的最大值

getSuggestedMinimumHeight()方法與此處分析很類似,故不再贅述.
接下來看看getDefaultSize( )的源碼

public static int getDefaultSize(int size, int measureSpec) {  
     int result = size;  
     int specMode = MeasureSpec.getMode(measureSpec);  
     int specSize = MeasureSpec.getSize(measureSpec);  
     switch (specMode) {  
       case MeasureSpec.UNSPECIFIED:  
           result = size;  
           break;  
       case MeasureSpec.AT_MOST:  
       case MeasureSpec.EXACTLY:  
           result = specSize;  
           break;  
     }  

該方法用于獲取View的寬或者高的大小。
該方法的第一個輸入參數size就是調用getSuggestedMinimumWidth()方法獲得的View的寬或高的最小值。

從getDefaultSize()的源碼里的switch可看出該方法的返回值有兩種情況:
(1) measureSpec的specMode為MeasureSpec.UNSPECIFIED
在此情況下該方法的返回值就是View的寬或者高最小值.
該情況很少見,基本上可忽略

(2) measureSpec的specMode為MeasureSpec.AT_MOST或MeasureSpec.EXACTLY:
在此情況下getDefaultSize()的返回值就是該子View的measureSpec中的specSize。

除去第一種情況不考慮以外,可知:
在measure階段View的寬和高由其measureSpec中的specSize決定!!

看了這么久的源碼,我們終于搞清楚了這個問題;但是剛剛舒展開的眉頭又皺起來了。結合剛才的圖發現一個問題:在該圖的最后一行,如果子View在XML布局文件中對于大小的設置采用wrap_content,那么不管父View的specMode是MeasureSpec.AT_MOST還是MeasureSpec.EXACTLY對于子View而言系統給它設置的specMode都是MeasureSpec.AT_MOST,并且其大小都是parentLeftSize即父View目前剩余的可用空間。這時wrap_content就失去了原本的意義,變成了match_parent一樣了.

所以自定義View在重寫onMeasure()的過程中應該手動處理View的寬或高為wrap_content的情況。

至此,已經看完了getSuggestedMinimumWidth()和getDefaultSize()
最后再來看setMeasuredDimension( )的源碼

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {  
     mMeasuredWidth = measuredWidth;  
     mMeasuredHeight = measuredHeight;  
     mPrivateFlags |= MEASURED_DIMENSION_SET;  
}

經過了前面的一系列操作,終于得到了View的寬高。
在此調用setMeasuredDimension( )設置View的寬和高的測量值


好了,關于View的onMeasure( )的源碼及其調用流程都已經分析完了。 但是,剛才還遺留了一個問題: 自定義View在重寫onMeasure()的過程中要處理View的寬或高為wrap_content的情況(請參見下圖中的綠色標記部分)
View的MeasureSpec創建規則

我們該怎么處理呢?
第一種情況:
如果在xml布局中View的寬和高均用wrap_content.那么需要設置
View的寬和高為mWidth和mHeight.
第二種情況:
如果在xml布局中View的寬或高其中一個為wrap_content,那么就將該值設置為默認的寬或高,另外的一個值采用系統測量的specSize即可.

具體的實現可以這樣做:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
2   super.onMeasure(widthMeasureSpec , heightMeasureSpec);  

4   int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);  
5   int widthSpceSize = MeasureSpec.getSize(widthMeasureSpec);  

    int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);  
    int heightSpceSize = MeasureSpec.getSize(heightMeasureSpec);  

    if(widthSpecMode==MeasureSpec.AT_MOST&&heightSpecMode==MeasureSpec.AT_MOST){  
        setMeasuredDimension(mWidth, mHeight);  
    }else if(widthSpecMode==MeasureSpec.AT_MOST){  
        setMeasuredDimension(mWidth, heightSpceSize);  
    }else if(heightSpecMode==MeasureSpec.AT_MOST){  
15      setMeasuredDimension(widthSpceSize, mHeight);  
16  }  

 }  

該部分的處理主要有兩步

第一步:
調用super.onMeasure(),請參見第2行代碼

第二步:
處理子View的大小為wrap_content的情況,請參見第4-16行代碼。
此處涉及到的mWidth和mHeight均為一個默認值;應根據具體情況而設值。
其實,Andorid系統的控件比如TextView等也在onMeasure()中對其大小為wrap_content這一情況作了特殊的處理。

請注意在第二步的代碼中用的判斷條件:
widthSpecMode==MeasureSpec.AT_MOST或者heightSpecMode==MeasureSpec.AT_MOST或者兼而有之;總之,這里的著眼點是模式MeasureSpec.AT_MOST。
看到這里再聯想到下圖就有一個疑問了(請參見下圖中的紅色標記部分):


View的MeasureSpec創建規則

如果子View在布局文件中采用match_parent,并且父容器的SpecMode為MeasureSpec.AT_MOST, 那么此時該子View的SpecMode也為MeasureSpec.AT_MOST且其View的大小為parentLeftSize。

既然此時該子View的SpecMode也為MeasureSpec.AT_MOST那么當執行到onMeasure()時按照我們的判斷邏輯,它的寬或者高至少有一個會被設置成默認值(mWidth和mHeight)。說白了,本來是個match_parent,卻被設置成了具體指的默認值。

看到這里好像覺得也沒啥不對,但是不符合常理!!!到底是哪里錯了??? 我們這么想: 在什么情況下父容器的SpecMode為MeasureSpec.AT_MOST? 這個問題不難回答,共有兩種情況:
情況1: 當父容器的大小為wrap_content時系統給父容器的SpecMode為MeasureSpec.AT_MOST.
情況2: 當父容器的大小為match_parent時系統給父容器的SpecMode為MeasureSpec.AT_MOST.

回答了這個問題就以此答案為基礎繼續討論。 剛才的問題就可以描述為以下兩種情況:
情況1: 當父容器大小為wrap_content且其specMode為MeasureSpec.AT_MOST,子View大小為match_parent。 也就是說:子View想和父容器一樣大但父容器的大小又設定為包裹內容大小即wrap_content。那么,到底誰決定誰呢?誰也不能決定誰!父View和子View這父子倆就這么耗上了。 所以,該情況是理論上存在的但在實際情況中是很不合理甚至錯誤的,當然也是不可取的。

情況2: 當父容器大小為match_parent且其SpecMode為MeasureSpec.AT_MOST,子View大小為match_parent。
既然父容器大小為match_parent且其SpecMode為MeasureSpec.AT_MOST,那么父容器的父容器(以下簡稱“爺容器”)又該是什么情況呢?

  1. 爺容器的大小不可能是wrap_content(原理同情況1)
  2. 爺容器的大小不可能是某個具體的值。 因為若其大小為某具體值,那么其specMode應該為MeasureSpec.EXACTLY;父容器的specMode也該為MeasureSpec.EXACTLY。但是這里父容器的SpecMode為MeasureSpec.AT_MOST,相互矛盾了。
  3. 爺容器的大小是match_parent;那么其SpecMode有兩種情況:MeasureSpec.EXACTLY或者MeasureSpec.AT_MOST。 在此,為了便于理清思路,繼續分情況來討論 第一種情況: 爺容器的大小是match_parent,SpecMode為MeasureSpec.EXACTLY,且父容器此時大小為match_parent;那么父容器的SpecMode應該為MeasureSpec.EXACTLY;但是這里父容器的SpecMode為MeasureSpec.AT_MOST。兩者是矛盾的。所以不會出現這個情況。 第二種情況: 爺容器的大小是match_parent,SpecMode為MeasureSpec.AT_MOST,且父容器此時大小為match_parent,那么父容器的SpecMode可以為MeasureSpec.AT_MOST。這是唯一可能存在的情況。 試著將這種情況抽取出來,就陷入到一個循環:子View大小是match_parent其SpecMode為MeasureSpec.AT_MOST;父View的大小也是match_parent其SpecMode也為MeasureSpec.AT_MOST,爺容器亦如此……..直到根View根為止。但是根View的大小如果為match_parent,那么其SpecMode必為MeasureSpec.EXACTLY。所以這種情況也是矛盾的,也不會出現。
    至此,綜上所述,我們發現:子View在布局文件中采用match_parent,并且父容器的SpecMode為MeasureSpec.AT_MOST,那么此時該子View的SpecMode也為MeasureSpec.AT_MOST且其View的大小為parentLeftSize這種情況本身(圖中紅色標記部分)就是不合理的,不可取的。將這個問題再次抽取就可以簡化為情況1,殊途同歸。
    以此類推:
    (1) 不可能出現根View的大小為wrap_content但它的一個子View大小為match_parent。
    (2) 從根到這個子View的父容器都是wrap_content,而子View的大小為match_parent。這個極端情況也是不會的,可見情況1的分析.
    (3)從根到這個子View的父容器都是wrap_content,而子View大小也為wrap_content。這是個正常情況也正是我們改良后的onMeasure()來專門處理的子View大小為wrap_content的情況。

剛分析完了View的onMeasure()源碼,現在接著看ViewGroup的measure階段的實現

public abstract class ViewGroup extends View implements ViewParent,ViewManager{ }

首先,請注意ViewGroup是一個抽象類,它沒有重寫View的onMeasure( )但它提供了measureChildren( )測量所有的子View。在measureChildren()方法中調用measureChild( )測量每個子View,在該方法內又會調用到child.measure( )方法,這又回到了前面熟悉的流程。 即ViewGroup中測量子View的流程: measureChildren( )—>measureChild( )—>child.measure( )
既然ViewGroup繼承自View而且它是一個抽象類,那么ViewGroup的子類就應該根據自身的要求和特點重寫onMeasure( )方法。 那么這些ViewGroup的子類會怎么測量子View和確定自身的大小呢? 假若ViewGroup知道了每個子View的大小,將它們累加起來是不是就知道了自身的大小呢?
順著這個猜想,我們選擇ViewGroup的子類LinearLayout瞅瞅,就從它的onMeasure( )開始看吧

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mOrientation == VERTICAL) {
            measureVertical(widthMeasureSpec, heightMeasureSpec);
        } else {
            measureHorizontal(widthMeasureSpec, heightMeasureSpec);
        }
    }

嗯哼,代碼里分了水平線性和垂直線性這兩種情況進行測量,類似的邏輯在其onLayout( )階段也會看到的。我們就選measureVertical( )繼續往下看

void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
        mTotalLength = 0;
        int maxWidth = 0;
        int childState = 0;
        int alternativeMaxWidth = 0;
        int weightedMaxWidth = 0;
        boolean allFillParent = true;
        float totalWeight = 0;

        final int count = getVirtualChildCount();

        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        boolean matchWidth = false;
        boolean skippedMeasure = false;

        final int baselineChildIndex = mBaselineAlignedChildIndex;        
        final boolean useLargestChild = mUseLargestChild;

        int largestChildHeight = Integer.MIN_VALUE;

        for (int i = 0; i < count; ++i) {
            final View child = getVirtualChildAt(i);

            if (child == null) {
                mTotalLength += measureNullChild(i);
                continue;
            }

            if (child.getVisibility() == View.GONE) {
               i += getChildrenSkipCount(child, i);
               continue;
            }

            if (hasDividerBeforeChildAt(i)) {
                mTotalLength += mDividerHeight;
            }

            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();

            totalWeight += lp.weight;

            if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
                skippedMeasure = true;
            } else {
                int oldHeight = Integer.MIN_VALUE;

                if (lp.height == 0 && lp.weight > 0) {

                    oldHeight = 0;
                    lp.height = LayoutParams.WRAP_CONTENT;
                }


                measureChildBeforeLayout(
                       child, i, widthMeasureSpec, 0, heightMeasureSpec,
                       totalWeight == 0 ? mTotalLength : 0);

                if (oldHeight != Integer.MIN_VALUE) {
                   lp.height = oldHeight;
                }

                final int childHeight = child.getMeasuredHeight();
               final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
                       lp.bottomMargin + getNextLocationOffset(child));

                if (useLargestChild) {
                    largestChildHeight = Math.max(childHeight, largestChildHeight);
                }
            }


            if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {
               mBaselineChildTop = mTotalLength;
            }
            if (i < baselineChildIndex && lp.weight > 0) {
                throw new RuntimeException("Exception");
            }

            boolean matchWidthLocally = false;
            if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {
                matchWidth = true;
                matchWidthLocally = true;
            }

            final int margin = lp.leftMargin + lp.rightMargin;
            final int measuredWidth = child.getMeasuredWidth() + margin;
            maxWidth = Math.max(maxWidth, measuredWidth);
            childState = combineMeasuredStates(childState, child.getMeasuredState());

            allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
            if (lp.weight > 0) {
                weightedMaxWidth = Math.max(weightedMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);
            } else {
                alternativeMaxWidth = Math.max(alternativeMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);
            }

            i += getChildrenSkipCount(child, i);
        }

        if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {
            mTotalLength += mDividerHeight;
        }

        if (useLargestChild &&
                (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {
            mTotalLength = 0;

115         for (int i = 0; i < count; ++i) {
116             final View child = getVirtualChildAt(i);
117
118             if (child == null) {
                    mTotalLength += measureNullChild(i);
                    continue;
                }

                if (child.getVisibility() == GONE) {
                    i += getChildrenSkipCount(child, i);
                    continue;
                }

                final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
                        child.getLayoutParams();
130             final int totalLength = mTotalLength;
131             mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +
132                     lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
133         }
        }

        mTotalLength += mPaddingTop + mPaddingBottom;

        int heightSize = mTotalLength;

        heightSize = Math.max(heightSize, getSuggestedMinimumHeight());

        int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
        heightSize = heightSizeAndState & MEASURED_SIZE_MASK;

        int delta = heightSize - mTotalLength;
        if (skippedMeasure || delta != 0 && totalWeight > 0.0f) {
            float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;

            mTotalLength = 0;

            for (int i = 0; i < count; ++i) {
                final View child = getVirtualChildAt(i);

                if (child.getVisibility() == View.GONE) {
                    continue;
                }

                LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();

                float childExtra = lp.weight;
                if (childExtra > 0) {
                    int share = (int) (childExtra * delta / weightSum);
                    weightSum -= childExtra;
                    delta -= share;

                    final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                            mPaddingLeft + mPaddingRight +
                                    lp.leftMargin + lp.rightMargin, lp.width);


                    if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) {
                        int childHeight = child.getMeasuredHeight() + share;
                        if (childHeight < 0) {
                            childHeight = 0;
                        }

                        child.measure(childWidthMeasureSpec,
                                MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
                    } else {
                        child.measure(childWidthMeasureSpec,
                                MeasureSpec.makeMeasureSpec(share > 0 ? share : 0,
                                        MeasureSpec.EXACTLY));
                    }

                    childState = combineMeasuredStates(childState, child.getMeasuredState()
                            & (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
                }

                final int margin =  lp.leftMargin + lp.rightMargin;
                final int measuredWidth = child.getMeasuredWidth() + margin;
                maxWidth = Math.max(maxWidth, measuredWidth);

                boolean matchWidthLocally = widthMode != MeasureSpec.EXACTLY &&
                        lp.width == LayoutParams.MATCH_PARENT;

                alternativeMaxWidth = Math.max(alternativeMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);

                allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;

                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() +
                        lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
            }

            mTotalLength += mPaddingTop + mPaddingBottom;
        } else {
            alternativeMaxWidth = Math.max(alternativeMaxWidth,
                                           weightedMaxWidth);

            if (useLargestChild && heightMode != MeasureSpec.EXACTLY) {
                for (int i = 0; i < count; i++) {
                    final View child = getVirtualChildAt(i);

                    if (child == null || child.getVisibility() == View.GONE) {
                        continue;
                    }

                    final LinearLayout.LayoutParams lp =
                            (LinearLayout.LayoutParams) child.getLayoutParams();

                    float childExtra = lp.weight;
                    if (childExtra > 0) {
                        child.measure(
                                MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(),
                                        MeasureSpec.EXACTLY),
                                MeasureSpec.makeMeasureSpec(largestChildHeight,
                                        MeasureSpec.EXACTLY));
                    }
                }
            }
        }

        if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
            maxWidth = alternativeMaxWidth;
        }

238     maxWidth += mPaddingLeft + mPaddingRight;
239     maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
240
241     setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                heightSizeAndState);

        if (matchWidth) {
            forceUniformWidth(count, heightMeasureSpec);
        }
    }

這段代碼的主要操作:

遍歷每個子View,并對每個子View調用measureChildBeforeLayout(),請參見代碼第115-133行
在measureChildBeforeLayout()方法內又會調用measureChildWithMargins()從而測量每個子View的大小。在該過程中mTotalLength保存了LinearLayout的高度,所以每當測量完一個子View該值都會發生變化。
設置LinearLayout的大小,請參見代碼第241
以上就是LinearLayout的onMeasure( )的簡要分析。
其余的ViewGroup的子類均有自己各不相同的measure操作,具體實現請參考對應的源碼。

終于,分析完了和Measure有關的主要代碼。
代碼稍微有點多,所以就有點小小的犯暈了。
再回過頭看剛才討論過的兩個方法:

以下為measureChildWithMargins

/**
     * @param child The child to measure
     * @param parentWidthMeasureSpec The width requirements for this view
     * @param widthUsed Extra space that has been used up by the parent
     *        horizontally (possibly by other children of the parent)
     * @param parentHeightMeasureSpec The height requirements for this view
     * @param heightUsed Extra space that has been used up by the parent
     *        vertically (possibly by other children of the parent)
     */
    protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {

    }

對于參數parentWidthMeasureSpec和parentHeightMeasureSpec注釋的描述是:

The width(height) requirements for this view
父容器對子View寬(高)的要求(或者說是期望值)。

以下為getChildMeasureSpec

/**
     *
     * @param spec The requirements for this view
     * @param padding The padding of this view for the current dimension and margins, if applicable
     * @param childDimension How big the child wants to be in the current dimension
     * @return a MeasureSpec integer for the child
     */
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {

    }

注釋對于第一個參數spec的描述也是類似的:

The requirements for this view
咦,為什么我們在解讀這兩個方法的時候說:
parentWidthMeasureSpec和parentHeightMeasureSpec和spec這三個值表示寬或者高的MeasureSpec呢?

我們的表述和注釋的說明怎么不一樣呢?
難道我們理解錯了?非也,非也。

子View的MeasureSpec是由父View的MeasureSpec及子View自身的LayoutParam共同決定的。
關于“子View自身的LayoutParam”好理解,就是子View的寬或高等條件
那“父View的MeasureSpec”又體現在哪里呢?
當然就是這里出現的parentWidthMeasureSpec和parentHeightMeasureSpec以及spec。只不過文檔說得委婉些“對于子View的要求(期望)”。
其實,我個人覺得理解成為“要求”更好一些,就是父View對子View的要求。比如,在絕大多數情況下父View要求子View的大小不能超過其大小,這就是一種要求。而父View的大小就封裝在父View的MeasureSpec里的。
所以,可以從這個角度來體會文檔的描述。
嗯哼,終于看完了measure的過程。
當理解了MeasureSpec和measure的原理,我們才能更好的理解layout和draw從而掌握自定義View的流程。


who is the next one? ——> layout

原文地址:http://blog.csdn.net/lfdfhl/article/details/51347818

自定義View系列教程01--常用工具介紹
自定義View系列教程02--onMeasure源碼詳盡分析
自定義View系列教程03--onLayout源碼詳盡分析
自定義View系列教程04--Draw源碼分析及其實踐
自定義View系列教程05–示例分析
自定義View系列教程06–詳解View的Touch事件處理
自定義View系列教程07–詳解ViewGroup分發Touch事件
自定義View系列教程08–滑動沖突的產生及其處理

參考文章
Activity中setContentView剖析
Android走進Framework之AppCompatActivity.setContentView

Android LayoutInflater原理分析,帶你一步步深入了解View(一)
Android視圖繪制流程完全解析,帶你一步步深入了解View(二)
Android視圖狀態及重繪流程分析,帶你一步步深入了解View(三)
Android自定義View的實現方法,帶你一步步深入了解View(四)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,488評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,034評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,327評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,554評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,337評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,883評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,975評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,114評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,625評論 1 332
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,555評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,737評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,244評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,973評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,615評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,343評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,699評論 2 370

推薦閱讀更多精彩內容