【问题标题】:onMeasure custom view explanationonMeasure 自定义视图说明
【发布时间】:2012-08-29 07:33:13
【问题描述】:

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

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

【问题讨论】:

    标签: android view


    【解决方案1】:

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

    希望有所帮助。

    【讨论】:

    • 嘿@Devunwired 很好的解释是我迄今为止读到的最好的。您的解释回答了我的很多问题并消除了一些疑问,但仍然存在一个问题:如果我的自定义视图与其他一些视图一起位于 ViewGroup 中(无论哪种类型),那么 ViewGroup 将为他的所有孩子提供为每一个探测他们的 LayoutParams 约束并要求每个孩子根据他们的约束自行测量它?
    • 是的,这就是ViewGroupmeasureChildren()方法在测量/布局过程中所做的事情。
    • 请注意,如果您覆盖任何 ViewGroup 子类的 onMeasure,此代码将不起作用。您的子视图将不会显示,并且都将具有 0x0 的大小。如果您需要覆盖自定义 ViewGroup 的 onMeasure,更改 widthMode、widthSize、heightMode 和 heightSize,使用 MeasureSpec.makeMeasureSpec 将它们编译回 measureSpecs 并将结果整数传递给 super.onMeasure。
    • 很棒的答案。请注意,根据 Google 的文档,处理填充是 View 的责任。
    • View 类中有一些辅助方法,称为 resolveSizeAndStateresolveSize,它们应该可以做 'if' 子句的作用——我发现它们很有用,特别是如果你必须编写那些 IF 经常出现。
    【解决方案2】:

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

    • 精确 match_parent 是精确 + 父级的大小
    • AT_MOST wrap_content 生成 AT_MOST MeasureSpec
    • 未指定从未触发

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

    【讨论】:

    • 如果您觉得这里的某些答案不完整,请补充而不是部分回答。
    • 很好地将它与布局的工作方式联系起来,但在我的情况下,我的自定义视图调用了 3 次 onMeasure。有问题的视图有一个 wrap_content 高度和一个加权宽度(宽度 = 0,重量 = 1)。第一个调用是 UNSPECIFIED / UNSPECIFIED,第二个调用 AT_MOST / EXACTLY,第三个调用 EXACTLY / EXACTLY。
    【解决方案3】:

    如果您不需要更改 onMeasure 的内容 - 绝对没有必要覆盖它。

    Devunwired 代码(此处选择和投票最多的答案)与 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 的大小” - 是错误的。

    【讨论】:

      猜你喜欢
      • 2023-03-15
      • 2015-02-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-04
      • 1970-01-01
      • 2014-05-17
      • 1970-01-01
      相关资源
      最近更新 更多