Android MeasureSpec介绍及使用

Android MeasureSpec介绍及使用MeasureSpc类说明MeasureSpc类封装了父View传递给子View的布局(layout)要求。每个MeasureSpc实例代表宽度或者高度。它有三种模式:UNSPECIFIED(未指定):父元素不对子元素施加任何束缚,子元素可以得到任意想要的大小;EXACTLY(完全):父元素决定子元素的确切大小,子元素将被限定在给定的边界里而忽略它本身的大小;AT_MOST(最多):子元素至最

MeasureSpc类说明

MeasureSpc类封装了父View传递给子View的布局(layout)要求。每个MeasureSpc实例代表宽度或者高度。

它有三种模式:

  1. UNSPECIFIED(未指定):父元素不对子元素施加任何束缚,子元素可以得到任意想要的大小;
  2. EXACTLY(完全):父元素决定子元素的确切大小,子元素将被限定在给定的边界里而忽略它本身的大小;
  3. AT_MOST(最多):子元素至最多达到指定大小的值。

常用的三个函数:

static int getMode(int measureSpec) : 根据提供的测量格式,提取模式(上述三个模式之一)
static int getSize(int measureSpec) : 根据提供的测量格式,提取大小值(这个大小也就是我们通常所说的大小)
static int makeMeasureSpec(int size,int mode) : 根据提供的大小值和模式,创建一个测量格式

MeasureSpc类源码分析 其为View.java类的内部类,路径:frameworks/base/core/java/android/view/View.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;
}

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

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

 public static int resolveSize(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:
             result = Math.min(size, specSize);
             break;
         case MeasureSpec.EXACTLY:
             result = specSize;
             break;
         }
         return result;
     }

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

MeasureSpec.makeMeasureSpec方法的源码前面已经介绍过,很简单。这样大家不难理解size跟measureSpec区别了。看看它的使用ListView.measureItem(View child)

private void measureItem(View child) {
         ViewGroup.LayoutParams p = child.getLayoutParams();
         if (p == null) {
             p = new ViewGroup.LayoutParams(
                     ViewGroup.LayoutParams.MATCH_PARENT,
                     ViewGroup.LayoutParams.WRAP_CONTENT);
         }

         int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
                 mListPadding.left + mListPadding.right, p.width);
         int lpHeight = p.height;
         int childHeightSpec;
         if (lpHeight > 0) {
             childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
         } else {
             childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
         }
         child.measure(childWidthSpec, childHeightSpec);
     }

ViewGroup.getChildMeasureSpec(int spec, int padding, int childDimension)
计算MeasureSpec然后传递到特定的子视图,此方法用来计算一个合适子视图的尺寸大小(宽度或者高度),此方法的目的在于结合我们从子视图的LayoutParams所给出的MeasureSpec信息来获取最合适的结果。

比如,如果这个View知道自己的大小尺寸(因为它本身的MeasureSpec的model为Exactly,)并且子视图的大小恰好跟父窗口一样大,父窗口必须用给定的大小去layout子视图。

参数:
spec 父窗口传递给子视图的大小和模式
padding 父窗口的边距,也就是xml中的android:padding
childDimension 子视图想要绘制的准确大小,但最终不一定绘制此值

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

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

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
            mMeasuredWidth = measuredWidth;
            mMeasuredHeight = measuredHeight;
            mPrivateFlags |= MEASURED_DIMENSION_SET;
     }

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

Measure过程详解

UI框架开始绘制时,皆是从ViewRoot.java类开始绘制的。

ViewRoot类简要说明: 任何显示在设备中的窗口,例如:Activity、Dialog等,都包含一个ViewRoot实例,该类主要用来与远端 WindowManagerService交互以及控制(开始/销毁)绘制。

开始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

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/36495.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注