MeasureSpec的簡單說明,measurespec
註:Spec為specification的縮寫,以為規格或者說明書的意思(英語不好,專門 用英語翻譯軟體翻譯了一下)。所以顧名思義該類的所以就是定義View的測量規格或者測量規則。這個類是View裡面的嵌套內部類,提供了三個對外公開的static變數UNSPECIFIED,EXACTLY,AT_MOST,,這三個變數統稱為specMode,對於一個View來說它的寬和高各有屬於自己的specMode,至於其具體作用後面會有說說明。MeasureSpec提供了三個方法
1)makeMeasureSpec(int size,int mode):size參數由程式員自己設定,mode必須是specMode的三個值中的一個
2)getMode(int measureSpec):見名知意,方法返回specMode的三個值中的一個,注意方法參數measureSpec,這個參數的值是怎麼得來的呢?正是由makeMeasureSpec方法計算出來的
3)getSize(int measureSpec):擷取View的大小,方法參數的值同樣是由makeMeasureSpec計算的出來的。
注意:看android源碼的時候有一個小技巧:凡是參數的名字中帶有spec尾碼的參數,該值都是由makeMeasureSpec來計算出來的,這也是代碼中良好命名習慣帶來的好處之一。
android的繪製流程中,第一個流程是Measure測量流程,而MeasureSpec在測量的過程中起到了至關重要的作用,不論是自訂View還是android內建的View,只要重寫了onMearsure方法都能發現MeasureSpec的影子。測量過程中View類提供了measure方法,在該方法中調用了onMeasure方法,先看看這兩份方法的簽名 measure(int widthMeasureSpec, int heightMeasureSpec)、onMeasure(int widthMeasureSpec, int heightMeasureSpec),onMeasure方法中的參數是由measure方法直接傳過去的;但是measure方法的參數是由誰來傳過來的呢?還是那句話,看看方法名的尾碼都有spec,這肯定和MeasureSpec方法有關了,確定得說他們的值就是有makeMeasureSpec方法來決定的。事實上正是如此:在measure的時候measure方法是由ViewRoot的performTraversals來調用的,在該方法中調用了getRootMeasureSpec方法,詳見這位大神的部落格。
上面一直強調參數具名引數尾碼為spec的方法跟MeasureSpec的關係密切,下面就順著這個思路沿著View的繼承機構挑幾個類來查看分析MeasureSpec具體作用的體現。View類就有onMeasure和Measure方法,上面已經做了簡單的說明,下面具體看看ViewGroup的方法,在viewGroup裡面是沒有重寫onMeasure方法的,換句話說方法參數帶有spec尾碼的只有measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec),measureChildren(int widthMeasureSpec, int heightMeasureSpec)和measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,int parentHeightMeasureSpec, int heightUsed)。在這裡挑measureChild方法來進行說明:
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) { final LayoutParams lp = child.getLayoutParams(); final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height); //執行child的measure流程 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }對於以上代碼要注意點就是方法參數parentWidthMeasureSpec和parentHeightMeasureSpec說明是父View的Spec,並且在代碼裡調用了getChildMeasureSpec方法。下面就看看getChildMeasureSpec的方法具體都幹了些什麼好事。
方法參數說明:padding:當前父view的內邊距
childDimension: 子視圖想要繪製的準確大小,其值可以是LayoutParams對象的width或者height變數。也就ch是xml檔案中layout_height或者layout_width的值,可以是
match_parent和wrap_content,也可以是具體的值
spec:可以是父View經過makeMeasureSpec計算的值,也可以傳遞使用者自己通過makeMeasureSpec計算的值(如果在你自己的代碼中調用getChildMeasureSpec的話)
public static int getChildMeasureSpec(int spec, int padding, int childDimension) { //1.擷取specMode,注意此時是父類的spec modeint specMode = MeasureSpec.getMode(spec);//擷取view的size int specSize = MeasureSpec.getSize(spec);//設定父view的size int size = Math.max(0, specSize - padding);//因為spec有size和mode共同構成,所以定義了如下兩個變數,經過處理後供makeMeasureSpec使用 int resultSize = 0; int resultMode = 0;//解析specMode並做相應的處理 switch (specMode) { // Parent has imposed an exact size on us//當父View的spec mode為 EXACTLY 時,父View強加給子View一個確切的大小 case MeasureSpec.EXACTLY: //當使用者佈建了寬度值,比如xml檔案裡面設定了dimen值,直接用之 if (childDimension >= 0) { resultSize = childDimension;//在子view自己設定了size的情況下,比如xml檔案裡面layout_width或者layout_height設定了具體的dimen值 resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) {//如果子View layout_width或者layout_height為mactch_parent // Child wants to be our size. So be it.//子view的大小與父view的大小一樣 resultSize = size;//此時賦值為EXACTLY resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.WRAP_CONTENT) {//如果子View layout_width或者layout_height為wrap_content // Child wants to determine its own size. It can't be // bigger than us.//子view的大小由自己來決定,但是不能超過父View的大小 resultSize = size;//此時賦值為At_MOST resultMode = MeasureSpec.AT_MOST; } break; // Parent has imposed a maximum size on us//當父View的spec mode為 AT_MOST 時,父View強加給一個最大的size給子view case MeasureSpec.AT_MOST: //當使用者佈建了寬度值,比如xml檔案裡面設定了dimen值,直接用之 if (childDimension >= 0) { // Child wants a specific size... so be it resultSize = childDimension;//在子view自己設定了size的情況下,比如xml檔案裡面layout_width或者layout_height設定了具體的dimen值 resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) {//如果子View layout_width或者layout_height為mactch_parent // Child wants to be our size, but our size is not fixed. // Constrain child to not be bigger than us.//此時父view的大小和子view的大小一樣 resultSize = size;//此時mode 為AT_MOST resultMode = MeasureSpec.AT_MOST; } else if (childDimension == LayoutParams.WRAP_CONTENT) {//如果子View layout_width或者layout_height為wrap_content // Child wants to determine its own size. It can't be // bigger than us.//子view的大小由自己來決定,但是不能超過父View的大小 resultSize = size;//此時mode 為AT_MOST resultMode = MeasureSpec.AT_MOST; } break; // Parent asked to see how big we want to be//在父View的specMode為UNSPECIFIED,讓子view決定自己有多大 case MeasureSpec.UNSPECIFIED: //當使用者佈建了寬度值,比如xml檔案裡面設定了dimen值,直接用之 if (childDimension >= 0) { // Child wants a specific size... let him have it resultSize = childDimension;//在子view自己設定了size的情況下,比如xml檔案裡面layout_width或者layout_height設定了具體的dimen值 resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size... find out how big it should // be resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size.... find out how // big it should be resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; } break; }//最後調用makeMeasureSpec方法來提供子view的spec return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }通過分析這個方法可以得出如下結論:從這個方法裡可以看出父view的measureSpec在某種程度上決定了子view的measureSpec
1)不論父View 的specMode是EXACTLY、UNSPECIFIED、AT_MOST的哪一種,如果子view在xml檔案裡面把layout_width或者layout_height這是了具體
的dimen值、或者在代碼裡調用相應的方法為view的寬度或者高度設定了具體的值,那麼此時widthSpec的mode 或者heightSpec的mode就是EXACLTY,size就等於layout_width或者
layout_height的值。此時子view的specMode值不受父View的specMode的影響。
2)當父View的specMode為EXACTLY的時候:父View強加給子View一個確切的大小,有如下兩種情況
2.1)子View的layout_width或者layout_height設定為MATCH_PARENT的時候,子View的specMode為EXACTLY
2.2)子View的layout_width或者layout_height設定為WRAP_CONTENT的時候,子View的specMode為AT_MOST
2.3)不論子view為match_parent或者wrap_content,resultSize都等於父類的size.
3)當父view的specMode為AT_MOST的時候:父View強加給一個最大的size給子view,最大的size也就是父view的size
3.1)此時不論子view的為match_parent或者wrap_content,子view的specMode都為AT_MOST
3.2)resultSize的大小被設定為父view的大小
4)當父view的specMode為UNSPECIFIED的時候:
4.1) 此時不論子view的為match_parent或者wrap_content,子view的specMode都為UNSPECIFIED
4.3)此時reusltSize = 0
5)從對UNSPECIFIED分析可以知道,在1)情況之外的情況下,要是想讓子view自己決定自己的寬度或者高度的大小,MeasureSpec.makeMeasureSpec(0,UNSPECIFIED)這麼調用來擷取寬度和高度
的spec
6)根據方法的參數以及方法的實現可以知道,視圖的大小是由父視圖和子視圖共同決定的,前提是子視圖沒有設定具體的dimen值
可以用下表來總結上面的結論,並作為此篇部落格的總結: