前段时间做项目绘制一个View,想要控件包裹内容后控件的宽高是绘制区域的大小,但是绘制好之后,放入到不居中,使用wrap_content的宽高,控件默认显示的大小是match_parent。
先分析一下为什么会出现问题
- 首先看一下onMeasure在源码中是如何实现的:
/**
* 首先调用setMeasuredDimension来存储测量宽度和测量高度
* 在调用getDefaultSize方法获取对应的宽高,所以关键分析一下* getDefaultSize是如何实现的
*/
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//存储测量宽度和测量高度
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
/**
* @param size 提供的默认大小
* @param measureSpec 测量规格(包含测量模式和测量大小)
* 32位的int值,高2位位测量模式,地30位为测量大小
* ScrollerView雨ListView或者GridView冲突的时候
* 只需要在onMeasure的super()方法之前指定大小
* Integer.MAX_VALUE >> 2 (即int值左移动2位得到最大的测量值,指定模式为AT_MOST)
* int height = View.MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
* View.MeasureSpec.AT_MOST);
* @return 测量后的值
*/
public static int getDefaultSize(int size, int measureSpec) {
//传递过来的默认大小值
int result = size;
//获取测量模式
int specMode = View.MeasureSpec.getMode(measureSpec);
//获取测量大小
int specSize = View.MeasureSpec.getSize(measureSpec);
switch (specMode) {
//使用默认大小
case View.MeasureSpec.UNSPECIFIED:
result = size;
break;
//使用测量后的大小
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.EXACTLY:
result = specSize;
break;
}
//返回View的宽或高
return result;
}
分析源码后发现AT_MOST 和 EXACTLY模式的时候都使用测量后的大小,而AT_MOST对应的是wrap_content的宽高,EXACTLY对应的是match_parent的宽高,所以默认情况下AT_MOST和EXACTLY使用的都是match_parent的宽高,所以会出现自定义View使用wrap_content的时候布局会撑满全屏。
分析了问题后就要进行对症下药
首先考虑平时使用的TextView,ImageView等控件的wrap_content属性都是有效的,源码中肯定对onMeasure做了处理。看先下TextView中对onMeasure是如何处理的。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//获取宽的测量模式
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
//获取的高测量模式
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
//获取的测量额宽
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
//获取的测量额高
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//指定宽高值
int width;
int height;
BoringLayout.Metrics boring = UNKNOWN_BORING;
BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
// 判断文字绘制的方向
if (mTextDir == null) {
mTextDir = getTextDirectionHeuristic();
}
int des = -1;
boolean fromexisting = false;
if (widthMode == MeasureSpec.EXACTLY) {
//使用默认的测量值
width = widthSize;
} else {
//解决AT_MOST的时候wrap_content失效,对内容区域进行宽度测量给width赋值
if (mLayout != null && mEllipsize == null) {
des = desired(mLayout);
}
//...此处省略代码
if (boring == null || boring == UNKNOWN_BORING) {
if (des < 0) {
//更具内容获取到内容显示需要的宽度
des = (int) Math.ceil(Layout.getDesiredWidth(mTransformed, 0,
mTransformed.length(), mTextPaint, mTextDir));
}
//更具内容区域进行width赋值
width = des;
} else {
//...此处省略代码 与上类同进行更具内容进行测量需要的宽度对width进行赋值
}
//...以上省略代码
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
mDesiredHeightAtMeasure = -1;
} else {
//获取到内容显示区域需要的高度,对height进行赋值
int desired = getDesiredHeight();
height = desired;
mDesiredHeightAtMeasure = desired;
if (heightMode == MeasureSpec.AT_MOST) {
height = Math.min(desired, heightSize);
}
}
}
//...以上省略代码
// 设置测量好的宽高
setMeasuredDimension(width, height);
}
简单查看TextView的源码发现,在onMeasure方法中,单独对EXACTLY测量模式以外的的模式进行内容区域所占宽高进行测量,然后调用setMeasuredDimension对宽高重新赋值。
解决方案
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//获取显示内容宽高
int desiredWidth = this.desiredWidth;
int desiredHeight = this.desiredHeight;
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height;
//根据测量模式重新计算宽
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else if (widthMode == MeasureSpec.AT_MOST) {
width = Math.min(desiredWidth, widthSize);
} else {
width = desiredWidth;
}
//根据测量模式重新计算高
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else if (heightMode == MeasureSpec.AT_MOST) {
height = Math.min(desiredHeight, heightSize);
} else {
height = desiredHeight;
}
//对宽高重新赋值
setMeasuredDimension(width, height);
}
网友评论