happyaday

导航

关于view.measure

在编写下啦刷新的项目代码的时候,在Listview的HeaderView中的head.xml文件中,根布局为RelativeLayout的时候,在计算headerView.measure的时候,出现空指针异常,当将更布局改为Linearlayout就运行正常了。

 

在思考为何在RelativeLayout出现异常的问题的时候,在查阅官方网站的时候,我注意到这个段话:

 

Note: In platform version 17 and lower, RelativeLayout was affected by a measurement bug that could cause child views to be measured with incorrect MeasureSpec values. (See MeasureSpec.makeMeasureSpec for more details.) This was triggered when a RelativeLayout container was placed in a scrolling container, such as a ScrollView or HorizontalScrollView. If a custom view not equipped to properly measure with the MeasureSpec mode UNSPECIFIED was placed in a RelativeLayout, this would silently work anyway as RelativeLayout would pass a very large AT_MOST MeasureSpec instead.

This behavior has been preserved for apps that set android:targetSdkVersion="17" or older in their manifest's uses-sdktag for compatibility. Apps targeting SDK version 18 or newer will receive the correct behavior

翻下来就是说:

在android系统版本在17级以下(包含17的时候)。使用measure会出现NULL异常情况,这个是一个BUG。原因是在RelativeLayout的控件使用在含有scrolling的时候,该含有scrolling的控件中计算空间大小的时候,没有使用MeasureSpec mode UNSPECIFIED的布局方式在RelativeLayout。自定义的控件则会尽可能的使用AT_MOST 来替换对齐方式。

如果你想解决这个问题有2个方法:

1.讲SDK的目标版本升级

2.将需要使用RelativeLayout的上层包一个LinearLayout即可、

 

 

View

 

源码路径 frameworks\base\core\java\android\view\View.java

 

源码中国链接:http://www.oschina.net/code/explore/android-2.2-froyo/android/view/View.java

 

  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. }  

 

    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;
    }

可以看到measure函数有2个参数,widthMeasureSpec 和 heightMeasureSpec。我最初的疑问是不知道该怎么传这两个参数,于是跟到源码里面看看。这个函数的工作大概如下:

(mPrivateFlags这个还没研究,先跳过了)

 

1.检查传入的widthMeasureSpec和heightMeasureSpec是否与当前的值是一样的,不一样的话,调用onMeasure函数,并设置mPrivateFlags。

 

2.保存新值到mOldWidthMeasureSpec和mOldHeightMeasureSpec。这两个变量不用深究了,没有其他地方用到,就只是在这个函数中用来比较值用的。

 

3.这里判断符合条件后会抛出一个IllegalStateException的异常,它的提示信息很清楚,告诉我们要调用setMeasuredDimension()方法。但到底是怎么回事呢?这是在你需要重写onMeasure函数时需要注意的。

 

先来看看默认的View的onMeasure函数吧:

 

  1. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  2.     setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),  
  3.             getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));  
  4. }  

 

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

当我们需要重写onMeasure时,记得要调用setMeasuredDimension来设置自身的mMeasuredWidth和mMeasuredHeight,否则,就会抛出上面那个异常哦~

继续来看setMeasuredDimension:

 

  1. protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {  
  2.     mMeasuredWidth = measuredWidth;  
  3.     mMeasuredHeight = measuredHeight;  
  4.   
  5.     mPrivateFlags |= MEASURED_DIMENSION_SET;  
  6. }  

 

    protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;

        mPrivateFlags |= MEASURED_DIMENSION_SET;
    }

哦,很简单,就是设置了mMeasuredWidth和mMeasuredHeight,然后给mPrivateFlags设置了MEASURED_DIMENSION_SET标志位。那么计算都是在getDefaultSize函数里实现的:

  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. }  

 

    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,看来主要工作是在这里,必须得进去看看了。

 

  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. }  

 

    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语句中可以看出来。

 

 

 

  1. case MeasureSpec.UNSPECIFIED:  
  2.     result = size;  
  3.     break;  

 

        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;

这里的size就是getSuggestedMinimumWidth()或者getSuggestedMinimumHeight(),是一个默认的最小宽或高,可以看到如果specMode为MeasureSpec.UNSPECIFIED时,specSize(即我们希望设置的size)是没有用到的。

  1. case MeasureSpec.AT_MOST:  
  2. case MeasureSpec.EXACTLY:  
  3.     result = specSize;  
  4.     break;  

 

        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;

当specMode为MeasureSpec.AT_MOST或MeasureSpec.EXACTLY时,从我们传入的参数measureSpec中提取出来的specSize被采用了。这种情况下上面的size就被废弃了。当result确定后,就是setMeasuredDimension被调用了,在里面将会对mMeasuredWidth和mMeasuredHeight进行设置。 简单示例: OK,现在应该理解了吧,下面是一个调用measure方法的示例:

  1. mTextView.measure(MeasureSpec.EXACTLY + mTextView.getWidth(), MeasureSpec.EXACTLY);  
  2. mTextView.layout(0, 0, mTextView.getMeasuredWidth(), mTextView.getMeasuredHeight());  

 

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,包括整个布局流程,我将要写另一篇博文介绍。因此在这里就不再赘述了。

 

posted on 2015-06-19 09:46  happyaday  阅读(173)  评论(0编辑  收藏  举报