onMeasure(): wrap_content,我该如何知道要包裹的大小?

19
我已经创建了一个自定义的视图,覆盖了 onDraw() 方法,在画布上绘制了一张位图。当我在布局文件中指定要使用包裹内容(wrap_content)的方式时,它仍然填满整个屏幕。在 onMeasure() 中,它显示了这个信息:

测量的基类实现默认为背景大小,除非 MeasureSpec 允许更大的尺寸。子类应该重写 onMeasure(int, int) 方法提供更好的内容测量。

好的,我知道我需要重写 onMeasure() 并使用 MeasureSpec。根据这个答案

UNSPECIFIED 表示将 layout_width 或 layout_height 的值设置为 wrap_content。你可以选择任何大小。

现在我遇到了问题,如何在 onMeasure() 中测量我的还没有创建的位图并测量/包裹它?我知道其他 Android 视图必须做些什么,因为它们如果设置为 wrap_content 就不会阻挡整个屏幕。提前致谢!
2个回答

38

这些常用的视图方法运行的顺序如下:

1. Constructor    // choose your desired size 
2. onMeasure      // parent will determine if your desired size is acceptable
3. onSizeChanged
4. onLayout       
5. onDraw         // draw your view content at the size specified by the parent

选择所需的尺寸

如果您的视图可以任意选择大小,那么它会选择什么大小呢?这将成为您的wrap_content大小,并且将取决于自定义视图的内容。例如:

  • 如果您的自定义视图是一张图片,则所需大小可能是位图的像素尺寸加上任何填充。(在选择大小并绘制内容时,您需要考虑填充的计算责任。)
  • 如果您的自定义视图是模拟时钟,则所需大小可以是某个默认大小,看起来很好。(您始终可以获取设备的dppx大小。)

如果您所需的尺寸使用了复杂的计算,请在构造函数中完成。否则,您可以在onMeasure中直接分配它。(由于onMeasureonLayoutonDraw可能会被多次调用,因此在这里进行繁重的工作不是一个好主意。)

协商最终大小

onMeasure 方法是子视图告诉父视图它希望有多大的空间,然后父视图决定是否接受。这个方法通常被调用几次,每次传入不同的大小要求,看是否可以达成妥协。然而,最终,子视图需要尊重父视图的大小要求。

当我需要刷新如何设置我的 onMeasure 时,我总是会参考 this answer

@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之后,已知视图的大小。这个大小可能是您请求的,也可能不是,但现在您必须使用它来在onDraw中绘制视图上的内容。
注释
  • 任何时候,如果您对视图进行更改会影响外观但不影响大小,则调用invalidate()。这将导致再次调用onDraw(但不是所有先前的方法)。
  • 如果您更改了视图的大小,则调用requestLayout()。这将从onMeasure开始重新测量和绘制。通常与调用invalidate()结合使用。
  • 如果由于某种原因您确实无法事先确定适当的期望大小,则可以像@nmw建议的那样请求零宽度,零高度。然后在加载完所有内容后请求布局(不仅仅是invalidate())。不过这似乎有点浪费,因为您要求整个视图层次两次连续布局。

4

如果在onMeasure调用之前无法测量位图,则可以返回零大小,直到位图加载完毕。一旦加载完成,使父ViewGroup失效以强制进行另一次测量(无法记住是否在View本身上调用invalidate()会强制执行onMeasure)。


经过一番搜索,我发现你说的是对的。在视图创建后,我给它提供了位图并调用了requestLayout(); 这将调用我的onMeasure并使视图变为正确的大小。谢谢! - Simon Zettervall
1
@nmw 我也遇到了同样的问题,但是从上面的回答中没有得到任何思路。你能否在评论区或者编辑你之前的回答时添加更多的解释呢?这会对我很有帮助。 - Shirish Herwade

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接