Understanding when and how Android Views have dimensions set

android views

I believe that almost every Android dev has at least once tried to get a View’s dimension, but it returned 0, nada. This is a very common situation, it is one of those problems that we just Google for a solution and find a workaround that seems to work. We add it to the code without having much idea of what’s going on and that’s it. In this article I’ll explain why this happens and why many of the most used workarounds should be avoided. In the end, I’ll give you a very sweet solution about Android views.

Views dimension in Activity lifecycle 

One of the very basic Android concepts that we first learn is the Activity lifecycle. We understand that the onCreate() callback is where the UI should be declared and configured. Having this in mind, let’s say we have a View that for some reason needs a manual setup like this:

myView.layoutParams.width = myView.width / 2

This sets myView width based on its own width, this means that this code should run only once. You add it inside onCreate() right after setting content view, run the application but it doesn’t work as expected. myView had a width of zero and then your code just divided 0 by 2. So you think, maybe the view will be laid out in onStart(). You test it and again it doesn’t work. Maybe onResume() will work? Wait… what?! onResume doesn’t work either?! The next lifecycle step should be when the Activity is already running and you are still unable to finish the UI setup. What else can we do?

A meme of a blond woman thinking with some formular drawn on her picture.

Ok, let’s be radical and try onWindowsFocusChanged(). Voilà, it works! You are happy and can go back to work. Suddenly your app goes to background and a phone call pops up, it’s your mom asking you to buy some bread on your way home. After the call ends, your app comes back to the foreground, as well as your frustration. myView has now half the width that it should, and you realize that onWindowsFocusChanged() was called again after the app came back to the foreground. 

Gif of Jim Carrey drinking a glass of water and spitting it out.

We now know that for some reason there isn’t a lifecycle callback for when the activity UI is fully laid out, which is something that some stacks provide for us, such as on iOS. So the question is, when is a view fully laid out and drawn?

How Android draws the UI

An Activity layout is drawn upon the view hierarchy. From the image below, you can see that this hierarchy is a tree with two different nodes – ViewGroup and View.

View group's hierarchy picture.

ViewGroup is also a View, but it is a layout or container that can contain many children. Examples of ViewGroups are LinearLayout and ConstraintLayout. View is any other UI component. 

Each View has two different types of sizes: the measured width and height, which is basically how big it wants to be, and the width and height that are the actual dimensions they have after being laid out. The desired size cannot always be provided, e.g. a view could request a width larger than its parent’s width. 

Both measured and actual sizes are set in two steps: 

  • Measure pass: each View has a desired size – a measured dimension that is basically how big it wants to be. The measure pass is a process that starts from the root View Group and goes in a top-down transversal way setting the measured width and height of each View based on requirements passed from parent to children. Each View returns to its descendants the desired dimension. When this phase is over, every View has both measured width and height set.
  • Layout pass: also top-down transversal, each parent is responsible for positioning its children Views based on actual sizes, which are not always the measured sizes set on Measure pass – every View has a measured width and height.

After the layout pass finishes, the layout can finally be drawn by the OS. 

This is a very basic explanation, but it is enough to have an idea of how the draw process works. But when does it happen? The documentation states that it starts when the Activity receives focus, then let’s see it happening with a small code that I wrote. 

The code has a custom LinearLayout and TextView which I created just to override onMeasure(), onLayout()/layout() and draw() functions and add some logs to them. You can check it out in this Gist

Running it results in these logs:

1 onCreate() executed

2 onStart() executed

3 onResume() executed

4 LinearLayout: entering onMeasure(). Measured width: 0

5 TextView: entering onMeasure(). Measured width: 0

6 TextView: leaving onMeasure(). Measured width: 171

7 LinearLayout: leaving onMeasure(). Measured width: 171

8 LinearLayout: entering onLayout(). Actual width: 171

9 TextView: entering layout(). Actual width: 0

10 TextView: leaving layout(). Actual width: 171

11 LinearLayout: leaving onLayout(). Actual width: 171

12 onWindowFocusChanged() executed

13 LinearLayout: entering onMeasure(). Measured width: 171

14 TextView: entering onMeasure(). Measured width: 171

15 TextView: leaving onMeasure(). Measured width: 171

16 LinearLayout: leaving onMeasure(). Measured width: 171

17 LinearLayout: entering onLayout(). Actual width: 171

18 TextView: entering layout(). Actual width: 171

19 TextView: leaving layout(). Actual width: 171

20 LinearLayout: leaving onLayout(). Actual width: 171

21 TextView: draw() executed

We can perfectly see measure and layout passes happening from lines 1 to 11, with measured and actual sizes of both views going from 0 to 171. We can also notice that the two passes happen twice, once before onWindowFocusChanged()and again from lines 13 to 21, but TextView calls draw() only when the Activity gets focus. This means that the layout is indeed drawn only after the Activity receives focus, but the views are laid out before it.

