Watch That Baseline Alignment

Written by: on October 17

Let's go out on a limb and say that every Android developer at one point or another uses LinearLayout to build a row of elements.

Let’s take the following simple layout as an example of what I mean.  A horizontal row of buttons all evenly spaced out across the container using weight.

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">
    <Button
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:textSize="12dp"
        android:text="Last" />
    <Button
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:textSize="12dp"
        android:text="Next" />
    <Button
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:textSize="12dp"
        android:text="Reload" />
    <Button
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:textSize="12dp"
        android:text="Exit" />
</LinearLayout>

The weight system of LinearLayout makes using it to build rows with even spacing across the children simple and effective.  However, as often as you may have done this, you might have also run into a perplexing issue that arises when the text of one or more elements inside the row gets long enough to force line wrapping.

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">
    <Button
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:textSize="12dp"
        android:text="Last" />
    <Button
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:textSize="12dp"
        android:text="Next" />
    <Button
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:textSize="12dp"
        android:text="Reload Content" />
    <Button
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:textSize="12dp"
        android:text="Exit" />
</LinearLayout>

Seeing this is often followed by…

(╯°□°) ╯︵ ┻━┻

…well, at least in my case. The first time you see this, it is a bit maddening because there is no apparent reason for what has just happened.  If you’re like me, you’ll go through your mental checklist:

  • Did I miss a margin somewhere?
  • Is there extra padding in the view or the background image?
  • Does the view add extra padding to accommodate for multiple lines of text?
  • Why does it only happen to the view when the text wraps?

Not only has the view content been shifted down, but the parent container isn’t sized to fit the larger height this creates, so part of the shifted view is clipped!  Also notice that the view is shifted even in cases where the multiple lines don’t make the view inherently taller. This behavior makes absolutely no sense, until you understand what is actually going on under the hood.  If we take a much closer look at the alignment of the child elements, something interesting emerges. Eureka!  The views are being laid out such that the first line of text is vertically aligned for each child.  This is known as “baseline” alignment, and it just so happens LinearLayout has a flag to control this, and it’s enabled by default.  In other words, LinearLayout (and its subclasses) will attempt to align child elements by their text baseline when possible.  Child elements subject to this are those that report a valid baseline; basically all the subclasses of TextView, but it could be any View that overrides and returns a positive value from getBaseline(). Because of this, you may encounter this behavior for any container widget that is built from LinearLayout (which includes TableLayout, RadioGroup, and SearchView) and child element based on TextView (Button, CheckBox, RadioButton, EditText, just to name a few).  More often than not, I hit this condition in my own code building a grid of checkable boxes or a row of radio buttons. ##Solution In some cases, depending on your container height sizing, the solution can simply be to enforce centered gravity on the child elements by adding android:gravity="center_vertical" to the layout container.  Although to some degree, even in cases where this works it is a case of solving the symptom rather than the cause. In this author’s humble opinion, a better solution is to disable baseline alignment for the container view when it is not useful for your application.  This can be done in XML with android:baselineAligned="false" or in Java code via setBaselineAligned(false).  Here is our previous example with the fix applied.

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_baselineAligned="false"
    android:orientation="horizontal">
    <Button
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:textSize="12dp"
        android:text="Last" />
    <Button
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:textSize="12dp"
        android:text="Next" />
    <Button
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:textSize="12dp"
        android:text="Reload Content" />
    <Button
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:textSize="12dp"
        android:text="Exit" />
</LinearLayout>

Hopefully this tip will save you from the many facepalms that my forehead was forced to endure. Like solving problems like these? Interested in an Android development position at Double Encore? We’re currently hiring new engineers for our Denver team!

Dave Smith

Dave Smith is a Senior Engineer at Double Encore, Inc., a leading mobile development company. Dave is an expert in developing mobile applications that integrate with custom hardware and devices. His recent focus lies mainly in integrating the Android platform with embedded SoC hardware. He is a published author and speaks regularly at conferences on topics related to Android development. You can follow Dave on Twitter and Google+.

Article


Add your voice to the discussion: