天天看點

MeasureSpec介紹及使用 android學習——MeasureSpec介紹及使用 - 南小爵

android學習——MeasureSpec介紹及使用 - 南小爵

時間 2014-01-29 22:29:00   部落格園-所有随筆區 原文   http://www.cnblogs.com/nanxiaojue/p/3536381.html 主題  安卓開發

一、MeasureSpc類說明
  SDK的介紹:MeasureSpc類封裝了父View傳遞給子View的布局(layout)要求。每個MeasureSpc執行個體代表寬度或者高度
      

它有三種模式: 

①、UNSPECIFIED(未指定),父元素部隊自元素施加任何束縛,子元素可以得到任意想要的大小; 

②、EXACTLY(完全),父元素決定自元素的确切大小,子元素将被限定在給定的邊界裡而忽略它本身大小; 

③、AT_MOST(至多),子元素至多達到指定大小的值。

常用的三個函數: 

static int getMode(int measureSpec) : 根據提供的測量值(格式),提取模式(上述三個模式之一) 

static int getSize(int measureSpec) : 根據提供的測量值(格式),提取大小值(這個大小也就是我們通常所說的大小) 

static int makeMeasureSpec(int size,int mode) : 根據提供的大小值和模式,建立一個測量值(格式)

MeasureSpc類源碼分析 其為View.java類的内部類,路徑:frameworksbasecorejavaandroidviewView.java

MeasureSpec介紹及使用 android學習——MeasureSpec介紹及使用 - 南小爵
1 public class View implements ... {
 2 ...
 3 public static class MeasureSpec {
 4 private static final int MODE_SHIFT = 30; //移位位數為30
 5 //int類型占32位,向右移位30位,該屬性表示掩碼值,用來與size和mode進行"&"運算,擷取對應值。
 6 private static final int MODE_MASK = 0x3 << MODE_SHIFT;
 7  //向右移位30位,其值為00 + (30位0)  , 即 0x0000(16進制表示)  
 8     public static final int UNSPECIFIED = 0 << MODE_SHIFT;  
 9     //向右移位30位,其值為01 + (30位0)  , 即0x1000(16進制表示)  
10     public static final int EXACTLY     = 1 << MODE_SHIFT;  
11     //向右移位30位,其值為02 + (30位0)  , 即0x2000(16進制表示)  
12     public static final int AT_MOST     = 2 << MODE_SHIFT;  
13 
14     //建立一個整形值,其高兩位代表mode類型,其餘30位代表長或寬的實際值。可以是WRAP_CONTENT、MATCH_PARENT或具體大小exactly size  
15     public static int makeMeasureSpec(int size, int mode) {  
16         return size + mode;  
17     }  
18     //擷取模式  ,與運算  
19     public static int getMode(int measureSpec) {  
20         return (measureSpec & MODE_MASK);  
21     }  
22     //擷取長或寬的實際值 ,與運算  
23     public static int getSize(int measureSpec) {  
24         return (measureSpec & ~MODE_MASK);  
25     }  
26 
27 }  
28 ...
           

View Code

MeasureSpec類的處理思路是:
      
右移運算,使int 類型的高兩位表示模式的實際值,其餘30位表示其餘30位代表長或寬的實際值----可以是WRAP_CONTENT、MATCH_PARENT或具體大小exactly size。

通過掩碼MODE_MASK進行與運算 “&”,取得模式(mode)以及長或寬(value)的實際值。      

MeasureSpec . makeMeasureSpec 方法,實際上這個方法很簡單:

MeasureSpec介紹及使用 android學習——MeasureSpec介紹及使用 - 南小爵
1 public static int makeMeasureSpec(int size, int mode) {
2             return size + mode;
3      }      

View Code

其用法如下:

MeasureSpec介紹及使用 android學習——MeasureSpec介紹及使用 - 南小爵
1 int w = View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED);
2 int h = View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED);
3 ssidtext.measure(w, h);
4 int width =ssidtext.getMeasuredWidth();
5 int height =ssidtext.getMeasuredHeight();      

View Code

二、measure過程詳解

UI架構開始繪制時,皆是從ViewRoot.java類開始繪制的:

ViewRoot類簡要說明: 任何顯示在裝置中的視窗,例如:Activity、Dialog等,都包含一個ViewRoot執行個體,該類主要用來與遠端 WindowManagerService互動以及控制(開始/銷毀)繪制。      

1、開始UI繪制 , 具體繪制方法則是:

MeasureSpec介紹及使用 android學習——MeasureSpec介紹及使用 - 南小爵
//開始View繪制流程  
private void performTraversals(){  
    ...  
    //這兩個值我們在後面讨論時,在回過頭來看看是怎麼指派的。現在隻需要記住其值MeasureSpec.makeMeasureSpec()建構的。  
    int childWidthMeasureSpec; //其值由MeasureSpec類建構 , makeMeasureSpec  
    int childHeightMeasureSpec;//其值由MeasureSpec類建構 , makeMeasureSpec  


    // Ask host how big it wants to be  
    host.measure(childWidthMeasureSpec, childHeightMeasureSpec);  
    ...  
}  
...        

View Code

2、調用measure()方法去做一些前期準備 measure()方法原型定義在View.java類中,final修飾符修飾,其不能被重載:

MeasureSpec介紹及使用 android學習——MeasureSpec介紹及使用 - 南小爵
1 public class View implements ... {
 2 ...
 3 /**
 4 * This is called to find out how big a view should be. The parent
 5 * supplies constraint information in the width and height parameters.
 6 *
 7 * @param widthMeasureSpec Horizontal space requirements as imposed by the
 8 * parent
 9 * @param heightMeasureSpec Vertical space requirements as imposed by the
10 * parent
11 * @see #onMeasure(int, int)
12 */
13 public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
14 //判斷是否為強制布局,即帶有“FORCE_LAYOUT”标記 以及 widthMeasureSpec或heightMeasureSpec發生了改變
15 if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
16 widthMeasureSpec != mOldWidthMeasureSpec ||
17 heightMeasureSpec != mOldHeightMeasureSpec) {
18 // first clears the measured dimension flag  
19         //清除MEASURED_DIMENSION_SET标記   ,該标記會在onMeasure()方法後被設定  
20         mPrivateFlags &= ~MEASURED_DIMENSION_SET;   
21 
22         // measure ourselves, this should set the measured dimension flag back  
23         // 1、 測量該View本身的大小 ; 2 、 設定MEASURED_DIMENSION_SET标記,否則接寫來會報異常。  
24         onMeasure(widthMeasureSpec, heightMeasureSpec);  
25 
26         // flag not set, setMeasuredDimension() was not invoked, we raise  
27         // an exception to warn the developer  
28         if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {  
29             throw new IllegalStateException("onMeasure() did not set the"  
30                     + " measured dimension by calling" + " setMeasuredDimension()");  
31         }  
32 
33         mPrivateFlags |= LAYOUT_REQUIRED;  //下一步是layout了,添加LAYOUT_REQUIRED标記  
34     }  
35 
36     mOldWidthMeasureSpec = widthMeasureSpec;   //儲存值  
37     mOldHeightMeasureSpec = heightMeasureSpec; //儲存值  
38 }  
39 ...
           

View Code

參數widthMeasureSpec和heightMeasureSpec 由父View建構,表示父View給子View的測量要求。其值地建構如下:
measure()方法顯示判斷是否需要重新調用設定改View大小,即調用onMeasure()方法,然後操作兩個辨別符:
①、重置MEASURED_DIMENSION_SET : onMeasure()方法中,需要添加該辨別符,否則,會報異常;
②、添加LAYOUT_REQUIRED : 表示需要進行layout操作。最後,儲存目前的widthMeasureSpec和heightMeasureSpec值。


           

 3、 調用onMeasure()方法去真正設定View的長寬值,其預設實作為:

MeasureSpec介紹及使用 android學習——MeasureSpec介紹及使用 - 南小爵
1 //設定該View本身地大小
 2 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 3 setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
 4 getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
 5 }
 6 
 7 
 8 //@param size參數一般表示設定了android:minHeight屬性或者該View背景圖檔的大小值
 9 public static int getDefaultSize(int size, int measureSpec) {
10 int result = size;
11 int specMode = MeasureSpec.getMode(measureSpec);
12 int specSize = MeasureSpec.getSize(measureSpec);
13 
14 //根據不同的mode值,取得寬和高的實際值。  
15   switch (specMode) {  
16   case MeasureSpec.UNSPECIFIED:  //表示該View的大小父視圖未定,設定為預設值  
17       result = size;  
18       break;  
19   case MeasureSpec.AT_MOST:      //表示該View的大小由父視圖指定了  
20   case MeasureSpec.EXACTLY:  
21       result = specSize;  
22       break;  
23   }  
24   return result;  
25 }
26 
27 //獲得設定了android:minHeight屬性或者該View背景圖檔的大小值, 最為該View的參考值
28 protected int getSuggestedMinimumWidth() {
29 int suggestedMinWidth = mMinWidth; // android:minHeight
30 if (mBGDrawable != null) { // 背景圖檔對應地Width。  
31       final int bgMinWidth = mBGDrawable.getMinimumWidth();  
32       if (suggestedMinWidth < bgMinWidth) {  
33           suggestedMinWidth = bgMinWidth;  
34       }  
35   }  
36 
37   return suggestedMinWidth; 
38 }
39 //設定View在measure過程中寬和高
40 protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
41 mMeasuredWidth = measuredWidth;
42 mMeasuredHeight = measuredHeight;
43 mPrivateFlags |= MEASURED_DIMENSION_SET;  //設定了MEASURED_DIMENSION_SET标記  
44 }
           

View Code

主要功能就是根據該View屬性(android:minWidth和背景圖檔大小)和父View對該子View的"測量要求",設定該View的 mMeasuredWidth 和 mMeasuredHeight 值。
這兒隻是一般的View類型地實作方法。一般來說,父View,也就是ViewGroup類型,都需要在重寫onMeasure()方法,周遊所有子View,設定每個子View的大小。基本思想如下:周遊所有子View,設定每個子View的大小。僞代碼表示為:
           
MeasureSpec介紹及使用 android學習——MeasureSpec介紹及使用 - 南小爵
1 //某個ViewGroup類型的視圖
 2 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 3 //必須調用super.ononMeasure()或者直接調用setMeasuredDimension()方法設定該View大小,否則會報異常。
 4 super.onMeasure(widthMeasureSpec , heightMeasureSpec)
 5 
 6 //周遊每個子View
 7 for(int i = 0 ; i < getChildCount() ; i++){
 8 View child = getChildAt(i);
 9 //調用子View的onMeasure,設定他們的大小。childWidthMeasureSpec , childHeightMeasureSpec ?
10 child.onMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
11 }
12 }
           

View Code

如何去設定每個子View的大小,基本思想也如同我們之前描述的思想:周遊所有子View,設定每個子View的大小。      
MeasureSpec介紹及使用 android學習——MeasureSpec介紹及使用 - 南小爵
1 //widthMeasureSpec 和 heightMeasureSpec 表示該父View的布局要求
 2 //周遊每個子View,然後調用measureChild()方法去實作每個子View大小
 3 protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
 4 final int size = mChildrenCount;
 5 final View[] children = mChildren;
 6 for (int i = 0; i < size; ++i) {
 7 final View child = children[i];
 8 if ((child.mViewFlags & VISIBILITY_MASK) != GONE) { // 不處于 “GONE” 狀态
 9 measureChild(child, widthMeasureSpec, heightMeasureSpec);
10 }
11 }
12 }
13 //測量每個子View高寬時,清楚了該View本身的邊距大小,即android:padding屬性 或android:paddingLeft等屬性标記
14 protected void measureChild(View child, int parentWidthMeasureSpec,
15 int parentHeightMeasureSpec) {
16 final LayoutParams lp = child.getLayoutParams(); // LayoutParams屬性
17 //設定子View的childWidthMeasureSpec屬性,去除了該父View的邊距值 mPaddingLeft + mPaddingRight
18 final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
19 mPaddingLeft + mPaddingRight, lp.width);
20 //設定子View的childHeightMeasureSpec屬性,去除了該父View的邊距值 mPaddingTop + mPaddingBottom
21 final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
22 mPaddingTop + mPaddingBottom, lp.height);
23 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
           

View Code

measureChildren()方法:周遊所有子View,調用measureChild()方法去設定該子View的屬性值。
measureChild()  方法   : 擷取特定子View的widthMeasureSpec、heightMeasureSpec,調用measure()方法設定子View的實際寬高值。
getChildMeasureSpec()就是擷取子View的widthMeasureSpec、heightMeasureSpec值。      
MeasureSpec介紹及使用 android學習——MeasureSpec介紹及使用 - 南小爵
1 // spec參數 表示該父View本身所占的widthMeasureSpec 或 heightMeasureSpec值
 2 // padding參數 表示該父View的邊距大小,見于android:padding屬性 或android:paddingLeft等屬性标記
 3 // childDimension參數 表示該子View内部LayoutParams屬性的值,可以是wrap_content、match_parent、一個精确指(an exactly size),
 4 // 例如:由android:width指定等。
 5 public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
 6 int specMode = MeasureSpec.getMode(spec); //獲得父View的mode
 7 int specSize = MeasureSpec.getSize(spec); //獲得父View的實際值
 8 int size = Math.max(0, specSize - padding); //父View為子View設定的大小,減去邊距值,  
 9 
10 int resultSize = 0;    //子View對應地 size 實際值 ,由下面的邏輯條件指派  
11 int resultMode = 0;    //子View對應地 mode 值 , 由下面的邏輯條件指派  
12 
13 switch (specMode) {  
14 // Parent has imposed an exact size on us  
15 //1、父View是EXACTLY的 !  
16 case MeasureSpec.EXACTLY:   
17     //1.1、子View的width或height是個精确值 (an exactly size)  
18     if (childDimension >= 0) {            
19         resultSize = childDimension;         //size為精确值  
20         resultMode = MeasureSpec.EXACTLY;    //mode為 EXACTLY 。  
21     }   
22     //1.2、子View的width或height為 MATCH_PARENT/FILL_PARENT   
23     else if (childDimension == LayoutParams.MATCH_PARENT) {  
24         // Child wants to be our size. So be it.  
25         resultSize = size;                   //size為父視圖大小  
26         resultMode = MeasureSpec.EXACTLY;    //mode為 EXACTLY 。  
27     }   
28     //1.3、子View的width或height為 WRAP_CONTENT  
29     else if (childDimension == LayoutParams.WRAP_CONTENT) {  
30         // Child wants to determine its own size. It can't be  
31         // bigger than us.  
32         resultSize = size;                   //size為父視圖大小  
33         resultMode = MeasureSpec.AT_MOST;    //mode為AT_MOST 。  
34     }  
35     break;  
36 
37 // Parent has imposed a maximum size on us  
38 //2、父View是AT_MOST的 !      
39 case MeasureSpec.AT_MOST:  
40     //2.1、子View的width或height是個精确值 (an exactly size)  
41     if (childDimension >= 0) {  
42         // Child wants a specific size... so be it  
43         resultSize = childDimension;        //size為精确值  
44         resultMode = MeasureSpec.EXACTLY;   //mode為 EXACTLY 。  
45     }  
46     //2.2、子View的width或height為 MATCH_PARENT/FILL_PARENT  
47     else if (childDimension == LayoutParams.MATCH_PARENT) {  
48         // Child wants to be our size, but our size is not fixed.  
49         // Constrain child to not be bigger than us.  
50         resultSize = size;                  //size為父視圖大小  
51         resultMode = MeasureSpec.AT_MOST;   //mode為AT_MOST  
52     }  
53     //2.3、子View的width或height為 WRAP_CONTENT  
54     else if (childDimension == LayoutParams.WRAP_CONTENT) {  
55         // Child wants to determine its own size. It can't be  
56         // bigger than us.  
57         resultSize = size;                  //size為父視圖大小  
58         resultMode = MeasureSpec.AT_MOST;   //mode為AT_MOST  
59     }  
60     break;  
61 
62 // Parent asked to see how big we want to be  
63 //3、父View是UNSPECIFIED的 !  
64 case MeasureSpec.UNSPECIFIED:  
65     //3.1、子View的width或height是個精确值 (an exactly size)  
66     if (childDimension >= 0) {  
67         // Child wants a specific size... let him have it  
68         resultSize = childDimension;        //size為精确值  
69         resultMode = MeasureSpec.EXACTLY;   //mode為 EXACTLY  
70     }  
71     //3.2、子View的width或height為 MATCH_PARENT/FILL_PARENT  
72     else if (childDimension == LayoutParams.MATCH_PARENT) {  
73         // Child wants to be our size... find out how big it should  
74         // be  
75         resultSize = 0;                        //size為0! ,其值未定  
76         resultMode = MeasureSpec.UNSPECIFIED;  //mode為 UNSPECIFIED  
77     }   
78     //3.3、子View的width或height為 WRAP_CONTENT  
79     else if (childDimension == LayoutParams.WRAP_CONTENT) {  
80         // Child wants to determine its own size.... find out how  
81         // big it should be  
82         resultSize = 0;                        //size為0! ,其值未定  
83         resultMode = MeasureSpec.UNSPECIFIED;  //mode為 UNSPECIFIED  
84     }  
85     break;  
86 }  
87 //根據上面邏輯條件擷取的mode和size建構MeasureSpec對象。  
88 return MeasureSpec.makeMeasureSpec(resultSize, resultMode);      

View Code

每個View大小的設定都事由其父View以及該View共同決定的。但這隻是一個期望的大小,每個View在測量時最終大小的設定是由setMeasuredDimension()最終決定的。是以,最終确定一個View的“測量長寬“是由以下幾個方面影響:      
1、父View的MeasureSpec屬性;
2、子View的LayoutParams屬性 ;
3、setMeasuredDimension()或者其它類似設定 mMeasuredWidth 和 mMeasuredHeight 值的方法。
      
MeasureSpec介紹及使用 android學習——MeasureSpec介紹及使用 - 南小爵
1 //設定View在measure過程中寬和高
2 protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
3 mMeasuredWidth = measuredWidth;
4 mMeasuredHeight = measuredHeight;
5 mPrivateFlags |= MEASURED_DIMENSION_SET;  //設定了MEASURED_DIMENSION_SET标記  
6 }