Computer scienceMobileAndroidUser InterfaceUI componentsContainers

LinearLayout in Action

21 minutes read

Many real-life UIs can be built using basic layouts and widgets you're already familiar with. To help you understand how to do this, we're going to dive deeper into LinearLayout usage scenarios and patterns in this topic.

ScrollView

When a view is bigger than its container, you can use ScrollView to make it scrollable. It is a container for wrapping a single child that is often a LinearLayout. You can see an example below:

Scrollable LinearLyout Inside a ScrollView

Show XML as text
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textAppearance="?android:textAppearanceLarge"
            android:text="End-user license agreement" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textAppearance="?android:textAppearanceMedium"
            android:text="..." />

    </LinearLayout>

</ScrollView>

ScrollView is used for vertical scrolling and normally has a match_parent or fixed height. It expects its child's height to be set to wrap_content.

The HorizontalScrollView container is similar to ScrollView and is used for horizontal scrolling, as its name suggests.

LinearLayout baseline alignment

You already know that LinearLayout allows you to line up views vertically or horizontally. It also supports gravities and weights. So, what other features does LinearLayout have? Let's start by discussing baseline alignment.

Every line of text has a baseline — an imaginary horizontal line on which its letters "stand."

A horizontal LinearLayout is baselineAligned by default. This means that all TextView children share the same baseline. Consider the following layout example (note that CheckBox inherits TextView and also has a baseline):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <CheckBox
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="24sp"
        android:text="I accept terms " />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="16sp"
        android:text="(and read them carefully)" />

</LinearLayout>

These TextViews each have a different textSize and a different height. No gravities have been specified, and you can see how the layout looks below (a gray line has been added to indicate the baseline):

Text and checkbox on the same line

The above is rendered nicely, but you need to be aware that baseline alignment can play a nasty trick on you when dealing with multiline text. The following screenshot is taken on a screen with smaller width:

Text multilined but still on the same lines

Another case where problems can occur is with bordered components such as buttons:

Buttons aligned by text instead of borders

Material theme makes buttons look like this: with background, shadow, and capitalized text.

In the above example, the left button expands its height to match the minHeight theme property. This results in the vertical distance between the text and the horizontal borders becoming bigger than the padding. However, the right button uses more height than minHeight, and the vertical space is caused by the padding, also from the theme. So, baseline alignment leads to an undesirable offset in this case.

Diagram of buttons aligned by text

The lesson here is that baseline alignment plays well with single-line pieces of text but gives unexpected results with multiline ones. It should therefore be explicitly disabled for every horizontal LinearLayout where this type of alignment is not intended.

Disabling baseline alignment is not the only cure. Buttons look better when they have equal height, and plain text can be forced to remain on a single line, as shown below:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:baselineAligned="false">
        <!--
        baselineAligned won't break this layout
        because buttons have match_parent height.
        However, it is disabled both for explicitness
        and performance reasons.
        -->

        <Button
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:text="Accept" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:text="Decline, exit, delete\u00A0account, and\u00A0uninstall" />

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <CheckBox
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="24sp"
            android:text="I accept terms " />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="16sp"
            android:ellipsize="middle"
            android:singleLine="true"
            android:text="(and read them carefully)" />

    </LinearLayout>

</LinearLayout>

Button and text aligned by borders

LinearLayout dividers

LinearLayout can also draw dividers between children. To take advantage of this feature, you simply need to create an appropriate drawable (a horizontal line, in our case), set it as a divider, and specify where you want it to be placed:

<!-- drawable/divider.xml -->
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#20000000" />
    <size android:height="1dp" />
</shape>
<!-- layout/linear_with_dividers.xml -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:divider="@drawable/divider"
    android:showDividers="middle|end">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="8dp"
        android:textAppearance="?android:textAppearanceLarge"
        android:text="Thing #1" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="8dp"
        android:textAppearance="?android:textAppearanceLarge"
        android:text="Thing #2" />

</LinearLayout>

The value middle|end is used in the above example. This specifies that dividers should be placed between views and after the last one:

The other option is beginning, which draws a divider before the first view. You can specify any combination of these three options, including all three together if you wish: beginning|middle|end.

You could also use (some would say abuse) this feature to add spaces between views by setting android:showDividers="middle" and using an invisible <shape> drawable without a <solid color=…> tag. The benefit of this approach is that you don't need to decide whether to set the top view's layout_marginBotton or the bottom view's layout_marginTop. It also helps when you need to show or hide views programmatically and preserve consistent spacing.

LinearLayout weights

Views with a nonzero layout_weight expand to fill all the available space, which is distributed proportionally to their weight values. You can see the simplest example of this below:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.appcompat.widget.Toolbar
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#E2E2E2"
        app:title="The coolest app ever" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:gravity="center"
        android:text="…is not developed yet."
        android:textSize="24sp" />

</LinearLayout>

TextView inside LinearLayout

The wrap_content Toolbar fills the required space, and the TextView takes the rest. Its height is set to 0dp because it is irrelevant for a weighted view.

What else can be done with weights?

LinearLayout itself has a weightSum attribute and its value can differ from the actual sum of its children's weights. When the weightSum is smaller, the children fall outside the LinearLayout's bounds, which isn't very useful. But if the weightSum is bigger, the spare part serves as space. For example, we can specify that one child uses 30% and another uses 60% of the available space:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    android:weightSum="1"
    android:gravity="center_horizontal">

    <View
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight=".3"
        android:background="@android:color/holo_green_light" />

    <View
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight=".6"
        android:background="@android:color/holo_red_light" />

</LinearLayout>

These settings yield the following result:

First view takes 30% of space and the second 60%

There's also another interesting feature you should know about: layout_weight can be used together with the wrap_content size. If you take this approach and have some excess space available, the view will receive a part of it. Otherwise, wrap_content will behave in the usual way, and the content won't be squeezed. This is especially useful inside a ScrollView when content is small on big screens but can become scrollable on smaller ones.

You can see how to do this in the example below. The fillViewport attribute of ScrollView is also set to true so that its content is at least as large as the ScrollView itself:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fillViewport="true">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="8dp">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:textAppearance="?android:textAppearanceLarge"
            android:text="End-user license agreement" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textAppearance="?android:textAppearanceMedium"
            android:text="..." />

        <!-- another two TextViews: title and content -->

    </LinearLayout>

</ScrollView>

This gives us space around titles on bigger screens and scrollable content on smaller ones:

Scrollable text inside LinearLayot with titles

TextView compound drawables

We often need to place an image and text next to each other: a product photo near its name, a profile photo above a username, or even a button with an icon. An obvious way to do this is to enclose a TextView and an ImageView in a LinearLayout. This is acceptable, but there's another option that is simpler and more performant.

A TextView has six XML attributes that enable you to attach images known as compound drawables next to its contents: drawable(Left|Start|Top|Right|End|Bottom). This means that a TextView is capable of showing up to four drawables on its own. It's also possible to distance drawables from text using the drawablePadding attribute.

<Button
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Play"
    android:drawablePadding="4dp"
    android:drawableTop="@android:drawable/ic_media_play" />

Button with icon on the top of it

There are, of course, some restrictions, which are listed below. But you can use a LinearLayout if you run into any of them:

  • gravity never affects compound drawables; it only impacts text positioning.

  • There is only one drawablePadding attribute for all four drawables (you can work around this by creating an XML drawable file with <inset> root tag and specifying different offsets).

  • There's no scaleType and many other ImageView-specific attributes are also missing.

  • Also, there aren't separate click listeners to indicate when the specific drawable is clicked — an OnClickListener can only be applied to the whole TextView.

The Java/Kotlin API related to compound drawables slightly differs from the XML attributes. There's a (get|set)CompoundDrawablePadding method pair for the drawablePadding attribute, and a bunch of methods for getting and setting drawables themselves. These can be expressed generally as setCompoundDrawables[Relative][WithIntrinsicBounds](4×(Drawable? or @DrawableRes Int)) and getCompoundDrawables[Relative](): Array<Drawable>.

The simplest one is setCompoundDrawablesWithIntrinsicBounds(left: Int, top: Int, right: Int, bottom: Int): you just pass drawable resource IDs, and it works. For example, setCompoundDrawablesWithIntrinsicBounds(0, android.R.drawable.ic_media_play, 0, 0) is similar to the XML version above. Relative versions use start and end instead of left and right. Also, both methods are overloaded to accept the Drawable? type for times when they are already instantiated or created programmatically.

So what does "with intrinsic bounds" mean? Some drawables have their own sizes (all vector ones do), and some of them don't (like color drawables). Shapes are even more variable: they can have width only, height only, both, or none. The WithIntrinsicBounds method versions use the sizes provided by drawables.

If you need to specify a drawable's size explicitly, you can only do this programmatically. Start by setting the drawables' bounds, and then call setCompoundDrawables[Relative](Drawable?, Drawable?, Drawable?, Drawable?), which never uses intrinsic bounds. The following snippet illustrates this approach by creating the whole TextView and a shape programmatically:

val sp = resources.displayMetrics.scaledDensity

TextView(context).apply {
    text = "#8C5AFF"
    setTextColor(Color.BLACK)
    textSize = 24f
    typeface = Typeface.MONOSPACE
    setCompoundDrawables(
        GradientDrawable().apply {
            cornerRadius = 4 * sp
            setColor(0xFF8C5AFF.toInt())
            setBounds(0, 0, (24 * sp).toInt(), (24 * sp).toInt())
        },
        null,
        null,
        null,
    )
    compoundDrawablePadding = (4 * sp).toInt()
}

It's important to pay attention to the setBounds method call: it accepts left, top, right, and bottom integers. In the above example, we have passed the desired width for the right parameter and the height for the bottom one. You can see how the result looks below:

Color code with color icon at the right side

Conclusion

You've learned a lot by reading this topic! You now know how to use various LinearLayout features, like baseline alignment, dividers, and weightSum. You're also familiar with ScrollView and its fillViewport flag. In addition, you've discovered that using an extra LinearLayout can be avoided when you just need a TextView with an image.

45 learners liked this piece of theory. 0 didn't like it. What about you?
Report a typo