很多时候我们是需要预估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
- public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
- if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
- widthMeasureSpec != mOldWidthMeasureSpec ||
- heightMeasureSpec != mOldHeightMeasureSpec) {
- // first clears the measured dimension flag
- mPrivateFlags &= ~MEASURED_DIMENSION_SET;
- if (ViewDebug.TRACE_HIERARCHY) {
- ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);
- }
- // measure ourselves, this should set the measured dimension flag back
- 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;
- }
- mOldWidthMeasureSpec = widthMeasureSpec;
- mOldHeightMeasureSpec = heightMeasureSpec;
- }
(mPrivateFlags这个还没研究,先跳过了)
1.检查传入的widthMeasureSpec和heightMeasureSpec是否与当前的值是一样的,不一样的话,调用onMeasure函数,并设置mPrivateFlags。
2.保存新值到mOldWidthMeasureSpec和mOldHeightMeasureSpec。这两个变量不用深究了,没有其他地方用到,就只是在这个函数中用来比较值用的。
3.这里判断符合条件后会抛出一个IllegalStateException的异常,它的提示信息很清楚,告诉我们要调用setMeasuredDimension()方法。但到底是怎么回事呢?这是在你需要重写onMeasure函数时需要注意的。
先来看看默认的View的onMeasure函数吧:
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
- getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
- }
继续来看setMeasuredDimension:
- protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
- mMeasuredWidth = measuredWidth;
- mMeasuredHeight = measuredHeight;
- mPrivateFlags |= MEASURED_DIMENSION_SET;
- }
- public static int getDefaultSize(int size, int measureSpec) {
- int result = size;
- int specMode = MeasureSpec.getMode(measureSpec);
- int specSize = MeasureSpec.getSize(measureSpec);
- switch (specMode) {
- case MeasureSpec.UNSPECIFIED:
- result = size;
- break;
- case MeasureSpec.AT_MOST:
- case MeasureSpec.EXACTLY:
- result = specSize;
- break;
- }
- return result;
- }
看到了一个MeasureSpec,看来主要工作是在这里,必须得进去看看了。
- public static class MeasureSpec {
- private static final int MODE_SHIFT = 30;
- private static final int MODE_MASK = 0x3 << MODE_SHIFT;
- public static final int UNSPECIFIED = 0 << MODE_SHIFT;
- public static final int EXACTLY = 1 << MODE_SHIFT;
- public static final int AT_MOST = 2 << MODE_SHIFT;
- 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);
- }
- }
类不大,就都贴出来了,为了精简篇幅,去掉了注释和toString函数。
这里MODE_MASK二进制是11000(一共30个0)00,也就是最高2位标识mode,其余位标识size。
接下来回到getDefaultSize函数
通过这个类的方法从参数measureSpec中提取出了specMode和specSize。 specMode的作用在下面的switch语句中可以看出来。
- case MeasureSpec.UNSPECIFIED:
- result = size;
- break;
- case MeasureSpec.AT_MOST:
- case MeasureSpec.EXACTLY:
- result = specSize;
- break;
简单示例:
OK,现在应该理解了吧,下面是一个调用measure方法的示例:
- mTextView.measure(MeasureSpec.EXACTLY + mTextView.getWidth(), MeasureSpec.EXACTLY);
- mTextView.layout(0, 0, mTextView.getMeasuredWidth(), mTextView.getMeasuredHeight());
把mode标志和你想设置的大小相加,传进去就OK啦。这里设置height的时候我是想设0,因此直接传了MeasureSpec.EXACTLY进去。
当然,measure完后,并不会实际改变View的尺寸,需要调用View.layout方法去进行布局。按示例调用layout函数后,View的大小将会变成你想要设置成的大小。
另外关于layout,包括整个布局流程,我将要写另一篇博文介绍。因此在这里就不再赘述了。