为什么要重写OnMeasure
在我们使用控件时,想要设置View的尺寸,经常会在布局方法中使用 match_parent设置View填充父元素或wrap_content包裹内容:
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content" />
又或者设定具体的参数值:
<com.example.ast.customview.MyTextView
android:layout_width="250dp"
android:layout_height="100dp"/>
但我们使用自定义View时,match_parent和指定具体参数控件能按要求绘制:
但当使用wrap_content时,情况却不对了:
为了让wrap_content能够正常工作,我们需要重写onMeasure方法。
出现问题的原因
View的绘制有三种模式:UNSPECIFIED
(未指定),EXACTLY
(完全)和AT_MOST
(至多)。这三种模式分别对应:
模式 | 布局参数 | 说明 |
---|---|---|
UNSPECIFIED(未指定) | —— | 控件尺寸无约束,未见过此情况 |
EXACTLY(完全) | match_parent或具体宽高值 | 控件尺寸被指定,内容只能在给定空间内绘制 |
AT_MOST(至多) | wrap_content | 控件尺寸由内容决定且尽可能大 |
查看View内的onMeasure源码:
//onMeasure
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
//getDefaultSize
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;
}
可以看到AT_MOST和EXACTLY返回的是相同的specSiz,这也是使用wrap_content和match_parent会出现相同的结果的原因。
如何解决问题
要解决这个问题,就要重写onMeasure方法:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec); //获取宽的模式
int heightMode = MeasureSpec.getMode(heightMeasureSpec); //获取高的模式
int widthSize = MeasureSpec.getSize(widthMeasureSpec); //获取宽的尺寸
int heightSize = MeasureSpec.getSize(heightMeasureSpec); //获取高的尺寸
Log.v(TAG, "宽的模式:"+widthMode);
Log.v(TAG, "高的模式:"+heightMode);
Log.v(TAG, "宽的尺寸:"+widthSize);
Log.v(TAG, "高的尺寸:"+heightSize);
int width;
int height ;
if (widthMode == MeasureSpec.EXACTLY) {
//如果match_parent或者具体的值,直接赋值
width = widthSize;
} else {
//如果是wrap_content,我们要得到控件需要多大的尺寸
float textWidth = mBound.width(); //文本的宽度
//控件的宽度就是文本的宽度加上两边的内边距。内边距就是padding值,在构造方法执行完就被赋值
width = (int) (getPaddingLeft() + textWidth + getPaddingRight());
Log.v(TAG, "文本的宽度:"+textWidth + "控件的宽度:"+width);
}
//高度跟宽度处理方式一样
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
float textHeight = mBound.height();
height = (int) (getPaddingTop() + textHeight + getPaddingBottom());
Log.v(TAG, "文本的高度:"+textHeight + "控件的高度:"+height);
}
//保存测量宽度和测量高度
setMeasuredDimension(width, height);
}
这样就能解决wrap_content铺满整个屏幕的问题了,但是这只是一个粗糙的方案,只是提供思路,使用这种解决方法并不能和TextView完全一致,当不设置padding时,View的大小就是字体的大小了: