自定义View设置控件大小或者处理尺寸时,使用如下代码的话,在父布局为RelativeLayout时,会出现控件尺寸显示不对的问题。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
width = MeasureSpec.getSize(widthMeasureSpec);
height = MeasureSpec.getSize(heightMeasureSpec);
int widthType = MeasureSpec.getMode(widthMeasureSpec);
int heightType = MeasureSpec.getMode(heightMeasureSpec);
int defaultWidth = 100;
int defaultHeight = 100;
width = widthType == MeasureSpec.AT_MOST ? defaultWidth : width;
height = heightType == MeasureSpec.AT_MOST ? defaultHeight : height;
width = Math.min(width, height);
height = Math.min(width, height);
setMeasuredDimension(width, height);
}
虽然我知道在自定义View测量过程中,onMeasure被调用了两次。但是没有具体研究过...
搞了半天才发现,出现问题的原因和父布局有关:
- 当View的父布局为RelativeLayout时
- onMeasure第一次调用时测量了widthMeasureSpec,此时heightMeasureSpec是默认的(mode是wrap_content类型)。
- 第二次调用时测量了heightMeasureSpec,但此时的widthMeasureSpec已经被“污染”了(在上述代码中)。
导致最后无论怎么设置控件大小,始终为defaultValue。
当View的父布局为LinearLayout时,onMeasure在第一次调用时就同时测量好了widthMeasureSpec和heightMeasureSpec。
继承自LinearLayout的自定义布局,也是测量两次!!!
所以自定义View测量时需要注意。(当然父布局为LinearLayout可以随便处理啦~)
也可以统一处理为:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
int defaultWidth = 100;
int defaultHeight = 100;
if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(defaultWidth, defaultHeight);
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(defaultWidth, heightSpecSize);
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSpecSize, defaultHeight);
} else {
widthSpecSize = Math.min(widthSpecSize, heightSpecSize);
heightSpecSize = Math.min(widthSpecSize, heightSpecSize);
setMeasuredDimension(widthSpecSize, heightSpecSize);
}
}
这样,就可以同时考虑两种情况,以前开发过程中不仔细,没有出现问题,在此记录一下~~也为其它遇到这种问题的小伙伴们提供些参考。
!!!!!!!!!!! resolveSize !!!!!!!!!!
Android已存在处理上诉问题的方法,即resolveSize(int size, int measureSpec)。
第一个参数可以理解为设置的大小尺寸(固定值,或者getSuggestedMinimumWidth()),第二个参数是xml中的模式和由此模式系统默认的大小。
源码如下:
// sdk 27
public static int resolveSize(int size, int measureSpec) {
return resolveSizeAndState(size, measureSpec, 0) & MEASURED_SIZE_MASK;
}
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
final int specMode = MeasureSpec.getMode(measureSpec);
final int specSize = MeasureSpec.getSize(measureSpec);
final int result;
switch (specMode) {
case MeasureSpec.AT_MOST:
//这块处理与之前sdk的处理不一样了
if (specSize < size) {
result = specSize | MEASURED_STATE_TOO_SMALL;
} else {
result = size;
}
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
case MeasureSpec.UNSPECIFIED:
default:
result = size;
}
return result | (childMeasuredState & MEASURED_STATE_MASK);
}
故可以写为:
setMeasuredDimension(resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec),
resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec));
- 注意:resolveSize和getDefaultSize有区别的,getDefaultSize将AT_MOST、EXACTLY一起处理。
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;
}