337

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

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

4

3 回答 3

773

onMeasure()您是否有机会告诉 Android 您希望自定义视图依赖于父级提供的布局约束有多大?这也是您的自定义视图有机会了解这些布局约束是什么(如果您想在某种match_parent情况下表现得不同wrap_content)。这些约束被打包到MeasureSpec传递给方法的值中。以下是众数值的粗略相关性:

  • EXACTLY表示layout_widthorlayout_height值已设置为特定值。你可能应该把你的视图设为这个大小。这也可以在match_parent使用时触发,以将大小精确设置为父视图(这取决于框架中的布局)。
  • AT_MOST通常意味着layout_widthorlayout_height值被设置为match_parentwrap_content需要最大尺寸的地方(这取决于框架中的布局),并且父维度的大小是该值。你不应该比这个尺寸大。
  • UNSPECIFIED通常意味着layout_widthorlayout_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);
}

希望有帮助。

于 2012-09-04T15:58:20.277 回答
5

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

  • EXACTLY match_parent 是 EXACTLY + 父级的大小
  • AT_MOST wrap_content 产生一个 AT_MOST MeasureSpec
  • 未指定从未触发

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

于 2013-10-30T07:28:42.903 回答
3

如果您不需要更改 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 的大小” - 是错误的。

于 2019-03-28T12:22:05.037 回答