onMeasure自定义视图解释

我试着做自定义组件。我扩展了View类,并在onDraw重写方法中做了一些绘图。为什么我需要覆盖onMeasure?如果我没有,一切看起来都是正确的。有人能解释一下吗?如何编写onMeasure方法?我看过一些教程,但每一个都有一点不同。有时它们在结尾调用super.onMeasure,有时它们使用setMeasuredDimension而不调用它。区别在哪里?

毕竟我想使用几个完全相同的组件。我将这些组件添加到我的XML文件中,但我不知道它们应该有多大。我想稍后在自定义组件类中设置它的位置和大小(为什么我需要在onMeasure中设置大小,如果我在onDraw中绘制它,也是如此)。我什么时候需要这么做?

148753 次浏览

onMeasure()是你告诉Android你希望你的自定义视图有多大依赖于父类提供的布局约束的机会;这也是你的自定义视图学习这些布局约束的机会(以防你想在match_parent情况下和wrap_content情况下表现不同)。这些约束被打包成传递给方法的MeasureSpec值。以下是模式值的粗略相关性:

  • 完全表示layout_widthlayout_height值被设置为特定值。您应该将视图设置为这个大小。当使用match_parent时,也可以触发这个,以精确地设置父视图的大小(这在框架中取决于布局)。
  • AT_MOST通常意味着layout_widthlayout_height值被设置为match_parentwrap_content,其中需要最大大小(这在框架中取决于布局),父维度的大小就是该值。你不能超过这个尺寸。
  • 未指明的通常意味着layout_widthlayout_height值被设置为wrap_content,没有任何限制。你想穿什么尺码就穿什么尺码。有些布局还使用这个回调函数来确定你想要的尺寸,然后再决定在第二个测量请求中传递给你什么规格。

onMeasure()存在的契约是,在结束时调用setMeasuredDimension() 必须,并使用您希望的视图大小。这个方法被所有框架实现调用,包括在View中找到的默认实现,这就是为什么如果适合你的用例,调用super是安全的。

当然,因为框架确实应用了默认实现,你可能没有必要重写这个方法,但如果你不这样做,你可能会在视图空间比你的内容小的情况下看到剪贴,如果你用wrap_content在两个方向上布局你的自定义视图,你的视图可能根本不显示,因为框架不知道它有多大!

通常,如果你覆盖的是View而不是另一个现有的小部件,提供一个实现可能是一个好主意,即使它像这样简单:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {


int desiredWidth = 100;
int desiredHeight = 100;


int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);


int width;
int height;


//Measure Width
if (widthMode == MeasureSpec.EXACTLY) {
//Must be this size
width = widthSize;
} else if (widthMode == MeasureSpec.AT_MOST) {
//Can't be bigger than...
width = Math.min(desiredWidth, widthSize);
} else {
//Be whatever you want
width = desiredWidth;
}


//Measure Height
if (heightMode == MeasureSpec.EXACTLY) {
//Must be this size
height = heightSize;
} else if (heightMode == MeasureSpec.AT_MOST) {
//Can't be bigger than...
height = Math.min(desiredHeight, heightSize);
} else {
//Be whatever you want
height = desiredHeight;
}


//MUST CALL THIS
setMeasuredDimension(width, height);
}

希望能有所帮助。

实际上,您的回答并不完整,因为这些值也取决于包装容器。在相对布局或线性布局的情况下,值的行为是这样的:

  • 完全 match_parent是EXACTLY +父对象的大小
  • AT_MOST wrap_content导致AT_MOST MeasureSpec
  • 未指明的从未触发

在水平滚动视图的情况下,您的代码将工作。

如果你不需要改变一些onMeasure -绝对没有必要让你重写它。

Devunwired代码(这里选择的和投票最多的答案)几乎与SDK实现为您所做的完全相同(我检查了-它从2009年开始就这样做了)。

你可以检查onMeasure方法在这里:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}


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;
}

重写SDK代码以替换完全相同的代码是没有意义的。

这个官方医生的观点声明“默认onMeasure()将始终设置100x100的大小”-是错误的。