Understanding this whole process is very helpful, but it’s still hard to know how to have a callback for when the entire screen is laid out.

Most common solutions

After doing a little research, you will probably find some solutions that people claim they work, which usually are:

  • View.post(Runnable action): this can be used to send your UI setup code to the message queue. Adding this after calling setContentView() inside onCreate() usually makes your code run after the UI is laid out. But you don’t have much control of message queue, which means this approach is much more a workaround than an elegant solution that we know will always work.
  • ViewTreeObserver.OnGlobalLayoutListener: with this listener, you can set up a code that will be executed when global layout changes happen in the whole view tree. There’s a nice article with its usage explained by Antonio Leiva. But in the article there’s also a tweet from Chris Banes where he explains that this approach is overkill because it is triggered when layout changes happened to any view in the tree, and even with some improvements, such as removing the listener after the first call, it’s not guaranteed that when the listener is called your view has already been laid out, i.e. it might not have width and height set yet.
  • View.OnLayoutChangeListener“Interface definition for a callback to be invoked when the layout bounds of a view changes due to layout processing.”This looks promising. It will be triggered only when a specific view is changed instead of the whole view tree. But what if at the time that this listener is added, this view is already laid out and then it never runs your code? In this case, we could make it run the code straight away. We could even write a Kotlin extension for this, right? Nah, no need, it’s already done and it’s in the Android Jetpack.

Android KTX and doOnLayout to the rescue

Android KTX is a library that is part of Android Jetpack and has a lot of useful Kotlin extensions. For our case, it has the View.doOnLayout() extension with a callback for when a view is laid out, and it also ensures that the callback runs even if the View has already been laid out and hasn’t requested layout. If you are wondering what’s the magic behind it, there isn’t. It uses View.OnLayoutChangeListener without any crazy logic, as you can see in the source code.

A gif of Steve Carell slapping a table and saying 'thank you'.

A lifecycle callback for when the entire layout is laid out

With doOnLayout() we have a callback for when a specific view is laid out, but what if you want a callback that is called when the entire layout is laid out? Well, we know that the root View is the last one to be laid out, so this means that by using doOnLayout() with the root View, it will be called when all other Views have already been laid out. There’s just a small problem: how to get the root View? 

To achieve this goal in a generic way, we could get the content view of top-level window decor using a base Activity class:

abstract class BaseActivity: AppCompatActivity() {

    protected open fun doOnRootViewLayout(action: () -> Unit) {

        val rootView = window.decorView.findViewById<View>(android.R.id.content)

        rootView.doOnLayout {

            action()

        }

    }

}

Unfortunately I couldn’t find much information about this approach and how safe it is, except an article explaining that this low-level layer can change in other Android versions or devices. Also on Android documentation there isn’t much information about android.R.id.content

My suggestion is to give an ID to the root view of your layout and use this view to call doOnLayout(), which also works with Fragments and custom views.

Wrapping up

We were able to understand when Views have dimensions set and are drawn. For some reason there is not a lifecycle callback for that, but at least we came up with a working solution using doOnLayout.

Working with Android is a mix of love and hate, we all know that. Sometimes very basic things are difficult to achieve or understand (and it is even harder to understand why they are not explained in the documentation). I hope this article helps you to diminish the hate moments caused by some Android flaws (although it might have added more questions than answers) and write a better code and UI without fear of all that obscurity that Android sometimes leaves to us.

References

https://developer.android.com/guide/components/activities/activity-lifecycle

https://developer.android.com/reference/android/app/Activity#onWindowFocusChanged(boolean)

https://developer.android.com/guide/topics/ui/how-android-draws

https://developer.android.com/reference/android/view/View.html#post(java.lang.Runnable)

https://developer.android.com/reference/android/view/ViewTreeObserver.OnGlobalLayoutListener

https://antonioleiva.com/kotlin-ongloballayoutlistener/

https://developer.android.com/reference/android/view/View.OnLayoutChangeListener

https://developer.android.com/jetpack/

https://developer.android.com/kotlin/ktx

https://developer.android.com/reference/kotlin/androidx/core/view/package-summary#doonlayout

https://android.googlesource.com/platform/frameworks/support/+/android-room-release/core/ktx/src/main/java/androidx/core/view/View.kt

https://android-developers.googleblog.com/2009/03/window-backgrounds-ui-speed.html

https://developer.android.com/reference/android/R.id#content

About the author.

Diego Oliveira
Diego Oliveira

A computer scientist who always dig deep to find the best solutions, and is in love with mobile development. Also likes playing video games and basketball.