onMeasure自定义视图说明

时间:2012-09-04 15:38:45

标签: android view

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

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

3 个答案:

答案 0 :(得分:698)

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

希望能帮助。

答案 1 :(得分:4)

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

  • 确实 match_parent正是父母的大小
  • AT_MOST wrap_content导致AT_MOST MeasureSpec
  • 已发布从未触发

如果是水平滚动视图,您的代码将起作用。

答案 2 :(得分:0)

如果您不需要在onMeasure上进行更改-绝对不需要您覆盖它。

断开连接的代码(此处选择和投票最多的答案)与SDK实现已为您完成的工作几乎完全相同(我检查过-自2009年以来就这样做了)。

您可以检查onMeasure方法here

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代码替换为完全相同的代码没有任何意义。

official doc's piece声称“默认的onMeasure()始终将大小设置为100x100”-是错误的。