44

First thing! I do know about ViewTreeObserver.onGlobalLayoutListener.

What made me ask this question is the following notice on Android developer documentation website:

The snippet below does the following:

  • Gets the parent view and posts a Runnable on the UI thread. This ensures that the parent lays out its children before calling the getHitRect() method. The getHitRect() method gets the child's hit rectangle (touchable area) in the parent's coordinates.

Snippet itself is:

parentView.post(new Runnable() {
            // Post in the parent's message queue to make sure the parent
            // lays out its children before you call getHitRect()
            @Override
            public void run() {
               /// do UI stuff
            }
});

(you can look at the full article)

So is this a wrong statement or is it true? I am asking because posting a runnable seems easier and more convenient compared to doing all that register-listener/handle-event/unregister-listener dance with ViewTreeObserver :)

UPDATE: One more question to bring clarity to the whole subject: If all this is nice and Runnable can actually be posted instead of using a global layout listener, then why do we have this ViewTreeObserver.onGlobalLayoutListener mechanism at all? When is it better to use it rather than posting a Runnable and what the difference is between this methods?

dimsuz
  • 8,969
  • 8
  • 54
  • 88
  • 1
    use Runnable, when being run you are after the layout phase – pskink Feb 21 '14 at 14:37
  • Great question, and agree it's better than the observer. You could test this by posting this in onCreate(), changing a layout of a UI element in run(), then also changing the same layout after posting, also in onCreate(). If the runnable does indeed happen after layout, you should see the layout change in the runnable, not the one in onCreate()???? Makes a nice change from "how do I add two numbers together", "nullPointerException" and "why do I get a missing ; symbol syntax error". – Simon Feb 21 '14 at 14:39
  • it is the way I usually use it. If I need to get the size of a View, for instance, I have to wait until the whole hierarchy has been drawn. So I usually take the root layout and post a runnable. This way I mostly sure that when my runnable is executed, the layout is already at least measured. – Blackbelt Feb 21 '14 at 14:45
  • @Simon, yep, or just check the UI component's getWidth(), it should not be 0 :) – dimsuz Feb 21 '14 at 14:46
  • @pskink, blackbelt yep, seems to work most of the time, but I'm curious is there an 'official' statement about this somewhere :) – dimsuz Feb 21 '14 at 14:47

1 Answers1

47

I like the question too. It forced me to dig into Android source code once again. I believe this works because post() gets called after setContentView().

Method setContentView() ends up in calling ViewGroup.addView() of the top view, and addView() call always triggers requestLayout(). In turn, requestLayout() posts a task to the main thread to be executed later. This task will execute measure and layout on the view hierarchy. Now if you post another task it will be put into the queue after layout task and, as the result, always executed after measure and layout happen. Thus you will always have valid sizes.

sergej shafarenka
  • 20,071
  • 7
  • 67
  • 86
  • Wow, thanks for digging in! And for the sake of completeness, how do you think - what will happen in situation when I am adding a newly constructed View to some existing ViewGroup in runtime (supposedly after initial layout has already happened). If I do post(...) right after I added this new view, will it still hold true that view will be already laid out inside Runnable.run()? – dimsuz Feb 21 '14 at 20:19
  • I'm quite sure this will be the case. First layout will happen, and then your `Runnable` will be executed. – sergej shafarenka Feb 21 '14 at 20:22
  • Another sub-question: If all this is nice and true, then why do we have this ViewTreeObserver.onGlobalLayoutListener mechanism at all? When it is better to use it rather than posting a Runnable? I will update the question, it is interesting to have all this cleared up :) – dimsuz Feb 21 '14 at 20:22
  • 2
    I believe, if you add a view then you can replace one-time listener with a posted runnable. But If you need to be notified about every layout or you need another kind of events, you need this listener. – sergej shafarenka Feb 21 '14 at 20:28
  • no, I mean what if *after* first layout happened in response to some button click I'll do 'myLayout.add(new View(...)); myLayout.post(Runnable)' - will runnable have View laid out? – dimsuz Feb 21 '14 at 20:28
  • Ah! This makes sense! (about using global listener for being repeatedly notified about layouts) – dimsuz Feb 21 '14 at 20:30
  • That's what I mean. If you post a runnable immediately after you added a view, it should be notified after layout. – sergej shafarenka Feb 21 '14 at 20:30
  • I'm not sure if I made mistakes but I tried viewTrees and it didn't work. The code in the listener never executed so I'm trying this out.. other difference is the listener only fires if the view is visible. So this may be better for scenarios where the view is hidden until its long draw is done so it can replace a loading screen view – ItIsEntropy Nov 11 '19 at 18:25