当前位置: 代码迷 >> 综合 >> 关于 View.measure 和 MeasureSpec 一下资料整理
  详细解决方案

关于 View.measure 和 MeasureSpec 一下资料整理

热度:13   发布时间:2024-01-21 15:18:35.0

很多时候我们是需要预估View的宽高的。因为View的getWidht和getHeight,getMeasuredWidth和getMeasuredHeight是在view放到layout中显示的时候才能获得正确的值的。
但是往往我们需要在它显示之前就知道它的大小是多少,宽高是多少。

这时候,我们就需要预估View的宽高。

其实之所以在View显示出来之后再获取它的宽高时能正确的得到它的大小,是因为在它显示之前经过了两个步骤:
Q一个是measure()一个是requestLayout()。. }.

 我们要预估View的大小,也要借助这个View.measure()方法。

下面是从网上搜集来的一些信息,懒得进一步整理了,现贴在这里做个记录
View.measure()方法的原型如下View.measure(widthMeasureSpec,heightMeasureSpec)
  它的两个参数是两个可以经过计算获得的MeasureSpec值,不需要时,可以传两个0。通过下面的方法可以计算得到MeasureSpec值。
View.MeasureSpec.makeMeasureSpec(maxW,View.MeasureSpec.AT_MOST)) 

这个方法的两个参数:

第一个参数表示父View中允许这个子View占用的宽/高

第二个参数是模式,

它有三种选择

UNSPECIFIED: 不限定
CEXACTLY: 固定
AT_MOST:最多

转自:http://www.lephone.net/forum.php?mod=viewthread&tid=652&extra=page%3D1\

在还没有构建View 之前无法取得View的度宽。 
在此之前我们必须选 measure 一下. 


测量的三种模式: 
UNSPECIFIED: 不限定 
EXACTLY: 固定 
AT_MOST:最多 

view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); 
measure()后调用getMeasuredWidth()和getMeasuredHeight()得到测量后的值。


 一个MeasureSpec封装了父布局传递给子布局的布局要求,每个MeasureSpec代表了一组宽度和高度的要求。一个MeasureSpec由大小和模式组成。它有三种模式:UNSPECIFIED(未指定),父元素部队自元素施加任何束缚,子元素可以得到任意想要的大小;EXACTLY(完全),父元素决定自元素的确切大小,子元素将被限定在给定的边界里而忽略它本身大小;AT_MOST(至多),子元素至多达到指定大小的值。

 

  它常用的三个函数:

  1.static int getMode(int measureSpec):根据提供的测量值(格式)提取模式(上述三个模式之一)

  2.static int getSize(int measureSpec):根据提供的测量值(格式)提取大小值(这个大小也就是我们通常所说的大小)

  3.static int makeMeasureSpec(int size,int mode):根据提供的大小值和模式创建一个测量值(格式)


  这个类的使用呢,通常在view组件的onMeasure方法里面调用但也有少数例外,看看几个例子:

 

  a.首先一个我们常用到的一个有用的函数,View.resolveSize(int size,int measureSpec)

 

8421     public static int resolveSize(int size, int measureSpec) {
8422         int result = size;
8423         int specMode = MeasureSpec.getMode(measureSpec);
8424         int specSize =  MeasureSpec.getSize(measureSpec);
8425         switch (specMode) {
8426         case MeasureSpec.UNSPECIFIED:
8427             result = size;
8428             break;
8429         case MeasureSpec.AT_MOST:
8430             result = Math.min(size, specSize);
8431             break;
8432         case MeasureSpec.EXACTLY:
8433             result = specSize;
8434             break;
8435         }
8436         return result;
8437     }

  上面既然要用到measureSpec值,那自然表示这个函数通常是在onMeasure方法里面调用的。简单说一下,这个方法的主要作用就是根据你提供的大小和模式,返回你想要的大小值,这个里面根据传入模式的不同来做相应的处理。

  再看看MeasureSpec.makeMeasureSpec方法,实际上这个方法很简单:

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

  这样大家不难理解size跟measureSpec区别了。看看它的使用吧,ListView.measureItem(View child)

 

2464     private void measureItem(View child) {
2465         ViewGroup.LayoutParams p = child.getLayoutParams();
2466         if (p == null) {
2467             p = new ViewGroup.LayoutParams(
2468                     ViewGroup.LayoutParams.MATCH_PARENT,
2469                     ViewGroup.LayoutParams.WRAP_CONTENT);
2470         }
2471 
2472         int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
2473                 mListPadding.left + mListPadding.right, p.width);
2474         int lpHeight = p.height;
2475         int childHeightSpec;
2476         if (lpHeight > 0) {
2477             childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
2478         } else {
2479             childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
2480         }
2481         child.measure(childWidthSpec, childHeightSpec);
2482     }

  measureSpec方法通常在ViewGroup中用到,它可以根据模式(MeasureSpec里面的三个)可以调节子元素的大小。

  注意,使用EXACTLY和AT_MOST通常是一样的效果,如果你要区别他们,那么你就要使用上面的函数View.resolveSize(int size,int measureSpec)返回一个size值,然后使用你的view调用setMeasuredDimension(int,int)函数。

 

8406     protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
8407         mMeasuredWidth = measuredWidth;
8408         mMeasuredHeight = measuredHeight;
8409 
8410         mPrivateFlags |= MEASURED_DIMENSION_SET;
8411     }

  然后你调用view.getMeasuredWidth,view.getMeasuredHeigth 返回的就是上面函数里的mMeasuredWidth,mMeasuredHeight的值。


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 plain copy
  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 plain copy
  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 plain copy
  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 plain copy
  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 plain copy
  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 plain copy
  1. case MeasureSpec.UNSPECIFIED:  
  2.     result = size;  
  3.     break;  
这里的size就是getSuggestedMinimumWidth()或者getSuggestedMinimumHeight(),是一个默认的最小宽或高,可以看到如果specMode为MeasureSpec.UNSPECIFIED时,specSize(即我们希望设置的size)是没有用到的。
[java]  view plain copy
  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 plain copy
  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,包括整个布局流程,我将要写另一篇博文介绍。因此在这里就不再赘述了。



  相关解决方案