自定义View之View的测量

1.view的测量时机

当view处于某个父布局里面,onMeasure()方法被父view调用,开始测量大小,可见view的大小是由其父布局和自身大小共同决定的。

2.view的测量流程

系统的测量方法

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

传入该view想要的大小,以及对应的测量模式MeasureSpec(父View传入的),然后调用setMeasuredDimension()方法就实现了view的测量。

3.MeasureSpec类

MeasureSpec是一个32位的int值,其中高2位为测量的模式,低30位为测量的大小。

     int specMode = MeasureSpec.getMode(measureSpec);
     int specSize = MeasureSpec.getSize(measureSpec); 
  • EXACTLY:精确值模式(1073741824)

当我们将空间的layout_width或者layout_height属性指定为具体值时,或者指定为match_parent属性时,系统使用的是EXACTLY。

  • AT_MOST:最大值模式(-2147483648)

当空间的layout_width属性或者layout_height属性为wrap_content时,控件大小一般随着空间的子控件或者内容的变化而变化,此时,控件的尺寸只要不超过父控件允许的最大尺寸即可。

  • UNSPECIFIED :无规则模式 (0)

不指定其测量大小,通常情况下在绘制自定义View时才会使用它。

自定义View之View的测量_第1张图片
image

从最顶层的DecorVie测量模式说起

/**
     * Figures out the measure spec for the root view 
     * in a window based on it's layout params.
     *
     * @param windowSize
     * The available width or height of the window
     *
     * @param rootDimension
     * The layout params for one dimension (width or height) of the window.
     *
     * @return The measure spec to use to measure the root view.
     */
    private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {
        //匹配父容器时,测量模式为MeasureSpec.EXACTLY,
        //测量大小直接为屏幕的大小,也就是充满真个屏幕
        case ViewGroup.LayoutParams.MATCH_PARENT:
            // Window can't resize. Force root view to be windowSize.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, 
            MeasureSpec.EXACTLY);
            break;
        //包裹内容时,测量模式为MeasureSpec.AT_MOST,
        //测量大小直接为屏幕大小,也就是充满真个屏幕
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            // Window can resize. Set max size for root view.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, 
            MeasureSpec.AT_MOST);
            break;
        //其他情况时,测量模式为MeasureSpec.EXACTLY,
        //测量大小为DecorView顶层视图布局设置的大小。
        default:
            // Window wants to be an exact size. Force root view to be that size.
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, 
            MeasureSpec.EXACTLY);
            break;
        }
        return measureSpec;
    }

分析:该方法主要作用是在整个窗口的基础上计算出root view(顶层视图DecorView)的测量规格,该方法的两个参数分别表示:
windowSize:当前手机窗口的有效宽和高,一般都是除了通知栏的屏幕宽和高
rootDimension 根布局DecorView请求的宽和高,在布局中是MATCH_PARENT。

可知,我们的DecorView根布局宽和高都是MATCH_PARENT,因此DecorView根布局的测量模式就是MeasureSpec.EXACTLY,测量大小一般都是整个屏幕大小,所以一般我们的Activity窗口都是全屏的。

从最顶层Decoriew发起测量,就是view树自顶而下进行遍历,不断调用子view的onMeasure方法测量其自身大小。系统默认测量方法如下:

protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension( getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
/**
 * 为宽度获取一个建议最小值
 */
protected int getSuggestedMinimumWidth () {
    return (mBackground == null) ? mMinWidth : max(mMinWidth , mBackground.getMinimumWidth());
}
/**
 * 获取默认的宽高值
 */
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;
}

由此可知,自定义view直接继承veiw,布局参数:wrap_content,结合传父view的MeasureSpec,此时子view的MeasureSpec判定为AT_MOST,并且specSize等于父view的宽高。默认情况下WRAP_CONTENT 的处理也将填充整个父控件。

4.自定义veiw直接继承view测量要点

注意要点:

  • AT_MOST模式下必须重写测量方法onMeasure()方法
  • 自定义图形文字,除了计算自身图形或者文本的宽高,支持padding,还要计算padding

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int defaultwith = (int) (getPaddingLeft() + getPaddingRight());
        int defaultHeight = (int) (getPaddingTop() + getPaddingBottom());
    
        setMeasuredDimension(getWidthSize(defaultwith, widthMeasureSpec), getHeightSize(defaultHeight, heightMeasureSpec));
    }
    
    
 public static int getWidthSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
            case MeasureSpec.UNSPECIFIED:
                result = size;
                //LogUtil.d("width specMode =" + specMode + ",specSize" + result);
                break;
            case MeasureSpec.AT_MOST:
                result = Math.min(result, specSize);
                break;
            case MeasureSpec.EXACTLY:
                result = specSize;
                // LogUtil.d("width specMode =" + specMode + ",specSize" + result);
                break;
        }
        return result;
    }
    
    
public static int getHeightSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
            case MeasureSpec.UNSPECIFIED:
                result = size;
                // LogUtil.d("Height specMode =" + specMode + ",specSize" + result);
                break;
            case MeasureSpec.AT_MOST:
                result = Math.min(result, specSize);
                break;
            case MeasureSpec.EXACTLY:
                result = specSize;
                // LogUtil.d("Height specMode =" + specMode + ",specSize" + result);
                break;
        }
        return result;
    }
    
    

你可能感兴趣的:(自定义View之View的测量)