自定义View的第一步就是要测量Measure控件的大小,所以了解OnMeasure至关重要
首先说说MeasureSpec类
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
MeasureSpec是帮助我们测量View的,MeasureSpec是一个32位的int值,其中高2位表示测量模式,后30位表示测量的大小。
测量模式值得一说,搞不清楚的话,很难写好OnMeasure()方法,测量模式分为三种:
- EXACTLY:精确模式,将控件的高或者宽设置为match_parent或者具体的数值(如100dp)时,系统采用EXACTLY
- AT_MOST,指定为wrap_content时,系统采用AT_MOST,控件随着子控件的内容变化而变化,指的是控件不超过父控件允许的最大尺寸即可
- UNSPECIFIED:系统的测量模式,我们不需要了解,自定义控件也用不到这个
如何让你写的控件支持wrap_content
如果你不重写OnMeasure()方法,你写的自定义控件只是支持EXACTLY模式,即只能用具体的数值(如100dp)或者match_parent,是不支持使用wrap_content的(如果你使用了wrap_content的话,效果与match_parent是一样的),如何让你写的控件支持wrap_content呢,重写OnMeasure()即可,以下代码为模板代码:
// 以下为模板代码,如果不重写onMeasure方法,无法使用wrap_content这个属性,系统不知道该默认什么尺寸,
// 就会默认填充整个父布局
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
}
private int measureWidth(int widthMeasureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec);
if (specMode == MeasureSpec.EXACTLY) {
//如果是EXACTLY模式,测量宽度就是父类传递过来的宽度,(或者说你在xml布局里写的具体的宽度数值或者match_parent)
result = specSize;
} else {
result = DensityUtil.dip2px(context, 200);
if (specMode == MeasureSpec.AT_MOST) {
//如果是AT_MOST模式,测量宽度就是你自己设置的默认宽度与父类传递过来的宽度之间的一个最小值(为什么取最小值呢,因为如果你设置的默认值很大,已经超过了父控件的大小,那么这个控件在父控件里就显示不全了,如果你设置的默认值小于父控件,那么就用你设置的默认值)
result = Math.min(result, specSize);
}
}
return result;
}
private int measureHeight(int heightMeasureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(heightMeasureSpec);
int specSize = MeasureSpec.getSize(heightMeasureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
result = DensityUtil.dip2px(context,200);
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}