这些常用的视图方法运行的顺序如下:
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
大小,并且将取决于自定义视图的内容。例如:
- 如果您的自定义视图是一张图片,则所需大小可能是位图的像素尺寸加上任何填充。(在选择大小并绘制内容时,您需要考虑填充的计算责任。)
- 如果您的自定义视图是模拟时钟,则所需大小可以是某个默认大小,看起来很好。(您始终可以获取设备的
dp
到px
大小。)
如果您所需的尺寸使用了复杂的计算,请在构造函数中完成。否则,您可以在onMeasure
中直接分配它。(由于onMeasure
、onLayout
和onDraw
可能会被多次调用,因此在这里进行繁重的工作不是一个好主意。)
协商最终大小
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;
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else if (widthMode == MeasureSpec.AT_MOST) {
width = Math.min(desiredWidth, widthSize);
} else {
width = desiredWidth;
}
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else if (heightMode == MeasureSpec.AT_MOST) {
height = Math.min(desiredHeight, heightSize);
} else {
height = desiredHeight;
}
setMeasuredDimension(width, height);
}
在上面的示例中,所需的宽度和高度仅设置为一些默认值。相反,您可以预先计算它们并使用类成员变量在此处设置它们。
使用所选大小
在onMeasure之后,已知视图的大小。这个大小可能是您请求的,也可能不是,但现在您必须使用它来在onDraw中绘制视图上的内容。
注释
- 任何时候,如果您对视图进行更改会影响外观但不影响大小,则调用
invalidate()
。这将导致再次调用onDraw
(但不是所有先前的方法)。
- 如果您更改了视图的大小,则调用
requestLayout()
。这将从onMeasure
开始重新测量和绘制。通常与调用invalidate()
结合使用。
- 如果由于某种原因您确实无法事先确定适当的期望大小,则可以像@nmw建议的那样请求零宽度,零高度。然后在加载完所有内容后请求布局(不仅仅是
invalidate()
)。不过这似乎有点浪费,因为您要求整个视图层次两次连续布局。