on LinearLayout measures

seeing that LinearLayout is one of the most often used ViewGroup types in Android development, i wanted to write a little bit about how LinearLayout measures its children (this was also inspired by Sriram’s post about Custom ViewGroups).

tldr; (key takeaways that this post will discuss):

  1. a (non-nested) LinearLayout will measure each non-hidden child either 1x, 2x, or 3x (gone views aren’t measured).
  2. whenever possible, set android:baselineAligned to false, especially when using weights.
  3. in a horizontal LinearLayout with wrap_content height, avoid setting any of the children’s heights to match_parent. conversely, in a vertical LinearLayout with wrap_content width, avoid setting any of the children’s widths to match_parent.

LinearLayout and child measurement

a (non-nested) LinearLayout will end up measuring each non-hidden view either 1x, 2x, or 3x. here are the rules:

  1. a View with android:visibility="gone" won’t be measured.
  2. a View will default to being measured once, unless more measurements are necessary (see rules 3 and 4).
  3. when the non-android:orientation direction of the LinearLayout is wrap_content and its child’s is match_parent, the child will be measured an extra time.
    • in a horizontal LinearLayout with wrap_content height, a child with match_parent height will be measured an extra time.
    • in a vertical LinearLayout with wrap_content width, a child with match_parent width will be measured an extra time.
  4. a child with a non-zero android:layout_weight will be measured an extra time if:
    • in a horizontal LinearLayout with non-wrap_content width, android:layout_width is not 0dp, (or is 0dp, but the LinearLayout doesn’t have android:baselineAligned="false" set).
    • in a vertical LinearLayout with non-wrap_content height, if android:layout_height is not 0dp.

the first two rules are fairly straight forward - for optimization purposes, a view that is set to gone will not be measured. secondly, every view must be measured at least once, so the default is that each view will be measured once, unless something causes it to have to measure more.

the width/height mismatch measure

consider a horizontal LinearLayout with two TextView children.

if the height of this LinearLayout is match_parent or a fixed value (ex 32dp), then measuring the height of the children is easy:

but what if the LinearLayout’s height is wrap_content? in this case, measuring the child is sometimes easy:

if, however, the child has a height of match_parent, what we’re really saying is, “i want this view to have the same height as the LinearLayout.”

so what is the height of the LinearLayout? in the case where the LinearLayout’s height is set to wrap_content, the height is not known until each child has been measured (the height MeasureSpec received in onMeasure by the LinearLayout itself will be AT_MOST with however much space can be taken up by it).

consequently, children with a match_parent height also have to wait until the LinearLayout’s height is determined, and then measured again in accordance to that height.

note that the same is true for vertical LinearLayouts when they have wrap_content widths and children with match_parent widths.

(note that in LinearLayout.java, you’ll see forceUniformHeight and forceUniformWidth in measureHorizontal and measureVertical, respectively, being called at the end of the measure cycle when they need to be).

example

here’s an example that shows this effect - compare:

the right square in the first image is measured twice, because its height is set to match_parent when the LinearLayout’s height is set to wrap_content. if these match (ex both are wrap_content, both are match_parent, or one of them is fixed), we eliminate this problem1.

note that sometimes, you really have to use match_parent - for example, if the right side had a background and we wanted it to be exactly the same height as the left side, we have no choice but to use match_parent for the height (unless we can specify a fixed height or change the parent, etc).

weights

setting a non-zero android:layout_weight may cause a View to be measured an extra time.

first, let’s consider when a non-zero android:layout_weight does not cause an extra measure pass -

for a vertical LinearLayout:

for a horizontal LinearLayout:

in all other cases, the view with non-zero layout_weight will be measured an extra time.

android:baselineAligned

the documentation for android:baselineAligned says:

When set to false, prevents the layout from aligning its children’s baselines. This attribute is particularly useful when the children use different values for gravity. The default value is true.

this is generally useful for aligning text with the same gravity. consider:

and compare it to:

here’s a more visual representation:

so when can you turn it off?

  1. if the children of the LinearLayout don’t need to be baseline aligned (i.e. are not TextViews or ImageViews with setBaseline or setBaselineAlignedBottom set, etc).
  2. if the children are TextViews but all have different alignments anyway.
  3. if it doesn’t matter (for example in the above screenshots, because both sides use English text with the same text size, the images are identical - if they were different languages, or if the text size of one was different than the other, the two would definitely be different).

here’s a visual representation of how baselineAlignment can make a difference:

why does this matter?

why write a blog post about this, and why does it matter? there are 2 main reasons - first, given the popularity of LinearLayout within most apps, its important to know how it works to have great performing apps.

secondly, measures can be expensive - and avoiding extra work is always great! this is especially relevant when you consider nested LinearLayouts - let’s suppose that a nested LinearLayout will be measured 3 times, and has children that it would ordinarily measure 3x - those children now get measured 72 times!


  1. note that this is only true for a non-nested LinearLayout - if instead, the aforementioned layout is the child of a vertical LinearLayout with a height of wrap_content, the TextView with match_parent height will still get measured twice, because the extra measure at the end happens when a child is match_parent and the parent’s height MeasureSpec is not EXACTLY (in this case, its AT_MOST due to wrap_content). ↩︎

  2. in theory, they’d get measured 9 times, but the variation in the specs that are passed in to the nested LinearLayout let some of the views skip extra measures, especially in the first 2 measure passes.
    
     ↩︎
comments powered by Disqus