on LinearLayout measures
Jan 27, 2016 · 6 minute read · Commentscode
android
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):
- a (non-nested)
LinearLayout
will measure each non-hidden child either 1x, 2x, or 3x (gone
views aren’t measured). - whenever possible, set
android:baselineAligned
tofalse
, especially when using weights. - in a horizontal
LinearLayout
withwrap_content
height, avoid setting any of the children’s heights tomatch_parent
. conversely, in a verticalLinearLayout
withwrap_content
width, avoid setting any of the children’s widths tomatch_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:
- a View with
android:visibility="gone"
won’t be measured. - a View will default to being measured once, unless more measurements are necessary (see rules 3 and 4).
- when the non-
android:orientation
direction of theLinearLayout
iswrap_content
and its child’s ismatch_parent
, the child will be measured an extra time.- in a horizontal
LinearLayout
withwrap_content
height, a child withmatch_parent
height will be measured an extra time. - in a vertical
LinearLayout
withwrap_content
width, a child withmatch_parent
width will be measured an extra time.
- in a horizontal
- 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 not0dp
, (or is0dp
, but theLinearLayout
doesn’t haveandroid:baselineAligned="false"
set). - in a vertical
LinearLayout
with non-wrap_content
height, ifandroid:layout_height
is not0dp
.
- in a horizontal
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:
- if the child has a fixed height, use that
- if the child’s height is
match_parent
, then pass in theLinearLayout
’s height with aMeasureSpec
mode ofEXACTLY
. - if the child’s height is
wrap_content
, then pass in theLinearLayout
’s height with aMeasureSpec
mode ofAT_MOST
.
but what if the LinearLayout
’s height is wrap_content
? in this case, measuring the child is sometimes easy:
- if the child has a fixed height, use that.
- if the child has a height of
wrap_content
, pass a heightMeasureSpec
with a mode ofAT_MOST
and the available space to theLinearLayout
.
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 LinearLayout
s 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
:
- when the
LinearLayout
has alayout_height
ofwrap_content
, there is no extra measure (this makes sense, since there is no “extra space” to divvy up amongst the views at the end, since we’re telling theLinearLayout
to only be as large as its content). - when the
layout_height
of the child is0dp
.
for a horizontal LinearLayout
:
- when the
LinearLayout
has alayout_width
ofwrap_content
, there is no extra measure. - when the
layout_width
of the child is0dp
, and theLinearLayout
itself hasandroid:baselineAligned="false"
set.
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?
- if the children of the
LinearLayout
don’t need to be baseline aligned (i.e. are notTextView
s orImageView
s with setBaseline orsetBaselineAlignedBottom
set, etc). - if the children are
TextView
s but all have different alignments anyway. - 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 LinearLayout
s - 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!
-
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). ↩︎
-
↩︎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.