什么时候会调用View.onMeasure()方法?

31

什么时候是

View.onMeasure(int widthMeasureSpec, int heightMeasureSpec)

你叫什么?我有一个需要在onMeasure被调用后执行操作的Activity

我的问题与这里发布的未回答的问题相同。View文档说明当requestLayout()被调用时会调用onMeasure,显然是由视图自身调用的,当它认为它无法适应其当前范围时。

然而,这并不能告诉我何时我的活动可以假定我的View已被测量。我使用这段代码扩展ImageView来形成TouchImageView。在这里建议我应该使用onMeasure方法来缩放我的图像。我希望在ImageView被测量后更新TextView的值,以显示图像被缩放的百分比。

1
因为我只有10个积分,所以即使它们指向像stackoverflow.com和developer.android.com这样的域,我也不能添加超过两个链接。谢谢您修复我的链接,但您不必要地删除了我的引用。由于我的限制,我无法在不删除链接的情况下进行修复。 - GregNash
抱歉,我不知道我删除了哪个引号。如果您告诉我,我可以把它放回去... - Matthieu
“当视图认为自己无法适应当前的边界时,它会被自身的视图调用。”这句话是从View文档中直接引用的,但其中有一个拼写错误。 - GregNash
2个回答

39

您可以在onSizeChanged方法中获取自定义视图的尺寸。

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);

    // use the provided width and height for whatever you need
}

解释

创建视图时,以下方法的调用顺序如下:

  1. 构造函数
    • CustomView(Context context)(如果是以编程方式创建的)
    • CustomView(Context context, AttributeSet attrs)(如果从xml创建)
  2. onFinishInflate(假设您使用了xml构造函数)
  3. onAttachedToWindow
  4. onMeasure
  5. onSizeChanged
  6. onLayout
  7. onDraw

您可以在onMeasure中获得该视图的测量尺寸,此之前宽度和高度都为0。然而,在onMeasure中应该做的唯一事情就是确定视图的大小。在视图告诉父级其想要有多大之后,该方法会被多次调用,但是父级会确定实际的最终大小。(有关onMeasure的正确用法,请参见此答案。)

如果您想要真正使用测量到的大小,则最早可以在onSizeChanged中这样做。由于大小从0更改为实际大小,因此每次创建视图时都会调用它。

您还可以使用onLayout,不过据我所知,onLayout是用于自定义您的自定义视图的任何子项应如何布局。它也可能比onSizeChanged更频繁地调用,例如如果在大小实际上没有更改的情况下调用requestLayout()

您还可以在onDraw中使用getMeasuredWidth()getMeasuredHeight()访问大小。但是,如果要使用它们进行任何重型计算,则最好事先执行该操作。一般来说,请尽量减少onDraw中的操作,因为它可能被调用多次。(每次调用invalidate()时都会调用它。)

亲自查看

如果您不相信我,可以查看以下自定义视图中调用事件的顺序。以下是输出:

XML constructor called, measured size: (0, 0)
onFinishInflate called, measured size: (0, 0)
onAttachedToWindow called, measured size: (0, 0)
onMeasure called, measured size: (350, 1859)
onMeasure called, measured size: (350, 350)
onMeasure called, measured size: (350, 2112)
onMeasure called, measured size: (350, 350)
onSizeChanged called, measured size: (350, 350)
onLayout called, measured size: (350, 350)
onDraw called, measured size: (350, 350)

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.example.viewlifecycle.CustomView
        android:id="@+id/customView"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:background="@color/colorAccent"/>

</RelativeLayout>

CustomView.java

public class CustomView extends View {

    private void printLogInfo(String methodName) {
        Log.i("TAG", methodName + " called, measured size: (" + getMeasuredWidth() + ", " + getMeasuredHeight() + ")");
    }

    // constructors

    public CustomView(Context context) {
        super(context);
        printLogInfo("Programmatic constructor");
    }

    public CustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
        printLogInfo("XML constructor");
    }

    // lifecycle methods

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        printLogInfo("onFinishInflate");
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        printLogInfo("onAttachedToWindow");
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        printLogInfo("onMeasure");
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        printLogInfo("onSizeChanged");
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        printLogInfo("onLayout");
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        printLogInfo("onDraw");
    }
}

更多阅读


你所说的“Constructor”是指“onCreate”吗? - dankal444
1
@dankal444,不,自定义视图没有onCreate方法。构造函数就是您用于创建新视图对象的方法。在我上面的CustomView.java示例中,有两个构造函数方法:CustomView(Context context)CustomView(Context context, AttributeSet attrs) - Suragch
我被难住了。如果 onLayout() 是子视图布局的地方,那么一个高度由子视图布局决定的 ViewGroup 在 onMeasure() 被调用之后才会有该高度。似乎子视图的布局(如果它们影响 ViewGroup 的高度)必须在 onMeasure 被调用之前完成(或在 onMeasure() 函数本身中完成)。由于这种布局-改变-高度-在_measure-完成之后的问题,这些调用的顺序似乎是不合理的。 - David Rector

20

当父视图需要计算布局时,会调用onMeasure。通常情况下,根据不同的子元素和它们的布局参数,可能会多次调用onMeasure。

onMeasure被调用时执行某些操作的最佳方法是(我认为)创建自己的控件,扩展ImageView并覆盖onMeasure(只需调用super.onMeasure并执行其他您想要执行的操作即可)。

如果这样做,请记住,onMeasure可能会使用不同的参数多次调用,因此测量的任何内容可能不是实际显示的最终宽度和高度。


1
谢谢您回答我的问题,但我认为您没有理解我的重点。正如我在问题中所说,我确实扩展了ImageView并覆盖了onMeasure方法。然而,在onMeasure被调用后,有一个属于我的活动(一个TextView)的对象需要被更新。 - GregNash
1
在扩展ImageView的类中添加一个函数setAssociatedTextView(TextView tv),并在onMeasure函数中使用它,不确定问题出在哪里...?但是,由于onMeasure很可能会被调用多次,所以你无法真正确定视图何时完全测量。 - Matthieu
这解决了我的问题,但感觉有些不规范。我肯定违反了某种依赖关系设计原则最佳实践。我不得不将代码从我的Activity复制到我的TouchImageView(ImageView扩展),现在Activity依赖于视图,而其中一个视图依赖于另一个视图。虽然它能工作,但还是感谢您提供的解决方案。 - GregNash

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