View和ViewGroup的测量(附送单位转换工具类)

作为Android小白,对自定义控件的掌握一直不扎实,系统的学习了一下,下面介绍一下自己对View和ViewGroup的测量的认识跟大家分享。

在现实生活中,如果我们要去画一个图形,就必须知道它的位置和大小。同样的,Android系统在绘制View和ViewGroup的时候,也要对其进行测量。

ViewGroup是View的容器,ViewGroup会管理其的子View,它的一个管理项目就是负责子View的显示大小。当ViewGroup的大小为wrap_content时,ViewGroup就需要对子View进行遍历,以便获得所有子View的大小,从而决定自己的大小,而在其他的模式之下,会通过具体的指定值来设置自身的大小。

  在自定义ViewGroup时,通常会去重写onLayout()方法来控制其子View显示位置的逻辑。同样,如果需要支持wrap_content属性,那么它必须重写onMeasure()方法。

下面详细介绍一下View的测量过程。

Android系统通过MeasureSpace类来进行View的测量。

测量的模式可分为以下三种:

1、EXACTLY

精确模式,当我们的控件的layout_width属性或高度属性为具体数值时,例如100dp,或者match_parent,系统使用的就是这种模式。

2、AT_MOST

最大值模式,当控件的layout_width属性或高度属性指定为wrap_content时,系统使用这种模式。

3、UNSPECIFIED

这个属性比较奇怪——它不指定其大小测量模式i,View想多大就多大,通常情况下在绘制自定义View时使用。


View类默认的onMeasure()方法之支持EXACTLY模式,所以在自定义控件的时候,如果不重写onMeasure()方法,只能使用EXACTLY模式。所以如果要让自定义的View支持wrap_content属性,就必须重写onMeasure()方法。

下面来看一下onMeasure()的源码:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
可以看出来,系统最终会调用setMeasureDimension(int measuredWidth,int measureHeight)方法将测量的宽高是指进去,所以重写 onMeasure()就需要把测量后的宽高值作为参数传递给setMeasureDimension()方法;

下面贴上我的练习代码:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
}

private int measureHeight(int measureSpec) {
    int result=0;
    int spacMode=MeasureSpec.getMode(measureSpec);//获得测量的模式
    int spacSize=MeasureSpec.getSize(measureSpec);//获得测量的宽度尺寸

    if (spacMode==MeasureSpec.EXACTLY){
        result=spacSize;
    }else {
        result= DisplayUtil.dip2px(getContext(),200);
        if (spacMode==MeasureSpec.AT_MOST){
            result=Math.min(result,spacSize);
        }
    }

    return result;
}

private int measureWidth(int measureSpec) {
    int result=0;
    int spacMode=MeasureSpec.getMode(measureSpec);//获得测量的模式
    int spacSize=MeasureSpec.getSize(measureSpec);//获得测量的高度尺寸

    if (spacMode==MeasureSpec.EXACTLY){
        result=spacSize;
    }else {
        result=DisplayUtil.dip2px(getContext(),200);
        if (spacMode==MeasureSpec.AT_MOST){
            result=Math.min(result,spacSize);
        }
    }

    return result;
}

在布局文件中,先指定确定的宽高200dp,效果如下:



当指定宽高属性为match_parent或者宽高属性为warp_content而不进行测量的情况下,效果如下:


重写onMeasure()方法之后,效果如下:


以上,就是自己对view的测量的理解,也是自定义view的第一步。


还有就是代码中出现的DisplayUtils是一个自己写的单位转换工具类,为了方便大家学习,也将代码贴在下面:

/**
 * 单位转换工具类
 * Created by xyd on 2016/1/7.
 */
public class DisplayUtil {

    /**
     * px值转换为dipdp值,保证尺寸大小不变
     *
     * @param context
     * @param pxValue
     * @return
     */
    public static int px2dip(Context context, float pxValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (pxValue / scale + 0.5f);
    }

    /**
     * dipdp值转换为px值,保证尺寸大小不变
     *
     * @param context
     * @param dipValue
     * @return
     */
    public static int dip2px(Context context, float dipValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dipValue * scale + 0.5f);
    }

    /**
     * px值转换为sp值,保证尺寸大小不变
     *
     * @param context
     * @param pxValue
     * @return
     */
    public static int px2sp(Context context, float pxValue) {
        final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
        return (int) (pxValue / fontScale + 0.5f);
    }

    /**
     * sp值转换为px值,保证尺寸大小不变
     *
     * @param context
     * @param spValue
     * @return
     */
    public static int sp2px(Context context, float spValue) {
        final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
        return (int) (spValue * fontScale + 0.5f);
    }

}
今天就到这里,下班回家。。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值