onMeasure自定义视图说明


316

我试图做自定义组件。我扩展了View类,并在onDraw重写的方法中进行了一些绘制。为什么我需要覆盖onMeasure?如果我不这样做,那么一切似乎都是对的。有人可以解释吗?我应该如何编写我的onMeasure方法?我看过一些教程,但是每个教程都有些不同。有时他们super.onMeasure在结尾处打电话,有时他们使用setMeasuredDimension但没有打电话。有何区别?

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

Answers:


735

onMeasure()是您告诉Android您希望自定义视图多大取决于父级提供的布局约束的机会;这也是您自定义视图的机会,以了解这些布局约束是什么(以防您希望在某种match_parent情况下与其他wrap_content情况有所不同)。这些约束打包成MeasureSpec传递给方法的值。这是模式值的粗略相关:

  • 完全表示将layout_widthlayout_height值设置为特定值。您可能应该使视图大小如此。match_parent使用时也会触发此事件,以将尺寸精确设置为父视图(这在框架中取决于布局)。
  • AT_MOST通常意味着将layout_widthor layout_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
嘿@Devunwired到目前为止我读过的最好的解释很好。您的解释回答了我曾提出的许多问题,并消除了一些疑问,但仍然存在一个问题:如果我的自定义视图与其他视图一起放在ViewGroup中,那么ViewGroup将会使他所有的孩子为每个探针确定其LayoutParams约束,并要求每个孩子根据其约束自己进行测量?
法老王2012年

47
请注意,如果您重写任何ViewGroup子类的onMeasure,则此代码将无效。您的子视图将不会显示,并且大小均为0x0。如果需要覆盖自定义ViewGroup的onMeasure,请更改widthMode,widthSize,heightMode和heightSize,然后使用MeasureSpec.makeMeasureSpec将它们编译回measureSpecs并将结果整数传递给super.onMeasure。
阿列克谢

1
很棒的答案。请注意,根据Google的文档,View负责处理填充。
jonstaff 2014年

4
由于复杂的c ** p使得Android成为痛苦的布局系统。他们本来可以拥有getParent()。get ***()...
Oliver Dixon

2
View类中有称为resolveSizeAndState和的辅助方法resolveSize,它们应该执行'if'子句的作用-我发现它们很有用,尤其是当您必须经常编写这些IF时。
stan0

5

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

  • 完全匹配match_parent是+父级的大小
  • AT_MOST wrap_content生成AT_MOST MeasureSpec
  • UNSPECIFIED从未触发

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


57
如果您觉得此处的答案不完整,请添加答案,而不要给出部分答案。
迈克尔

1
好的onya可以将其链接到布局的工作方式,但是在我的情况下,onMeasure被自定义视图调用了3次。所讨论的视图具有wrap_content高度和加权宽度(宽度= 0,重量= 1)。第一个电话已通过/未通过,第二个电话已为AT_MOST /完全匹配,第三个电话已完全/完全匹配。
William T. Mallard

0

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.