是什么触发了 View 的 measure() 方法被调用?

55
在我的应用程序中,我在一个View的onMeasure()覆盖方法上有一个无限循环。从我的onMeasure中的断点开始调试源代码,我能够一直追踪自己的堆栈跟踪到PhoneWindow$DecorView的measure()(在我的View层次结构中最顶部的类),该类由ViewRoot.performTraversals()调用。现在从这里继续单步执行,最终会通过Looper.loop()类中的消息再次调用PhoneWindow$DecorView的measure()。我猜想有些内容已经排队等候进行重新测量,比如invalidate。
我的问题是,什么会触发对View进行测量调用?
根据我对布局/测量/绘制过程的理解,这只会在特定视图上调用invalidate()方法时才会发生,并且这将会向下渗透并为其子级执行布局/测量/绘制操作。我假设我的View层次结构中最上面的View被无效了。
然而,我已经在每个invalidate调用上都明确地设置了断点,并没有以某种无限的方式调用invalidate。所以我认为这不是问题所在。是否有其他方法来触发测量调用?可能某些内部内容正在触发它吗?在看到没有东西无限失效之后,我有点束手无策了。

3
什么意思?如果您在滚动视图中滚动,它会被无效化。您不需要调用invalidate() - Sherif elKhatib
在我的代码中,当我对一个视图执行invalidate调用时,有许多明确的点。我的问题是,在底层会发生什么来使其无效并强制进行测量,以及除了invalidate之外是否还有其他会强制进行测量的东西。需要知道的是,像在scrollView中滚动这样的操作将执行invalidate。 - C Nick
2个回答

91
为了触发自定义视图的量测传递,您必须调用requestLayout()方法。例如,如果您正在实现一个扩展了View并且将表现得像TextView的自定义视图,则可以编写如下的setText方法:

为了触发自定义视图的量测传递,您必须调用requestLayout()方法。例如,如果您正在实现一个扩展了View并且将表现得像TextView的自定义视图,则可以编写如下的setText方法:

/**
 * Sets the string value of the view.
 * @param text the string to write
 */
public void setText(String text) {
    this.text = text;

            //calculates the new text width
    textWidth = mTextPaint.measureText(text);

    //force re-calculating the layout dimension and the redraw of the view
    requestLayout();
    invalidate();
}

6
你真的需要同时使用requestLayout()invalidate()吗?我之前认为invalidate()已经包含了两者的功能。 - Matthew
10
调用 "invalidate" 只会导致视图重新绘制。调用 "requestLayout" 将导致视图重新布局和绘制。 - metter
只需要调用invalidate()方法重新创建与视图相关联的显示列表对象以重绘它。这会在根视图上调用invalidateChildInParent方法,该方法将代表您安排测量和布局步骤。测量和布局步骤都将从顶部到底部遍历您的视图层次结构,每个子ViewGroup都将负责调用其每个子项的measure和layout方法。总之,可能有点技术性,但那就是发生在后台的事情。此外,这也是文档建议您保持层次结构扁平化的原因-以减少额外的成本。 - stdout
2
这并没有回答问题。问题是什么触发器而不是如何触发。 - roide
1
@metter 的意思是上面调用 invalidate() 是多余的吗? - sergey.n
对我来说,仅仅使用invalidate()是不够的——但将其与requestLayout()结合使用就可以解决问题了!谢谢。 - Kirill Starostin

1

如果您正在更改视图的内容,它最终将调用invalidate()方法。例如,您有一个名为“Text 1”的文本视图。现在,您将同一文本视图的文本更改为“Text 2”。同样,这里也会调用invalidate()方法。

因此,基本上,当视图上的某些内容发生更改时,您通常会期望调用invalidate()方法,并相应地调用measure()方法。

例如,查看TextView的源代码。 http://www.google.com/codesearch#uX1GffpyOZk/core/java/android/widget/TextView.java&q=TextView%20package:android&type=cs

计算invalidate()方法的调用次数。有相当多的调用。


谢谢你的回复,Kumar。我没有看到我的代码不断改变View值。但是,你的回复确实给了我一个很好的调试路径。我在View.class的invalidate()方法上设置了一个断点,但不幸的是,当我处于这个无限循环中时,我没有得到任何中断。检查invalidate的工作方式,它似乎在ViewRoot上调用invalidateChild并标记一个矩形为脏,但我不认为我有权限在ViewRoot内部中断:( - C Nick
好的,如果不深入了解您在应用程序中具体执行的操作,很难指出问题所在。也许是父级元素发生了变化,试图重新绘制其子元素。 - Kumar Bibek
1
我可以看到在自定义视图中,invalidate() 不会导致 measure() 被调用。 - Pavel Strakhov

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