android中View.measure方法詳解

yangxi_001發表於2014-07-30

View

原始碼路徑 frameworks\base\core\java\android\view\View.java

原始碼中國連結:http://www.oschina.net/code/explore/android-2.2-froyo/android/view/View.java

[java] view plaincopy
  1. public final void measure(int widthMeasureSpec, int heightMeasureSpec) {  
  2.     if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||  
  3.             widthMeasureSpec != mOldWidthMeasureSpec ||  
  4.             heightMeasureSpec != mOldHeightMeasureSpec) {  
  5.   
  6.         // first clears the measured dimension flag  
  7.         mPrivateFlags &= ~MEASURED_DIMENSION_SET;  
  8.   
  9.         if (ViewDebug.TRACE_HIERARCHY) {  
  10.             ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);  
  11.         }  
  12.   
  13.         // measure ourselves, this should set the measured dimension flag back  
  14.         onMeasure(widthMeasureSpec, heightMeasureSpec);  
  15.   
  16.         // flag not set, setMeasuredDimension() was not invoked, we raise  
  17.         // an exception to warn the developer  
  18.         if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {  
  19.             throw new IllegalStateException("onMeasure() did not set the"  
  20.                     + " measured dimension by calling"  
  21.                     + " setMeasuredDimension()");  
  22.         }  
  23.   
  24.         mPrivateFlags |= LAYOUT_REQUIRED;  
  25.     }  
  26.   
  27.     mOldWidthMeasureSpec = widthMeasureSpec;  
  28.     mOldHeightMeasureSpec = heightMeasureSpec;  
  29. }  
可以看到measure函式有2個引數,widthMeasureSpec 和 heightMeasureSpec。我最初的疑問是不知道該怎麼傳這兩個引數,於是跟到原始碼裡面看看。這個函式的工作大概如下:

(mPrivateFlags這個還沒研究,先跳過了)

1.檢查傳入的widthMeasureSpec和heightMeasureSpec是否與當前的值是一樣的,不一樣的話,呼叫onMeasure函式,並設定mPrivateFlags。

2.儲存新值到mOldWidthMeasureSpec和mOldHeightMeasureSpec。這兩個變數不用深究了,沒有其他地方用到,就只是在這個函式中用來比較值用的。

3.這裡判斷符合條件後會丟擲一個IllegalStateException的異常,它的提示資訊很清楚,告訴我們要呼叫setMeasuredDimension()方法。但到底是怎麼回事呢?這是在你需要重寫onMeasure函式時需要注意的。

先來看看預設的View的onMeasure函式吧:

[java] view plaincopy
  1. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  2.     setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),  
  3.             getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));  
  4. }  
當我們需要重寫onMeasure時,記得要呼叫setMeasuredDimension來設定自身的mMeasuredWidth和mMeasuredHeight,否則,就會丟擲上面那個異常哦~

繼續來看setMeasuredDimension:

[java] view plaincopy
  1. protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {  
  2.     mMeasuredWidth = measuredWidth;  
  3.     mMeasuredHeight = measuredHeight;  
  4.   
  5.     mPrivateFlags |= MEASURED_DIMENSION_SET;  
  6. }  
哦,很簡單,就是設定了mMeasuredWidth和mMeasuredHeight,然後給mPrivateFlags設定了MEASURED_DIMENSION_SET標誌位。那麼計算都是在getDefaultSize函式裡實現的:
[java] view plaincopy
  1. public static int getDefaultSize(int size, int measureSpec) {  
  2.     int result = size;  
  3.     int specMode = MeasureSpec.getMode(measureSpec);  
  4.     int specSize = MeasureSpec.getSize(measureSpec);  
  5.   
  6.     switch (specMode) {  
  7.     case MeasureSpec.UNSPECIFIED:  
  8.         result = size;  
  9.         break;  
  10.     case MeasureSpec.AT_MOST:  
  11.     case MeasureSpec.EXACTLY:  
  12.         result = specSize;  
  13.         break;  
  14.     }  
  15.     return result;  
  16. }  

看到了一個MeasureSpec,看來主要工作是在這裡,必須得進去看看了。

[java] view plaincopy
  1. public static class MeasureSpec {  
  2.   
  3.     private static final int MODE_SHIFT = 30;  
  4.     private static final int MODE_MASK  = 0x3 << MODE_SHIFT;  
  5.     public static final int UNSPECIFIED = 0 << MODE_SHIFT;  
  6.     public static final int EXACTLY     = 1 << MODE_SHIFT;  
  7.     public static final int AT_MOST     = 2 << MODE_SHIFT;  
  8.   
  9.     public static int makeMeasureSpec(int size, int mode) {  
  10.         return size + mode;  
  11.     }  
  12.   
  13.     public static int getMode(int measureSpec) {  
  14.         return (measureSpec & MODE_MASK);  
  15.     }  
  16.   
  17.     public static int getSize(int measureSpec) {  
  18.         return (measureSpec & ~MODE_MASK);  
  19.     }  
  20. }  

類不大,就都貼出來了,為了精簡篇幅,去掉了註釋和toString函式。

這裡MODE_MASK二進位制是11000(一共30個0)00,也就是最高2位標識mode,其餘位標識size。

接下來回到getDefaultSize函式

通過這個類的方法從引數measureSpec中提取出了specMode和specSize。 specMode的作用在下面的switch語句中可以看出來。

[java] view plaincopy
  1. case MeasureSpec.UNSPECIFIED:  
  2.     result = size;  
  3.     break;  
這裡的size就是getSuggestedMinimumWidth()或者getSuggestedMinimumHeight(),是一個預設的最小寬或高,可以看到如果specMode為MeasureSpec.UNSPECIFIED時,specSize(即我們希望設定的size)是沒有用到的。
[java] view plaincopy
  1. case MeasureSpec.AT_MOST:  
  2. case MeasureSpec.EXACTLY:  
  3.     result = specSize;  
  4.     break;  
當specMode為MeasureSpec.AT_MOST或MeasureSpec.EXACTLY時,從我們傳入的引數measureSpec中提取出來的specSize被採用了。這種情況下上面的size就被廢棄了。當result確定後,就是setMeasuredDimension被呼叫了,在裡面將會對mMeasuredWidth和mMeasuredHeight進行設定。

簡單示例:

OK,現在應該理解了吧,下面是一個呼叫measure方法的示例:

[java] view plaincopy
  1. mTextView.measure(MeasureSpec.EXACTLY + mTextView.getWidth(), MeasureSpec.EXACTLY);  
  2. mTextView.layout(00, mTextView.getMeasuredWidth(), mTextView.getMeasuredHeight());  

把mode標誌和你想設定的大小相加,傳進去就OK啦。這裡設定height的時候我是想設0,因此直接傳了MeasureSpec.EXACTLY進去。

當然,measure完後,並不會實際改變View的尺寸,需要呼叫View.layout方法去進行佈局。按示例呼叫layout函式後,View的大小將會變成你想要設定成的大小。

另外關於layout,包括整個佈局流程,我將要寫另一篇博文介紹。因此在這裡就不再贅述了。

相關文章