作为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值转换为dip或dp值,保证尺寸大小不变 * * @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); } /** * 将dip或dp值转换为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); } }今天就到这里,下班回家。。