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

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


当前回答

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

EXACTLY means the layout_width or layout_height value was set to a specific value. You should probably make your view this size. This can also get triggered when match_parent is used, to set the size exactly to the parent view (this is layout dependent in the framework). AT_MOST typically means the layout_width or layout_height value was set to match_parent or wrap_content where a maximum size is needed (this is layout dependent in the framework), and the size of the parent dimension is the value. You should not be any larger than this size. UNSPECIFIED typically means the layout_width or layout_height value was set to wrap_content with no restrictions. You can be whatever size you would like. Some layouts also use this callback to figure out your desired size before determine what specs to actually pass you again in a second measure request.

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

其他回答

如果你不需要改变一些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的大小”-是错误的。

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

EXACTLY match_parent是EXACTLY +父节点的大小 AT_MOST wrap_content导致AT_MOST MeasureSpec 未指定从未触发

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

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

EXACTLY means the layout_width or layout_height value was set to a specific value. You should probably make your view this size. This can also get triggered when match_parent is used, to set the size exactly to the parent view (this is layout dependent in the framework). AT_MOST typically means the layout_width or layout_height value was set to match_parent or wrap_content where a maximum size is needed (this is layout dependent in the framework), and the size of the parent dimension is the value. You should not be any larger than this size. UNSPECIFIED typically means the layout_width or layout_height value was set to wrap_content with no restrictions. You can be whatever size you would like. Some layouts also use this callback to figure out your desired size before determine what specs to actually pass you again in a second measure request.

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