Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 自定義View系列教程02--onMeasure源碼詳盡分析

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

編輯:關於Android編程

 

大家知道,自定義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代表在該模式下的規格大小.
可以通過如下方式分別獲取這兩個值:

int specSize = MeasureSpec.getSize(measureSpec)

獲取SpecSize

int specMode = MeasureSpec.getMode(measureSpec)

獲取specMode

當然,也可以通過這兩個值生成新的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) {
        final MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) child.getLayoutParams();
        final int childWidthMeasureSpec =
                  getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight +
                                      lp.leftMargin + lp.rightMargin + widthUsed, lp.width);
        final int childHeightMeasureSpec =
                  getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom +
                                      lp.topMargin + lp.bottomMargin + heightUsed, lp.height);
        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) {
        int specMode = View.MeasureSpec.getMode(spec);
        int specSize = View.MeasureSpec.getSize(spec);

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

        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
            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;
                }
                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;
                }
                break;
        }
        return View.MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

該方法就是確定子View的MeasureSpec的具體實現。

請注意該方法的參數:

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

padding
父容器(比如LinearLayout)在垂直方向或者水平方向已被占用的空間。
為什麼這麼說,它的依據在哪裡?
請看在measureChildWithMargins()方法裡調用getChildMeasureSpec()的地方,傳遞給getChildMeasureSpec()的第二個參數是如下構成:
比如:mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
其中:
mPaddingLeft和mPaddingRight表示父容器左右兩內側的padding
lp.leftMargin和lp.rightMargin表示子View左右兩外側的margin
這四部分都不可以再利用起來布局子View.所以說這些值的和表示:父容器在水平方向已經被占用的空間
同理:
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
表示:
父容器(比如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行代碼。
也請記住該先決條件,因為以下的討論都是基於此展開的。

我們首先看到一個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;

看完這個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;

我們來看第二個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行代碼。
也請記住該先決條件,因為以下的討論都是基於此展開的。

還是先看這個if判斷

if (childDimension >= 0)

表示子View的寬或高不是match_parent,也不是wrap_content而是一個具體的數值,比如100px。
那麼:子View的size就是childDimension,子View的mode也為MeasureSpec.EXACTLY,即:

resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;

繼續看第一個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;

接著看第二個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行代碼。
也請記住該先決條件,因為以下的討論都是基於此展開的。

還是先看這個if判斷

if (childDimension >= 0)

表示子View的寬或高不是match_parent,也不是wrap_content而是一個具體的數值,比如100px。
那麼:子View的size就是childDimension,子View的mode也為MeasureSpec.EXACTLY,即:

resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;

繼續看第一個else if

else if (childDimension == LayoutParams.MATCH_PARENT)

表示子View的寬或高是LayoutParams.MATCH_PARENT。
那麼:子View的size為0,子View的mode為MeasureSpec.UNSPECIFIED,即:

resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;

接著看第二個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的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?

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


好了,搞懂了MeasureSpec我們才正真地進入到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寬和高的最小值.

即這一系列的方法調用順序為:
這裡寫圖片描述

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

先來看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;  
     }  
     return result;  
} 

該方法用於獲取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寬和高的測量值。


好了,關於onMeasure( )的源碼及其調用流程都已經分析完了。

但是,剛才還遺留了一個問題:

自定義View在重寫onMeasure()的過程中要處理View的寬或高為wrap_content的情況(請參見下圖中的綠色標記部分)

 

這裡寫圖片描述

 

我們該怎麼處理呢?

第一種情況:

如果在xml布局中View的寬和高均用wrap_content.那麼需要設置

View的寬和高為mWidth和mHeight.

第二種情況:
如果在xml布局中View的寬或高其中一個為wrap_content,那麼就將該值設置為默認的寬或高,另外的一個值采用系統測量的specSize即可.

具體的實現可以這樣做:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec , heightMeasureSpec);  
    int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);  
    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){  
        setMeasuredDimension(widthSpceSize, mHeight);  
    }  

 }  

該部分的處理主要有兩步

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

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

請注意在第二步的代碼中用的判斷條件:
widthSpecMode==MeasureSpec.AT_MOST或者heightSpecMode==MeasureSpec.AT_MOST或者兼而有之;總之,這裡的著眼點是模式MeasureSpec.AT_MOST。
看到這裡再聯想到下圖就有一個疑問了(請參見下圖中的紅色標記部分):
這裡寫圖片描述
如果子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的情況。


嗯哼,終於看完了measure的過程。
當理解了MeasureSpec和measure的原理,我們才能更好的理解layout和draw從而掌握自定義View的流程。

who is the next one? ——> layout.

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved