android學習——MeasureSpec介紹及使用

一、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

public class View implements ... {
...
public static class MeasureSpec {
private static final int MODE_SHIFT = 30; //移位位數爲30
//int類型佔32位,向右移位30位,該屬性表示掩碼值,用來與size和mode進行"&"運算,獲取對應值。
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
 //向右移位30位,其值爲00 + (30位0)  , 即 0x0000(16進製表示)  
    public static final int UNSPECIFIED = 0 << MODE_SHIFT;  
    //向右移位30位,其值爲01 + (30位0)  , 即0x1000(16進製表示)  
    public static final int EXACTLY     = 1 << MODE_SHIFT;  
    //向右移位30位,其值爲02 + (30位0)  , 即0x2000(16進製表示)  
    public static final int AT_MOST     = 2 << MODE_SHIFT;  

    //創建一個整形值,其高兩位代表mode類型,其餘30位代表長或寬的實際值。可以是WRAP_CONTENT、MATCH_PARENT或具體大小exactly size  
    public static int makeMeasureSpec(int size, int mode) {  
        return size + mode;  
    }  
    //獲取模式  ,與運算  
    public static int getMode(int measureSpec) {  
        return (measureSpec & MODE_MASK);  
    }  
    //獲取長或寬的實際值 ,與運算  
    public static int getSize(int measureSpec) {  
        return (measureSpec & ~MODE_MASK);  
    }  

}  
...

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

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

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

public static int makeMeasureSpec(int size, int mode) {
      return size + mode;
}

其用法如下:

 int w = View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED);
 int h = View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED);
 ssidtext.measure(w, h);
 int width =ssidtext.getMeasuredWidth();
 int height =ssidtext.getMeasuredHeight();

二、measure過程詳解

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

ViewRoot類簡要說明: 任何顯示在設備中的窗口,例如:Activity、Dialog等,都包含一個ViewRoot實例,該類主要用來與遠端 WindowManagerService交互以及控制(開始/銷燬)繪製。

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

//開始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);  
    ...  
}  
...

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

public class View implements ... {
...
/**
* This is called to find out how big a view should be. The parent
* supplies constraint information in the width and height parameters.
*
* @param widthMeasureSpec Horizontal space requirements as imposed by the
* parent
* @param heightMeasureSpec Vertical space requirements as imposed by the
* parent
* @see #onMeasure(int, int)
*/
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
//判斷是否爲強制佈局,即帶有“FORCE_LAYOUT”標記 以及 widthMeasureSpec或heightMeasureSpec發生了改變
if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {
// first clears the measured dimension flag  
        //清除MEASURED_DIMENSION_SET標記   ,該標記會在onMeasure()方法後被設置  
        mPrivateFlags &= ~MEASURED_DIMENSION_SET;   

        // measure ourselves, this should set the measured dimension flag back  
        // 1、 測量該View本身的大小 ; 2 、 設置MEASURED_DIMENSION_SET標記,否則接寫來會報異常。  
        onMeasure(widthMeasureSpec, heightMeasureSpec);  

        // flag not set, setMeasuredDimension() was not invoked, we raise  
        // an exception to warn the developer  
        if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {  
            throw new IllegalStateException("onMeasure() did not set the"  
                    + " measured dimension by calling" + " setMeasuredDimension()");  
        }  

        mPrivateFlags |= LAYOUT_REQUIRED;  //下一步是layout了,添加LAYOUT_REQUIRED標記  
    }  

    mOldWidthMeasureSpec = widthMeasureSpec;   //保存值  
    mOldHeightMeasureSpec = heightMeasureSpec; //保存值  
}  
...

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

 3、調用onMeasure()方法去真正設置View的長寬值,其默認實現爲:

//設置該View本身地大小
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}


//@param size參數一般表示設置了android:minHeight屬性或者該View背景圖片的大小值
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);

//根據不同的mode值,取得寬和高的實際值。  
  switch (specMode) {  
  case MeasureSpec.UNSPECIFIED:  //表示該View的大小父視圖未定,設置爲默認值  
      result = size;  
      break;  
  case MeasureSpec.AT_MOST:      //表示該View的大小由父視圖指定了  
  case MeasureSpec.EXACTLY:  
      result = specSize;  
      break;  
  }  
  return result;  
}

//獲得設置了android:minHeight屬性或者該View背景圖片的大小值, 最爲該View的參考值
protected int getSuggestedMinimumWidth() {
int suggestedMinWidth = mMinWidth; // android:minHeight
if (mBGDrawable != null) { // 背景圖片對應地Width。  
      final int bgMinWidth = mBGDrawable.getMinimumWidth();  
      if (suggestedMinWidth < bgMinWidth) {  
          suggestedMinWidth = bgMinWidth;  
      }  
  }  

  return suggestedMinWidth; 
}
//設置View在measure過程中寬和高
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= MEASURED_DIMENSION_SET;  //設置了MEASURED_DIMENSION_SET標記  
}

主要功能就是根據該View屬性(android:minWidth和背景圖片大小)和父View對該子View的"測量要求",設置該View的 mMeasuredWidth 和 mMeasuredHeight 值。
這兒只是一般的View類型地實現方法。一般來說,父View,也就是ViewGroup類型,都需要在重寫onMeasure()方法,遍歷所有子View,設置每個子View的大小。
基本思想如下:遍歷所有子View,設置每個子View的大小。僞代碼表示爲:
//某個ViewGroup類型的視圖
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//必須調用super.ononMeasure()或者直接調用setMeasuredDimension()方法設置該View大小,否則會報異常。
super.onMeasure(widthMeasureSpec , heightMeasureSpec)

//遍歷每個子View
for(int i = 0 ; i < getChildCount() ; i++){
View child = getChildAt(i);
//調用子View的onMeasure,設置他們的大小。childWidthMeasureSpec , childHeightMeasureSpec ?
child.onMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}

如何去設置每個子View的大小,基本思想也如同我們之前描述的思想:遍歷所有子View,設置每個子View的大小。
//widthMeasureSpec 和 heightMeasureSpec 表示該父View的佈局要求
//遍歷每個子View,然後調用measureChild()方法去實現每個子View大小
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) { // 不處於 “GONE” 狀態
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
//測量每個子View高寬時,清楚了該View本身的邊距大小,即android:padding屬性 或android:paddingLeft等屬性標記
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams(); // LayoutParams屬性
//設置子View的childWidthMeasureSpec屬性,去除了該父View的邊距值 mPaddingLeft + mPaddingRight
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
//設置子View的childHeightMeasureSpec屬性,去除了該父View的邊距值 mPaddingTop + mPaddingBottom
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

measureChildren()方法:遍歷所有子View,調用measureChild()方法去設置該子View的屬性值。
measureChild()  方法   : 獲取特定子View的widthMeasureSpec、heightMeasureSpec,調用measure()方法設置子View的實際寬高值。
getChildMeasureSpec()就是獲取子View的widthMeasureSpec、heightMeasureSpec值。
// spec參數 表示該父View本身所佔的widthMeasureSpec 或 heightMeasureSpec值
// padding參數 表示該父View的邊距大小,見於android:padding屬性 或android:paddingLeft等屬性標記
// childDimension參數 表示該子View內部LayoutParams屬性的值,可以是wrap_content、match_parent、一個精確指(an exactly size),
// 例如:由android:width指定等。
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec); //獲得父View的mode
int specSize = MeasureSpec.getSize(spec); //獲得父View的實際值
int size = Math.max(0, specSize - padding); //父View爲子View設定的大小,減去邊距值,  

int resultSize = 0;    //子View對應地 size 實際值 ,由下面的邏輯條件賦值  
int resultMode = 0;    //子View對應地 mode 值 , 由下面的邏輯條件賦值  

switch (specMode) {  
// Parent has imposed an exact size on us  
//1、父View是EXACTLY的 !  
case MeasureSpec.EXACTLY:   
    //1.1、子View的width或height是個精確值 (an exactly size)  
    if (childDimension >= 0) {            
        resultSize = childDimension;         //size爲精確值  
        resultMode = MeasureSpec.EXACTLY;    //mode爲 EXACTLY 。  
    }   
    //1.2、子View的width或height爲 MATCH_PARENT/FILL_PARENT   
    else if (childDimension == LayoutParams.MATCH_PARENT) {  
        // Child wants to be our size. So be it.  
        resultSize = size;                   //size爲父視圖大小  
        resultMode = MeasureSpec.EXACTLY;    //mode爲 EXACTLY 。  
    }   
    //1.3、子View的width或height爲 WRAP_CONTENT  
    else if (childDimension == LayoutParams.WRAP_CONTENT) {  
        // Child wants to determine its own size. It can't be  
        // bigger than us.  
        resultSize = size;                   //size爲父視圖大小  
        resultMode = MeasureSpec.AT_MOST;    //mode爲AT_MOST 。  
    }  
    break;  

// Parent has imposed a maximum size on us  
//2、父View是AT_MOST的 !      
case MeasureSpec.AT_MOST:  
    //2.1、子View的width或height是個精確值 (an exactly size)  
    if (childDimension >= 0) {  
        // Child wants a specific size... so be it  
        resultSize = childDimension;        //size爲精確值  
        resultMode = MeasureSpec.EXACTLY;   //mode爲 EXACTLY 。  
    }  
    //2.2、子View的width或height爲 MATCH_PARENT/FILL_PARENT  
    else if (childDimension == LayoutParams.MATCH_PARENT) {  
        // Child wants to be our size, but our size is not fixed.  
        // Constrain child to not be bigger than us.  
        resultSize = size;                  //size爲父視圖大小  
        resultMode = MeasureSpec.AT_MOST;   //mode爲AT_MOST  
    }  
    //2.3、子View的width或height爲 WRAP_CONTENT  
    else if (childDimension == LayoutParams.WRAP_CONTENT) {  
        // Child wants to determine its own size. It can't be  
        // bigger than us.  
        resultSize = size;                  //size爲父視圖大小  
        resultMode = MeasureSpec.AT_MOST;   //mode爲AT_MOST  
    }  
    break;  

// Parent asked to see how big we want to be  
//3、父View是UNSPECIFIED的 !  
case MeasureSpec.UNSPECIFIED:  
    //3.1、子View的width或height是個精確值 (an exactly size)  
    if (childDimension >= 0) {  
        // Child wants a specific size... let him have it  
        resultSize = childDimension;        //size爲精確值  
        resultMode = MeasureSpec.EXACTLY;   //mode爲 EXACTLY  
    }  
    //3.2、子View的width或height爲 MATCH_PARENT/FILL_PARENT  
    else if (childDimension == LayoutParams.MATCH_PARENT) {  
        // Child wants to be our size... find out how big it should  
        // be  
        resultSize = 0;                        //size爲0! ,其值未定  
        resultMode = MeasureSpec.UNSPECIFIED;  //mode爲 UNSPECIFIED  
    }   
    //3.3、子View的width或height爲 WRAP_CONTENT  
    else if (childDimension == LayoutParams.WRAP_CONTENT) {  
        // Child wants to determine its own size.... find out how  
        // big it should be  
        resultSize = 0;                        //size爲0! ,其值未定  
        resultMode = MeasureSpec.UNSPECIFIED;  //mode爲 UNSPECIFIED  
    }  
    break;  
}  
//根據上面邏輯條件獲取的mode和size構建MeasureSpec對象。  
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);

每個View大小的設定都事由其父View以及該View共同決定的。但這只是一個期望的大小,每個View在測量時最終大小的設定是由setMeasuredDimension()最終決定的。因此,最終確定一個View的“測量長寬“是由以下幾個方面影響:
1、父View的MeasureSpec屬性;
2、子View的LayoutParams屬性 ;
3、setMeasuredDimension()或者其它類似設定 mMeasuredWidth 和 mMeasuredHeight 值的方法。
 //設置View在measure過程中寬和高
 protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
 mMeasuredWidth = measuredWidth;
 mMeasuredHeight = measuredHeight;
 mPrivateFlags |= MEASURED_DIMENSION_SET;  //設置了MEASURED_DIMENSION_SET標記  
 }

本文出自:http://www.cnblogs.com/nanxiaojue/p/3536381.html?utm_source=tuicool



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章