What triggers a View's measure() to be called
Asked Answered
C

2

55

In my application I have an infinite loop on one of my View's onMeasure() overrides. Debugging the source code starting from a break point in my onMeasure, I am able to trace myself all the way up the stack trace up to the PhoneWindow$DecorView's measure() (top most class in my View Hierarchy), which gets called by ViewRoot.performTraversals(). Now from here if I keep stepping over, I eventually get the PhoneWindow$DecorView's measure() called again by a message in the Looper.loop() class. I'm guessing something has queued up a message that it needs to remeasure, like an invalidate.

My question is, what triggers that a measure call needs to occur on a View?

From my understanding of the layout/measure/draw process, this will only occur when the invalidate() method is called on a specific view, and that will trickle down and perform a layout/measure/draw pass for that view invalidated and all of its children. I would assume that somehow my top most View in my View Hierarchy is getting invalidated.

However, I've explicitly put a break point on every single invalidate call I have, and am not calling invalidate in some infinite manner. So I do not think that is the case. Is there another way to trigger a measure pass? Could something internally be triggering this? I'm kind of out of ideas after seeing that nothing is infinity invalidating.

Clyburn answered 4/8, 2011 at 18:14 Comment(2)
what do you mean? If you scroll in a scrollview .. it gets invalidated.. you dont have to call invalidate()Chophouse
In my code, there are a bunch of explicit points when I perform a invalidate call on a View. What I'm asking is what under the hood will invalidate and force a measure, as well as if anything else other than an invalidate which will force a measure. It is good to know that things like scrolling in a scrollView will perform an invalidate.Clyburn
P
91

In order to trigger a measure pass for a custom View you must call the requestLayout() method. For example, if you are implementing a custom view that extends View and it will behave like a TextView, you could write a setText method like this:

/**
 * 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();
}
Pianist answered 15/12, 2012 at 12:58 Comment(6)
do you really need both requestLayout() and invalidate() here? I was under the impression that invalidate() would do both.Danitadaniyal
Invalidate will just cause the view to be drawn again. requestLayout will cause it to be laid out and drawn.Gush
You only need to call invalidate() to re-create the Display List object associated with the view to redraw it. It's going to call invalidateChildInParent method on your root view which will schedule measure and layout steps on your behalf. Both measure and layout steps will traverse top-to-down your view hierarchy and each sub ViewGroup will be responsible for callng each of it's children's measure and layout methods. Anyways, it might have been a bit technical but that's what going under the hood. Plus, that's why docs advising you to keep your hierarchy flattened - to reduce extra cost.Greenburg
This does not answer the question. The questions is what triggers not how to trigger.Opiate
@Gush meaning that calling invalidate() above is redundant?Algar
For me a simple invalidate() was not enough -- but a combination of it with a requestLayout() did the trick! Thanks.Lownecked
G
1

Well, If you are changing a View's content, it will eventually have to call invalidate(). For example, you have a TextView with a text called "Text 1". Now, you change the text of the same TextView to "Text 2". Here aslo, invalidate will be called.

So basically, when something changes on the view, more often than not, you would expect the invalidate method to be called, and a corresponding call to the measure().

Look at the source code for TextView, for example. http://www.google.com/codesearch#uX1GffpyOZk/core/java/android/widget/TextView.java&q=TextView%20package:android&type=cs

Count the number of invalidate calls. There are quite a few.

Guayaquil answered 4/8, 2011 at 19:20 Comment(3)
Thanks for the reply Kumar. I don't see my code changing View values constantly. However, your reply did give me a good path to debug through. I had put a break point on the View.class 's inavlidate() method and unfortunately I do not get a break when I'm in this infinite loop. Checking how invalidate works, it looks like it calls invalidateChild on the ViewRoot and marks a Rect dirty, but I don't think I have access to break inside ViewRoot :(Clyburn
Well, Without diving more into what exactly you do in your application, it's a little difficult to point out. May be, something in the parent changes, which tries to redraw the children as well.Guayaquil
I can see that in custom view invalidate() doesn't cause measure() call.Luigi

© 2022 - 2024 — McMap. All rights reserved.