Layout Managers in Android: The Basics

Continuing my research into the Android world, I’m concentrating today on layout managers (or just layouts). The position of every single widget on the screen of the device is controlled by the layout manager. In some cases, the visibility (or lack thereof) is also determined by the layout manager. Fortunately, there are only a few you need to know about.

The first thing to learn is that you can embed layouts within each other. A layout is just another widget. this is good when you want to break the layout you desire into zones that are handled separately.

Let’s take a look at a couple of layouts.

LinearLayout

I’m betting this is the first layout that most people understand fully. In a LinearLayout, each widget is placed next to the previous one – either horizontally or vertically. This also makes it perfect for building zoned layouts. Take this screenshot from the open-source Sol weather app:

I can represent most of this app in the LinearLayout by splitting the screen into zones:

I’ve drawn on the various boxes to indicate associated LinearLayouts. This layout has 9 nested LinearLayouts:

  • The green one (vertical) covers the entire page
  • The yellow one (vertical) covers the uppper big-text zone
  • The red one (horizontal) covers the mid-tier banner
  • The purple one (horizontal) covers the controls at the bottom of the page
  • The orange one (vertical) covers the current temperature readings
  • The blue one (horizontal) covers the high/low temperature

I would also have a vertical one for each of the day readings with the day and the icon in it (for an additional 3 layouts). If I were to translate this into XML, it would look like this:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/green_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="#CC33FF"
    android:padding="2dp"
    tools:context="com.shellmonger.linearlayoutexample.MainActivity">

    <!-- Yellow Zone -->
    <LinearLayout
        android:id="@+id/yellow_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="2dp"
        android:orientation="vertical">

        <TextView
            android:id="@+id/current_conditions_icon"
            android:text=" "
            android:textSize="56dp"
            android:paddingTop="24dp"
            android:paddingBottom="24dp"
            android:layout_gravity="center_horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

        <TextView
            android:id="@+id/current_conditions_text"
            android:text="Light Snow"
            android:textSize="40dp"
            android:textColor="@android:color/white"
            android:fontFamily="sans-serif-light"
            android:paddingTop="12dp"
            android:paddingBottom="6dp"
            android:gravity="center_horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

        <TextView
            android:id="@+id/current_location"
            android:text="Chicago, IL"
            android:textSize="24dp"
            android:textColor="@android:color/white"
            android:fontFamily="sans-serif-light"
            android:paddingTop="12dp"
            android:paddingBottom="12dp"
            android:gravity="center_horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>

    <!-- Red Zone -->
    <LinearLayout
        android:id="@+id/red_layout"
        android:background="#E580FF"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingTop="8dp"
        android:paddingBottom="8dp"
        android:orientation="horizontal">

        <!-- Orange Zone -->
        <LinearLayout
            android:id="@+id/orange_layout"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:paddingLeft="8dp"
            android:paddingRight="8dp"
            android:orientation="vertical">
            <TextView
                android:id="@+id/current_temperature"
                android:text="17°"
                android:textColor="@android:color/white"
                android:textSize="20dp"
                android:fontFamily="sans-serif-light"
                android:gravity="center_horizontal"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />
            <LinearLayout
                android:id="@+id/minmaxtemp_layout"
                android:layout_gravity="center_horizontal"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content">
                <TextView
                    android:text="L"
                    android:textColor="@android:color/white"
                    android:fontFamily="sans-serif-light"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content" />
                <TextView
                    android:id="@+id/min_temperature"
                    android:text="15°"
                    android:textColor="@android:color/white"
                    android:fontFamily="sans-serif-light"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content" />
                <TextView
                    android:text="H"
                    android:textColor="@android:color/white"
                    android:fontFamily="sans-serif-light"
                    android:paddingLeft="2dp"
                    android:paddingRight="2dp"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content" />
                <TextView
                    android:id="@+id/max_temperature"
                    android:text="18°"
                    android:textColor="@android:color/white"
                    android:fontFamily="sans-serif-light"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content" />
            </LinearLayout>
        </LinearLayout>

        <!-- Tomorrows Conditions Zone -->
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            
            <LinearLayout
                android:id="@+id/day1_layout"
                android:layout_width="60dp"
                android:layout_height="wrap_content"
                android:orientation="vertical">
                <TextView
                    android:id="@+id/day1_name"
                    android:text="Mon"
                    android:textColor="@android:color/white"
                    android:fontFamily="sans-serif-light"
                    android:layout_gravity="center_horizontal"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content" />
                <ImageView
                    android:id="@+id/day1_icon"
                    android:textColor="@android:color/white"
                    android:fontFamily="sans-serif-light"
                    android:layout_gravity="center_horizontal"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content" />
            </LinearLayout>
    
            <!-- Next Day Conditions Zone -->
            <LinearLayout
                android:id="@+id/day2_layout"
                android:layout_width="60dp"
                android:layout_height="wrap_content"
                android:orientation="vertical">
                <TextView
                    android:id="@+id/day2_name"
                    android:text="Tue"
                    android:textColor="@android:color/white"
                    android:fontFamily="sans-serif-light"
                    android:layout_gravity="center_horizontal"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content" />
                <ImageView
                    android:id="@+id/day2_icon"
                    android:textColor="@android:color/white"
                    android:fontFamily="sans-serif-light"
                    android:layout_gravity="center_horizontal"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content" />
            </LinearLayout>
    
            <!-- Following Day Conditions Zone -->
            <LinearLayout
                android:id="@+id/day3_layout"
                android:layout_width="60dp"
                android:layout_height="wrap_content"
                android:orientation="vertical">
                <TextView
                    android:id="@+id/day3_name"
                    android:text="Wed"
                    android:textColor="@android:color/white"
                    android:fontFamily="sans-serif-light"
                    android:layout_gravity="center_horizontal"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content" />
                <ImageView
                    android:id="@+id/day3_icon"
                    android:textColor="@android:color/white"
                    android:fontFamily="sans-serif-light"
                    android:layout_gravity="center_horizontal"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content" />
            </LinearLayout>
        </LinearLayout>
    </LinearLayout>

    <!-- Purple Zone -->
    <LinearLayout
        android:id="@+id/purple_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="bottom"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/info_icon"
            android:text="i"
            android:paddingLeft="6dp"
            android:paddingRight="6dp"
            android:textColor="@android:color/white"
            android:textSize="24dp"
            android:gravity="center_horizontal"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

        <TextView
            android:id="@+id/switcher"
            android:text="..."
            android:paddingLeft="6dp"
            android:paddingRight="6dp"
            android:textColor="@android:color/white"
            android:textSize="24dp"
            android:gravity="center_horizontal"
            android:layout_width="250dp"
            android:layout_height="wrap_content" />

        <TextView
            android:id="@+id/add_button"
            android:text="+"
            android:textSize="24dp"
            android:paddingLeft="6dp"
            android:paddingRight="6dp"
            android:textColor="@android:color/white"
            android:gravity="end"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </LinearLayout>
</LinearLayout>

This is a lot of code, but the basic premise here is to take a look at the embedded LinearLayout elements. These are “layouts within layouts” and I can take each one alone and construct it the way I want to.

There are two values for the width and height of each element:

  • wrap_content means take up just enough room to hold the widget.
  • match_parent means take up the rest of the space

I can also use absolute values for the width and height. However, I cannot use relative widths (for example, 33% of the width of the parent), which causes problems in a couple of places. For example, this layout when viewed in the Android Studio layout preview looks like this:

Specifically, take a look at the banner items – the light purple area is all squashed to one end, and the buttons in the lower controls bar is not exactly centered. This is because I’ve had to put absolute sizes in the linear layout widget in these areas. There is no way with the linear layout to say “put this widget in the corner, then fill the rest of the space with this other widget”. You cannot represent all layouts with just the LinearLayout.

TableLayout

Let’s talk about the banner across the middle of the screen first. The intent here is to have three equal areas after the current temperature is filled. This is an ideal time to use a TableLayout. I can create a TableRow and then make each element equal size by stretching all the columns to fill the maximum size:

        <!-- Future Conditions Zone -->
        <TableLayout
            android:stretchColumns="0,1,2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <TableRow>
                <!-- Tomorrows Conditions Zone -->
                <LinearLayout
                    android:id="@+id/day1_layout"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="vertical">
                    <TextView
                        android:id="@+id/day1_name"
                        android:text="Mon"
                        android:textColor="@android:color/white"
                        android:fontFamily="sans-serif-light"
                        android:layout_gravity="center_horizontal"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content" />
                    <ImageView
                        android:id="@+id/day1_icon"
                        android:textColor="@android:color/white"
                        android:fontFamily="sans-serif-light"
                        android:layout_gravity="center_horizontal"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content" />
                </LinearLayout>
                <!-- Next Day Conditions Zone -->
                <LinearLayout
                    android:id="@+id/day2_layout"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="vertical">
                    <TextView
                        android:id="@+id/day2_name"
                        android:text="Tue"
                        android:textColor="@android:color/white"
                        android:fontFamily="sans-serif-light"
                        android:layout_gravity="center_horizontal"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content" />
                    <ImageView
                        android:id="@+id/day2_icon"
                        android:textColor="@android:color/white"
                        android:fontFamily="sans-serif-light"
                        android:layout_gravity="center_horizontal"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content" />
                </LinearLayout>
                <!-- Following Day Conditions Zone -->
                <LinearLayout
                    android:id="@+id/day3_layout"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="vertical">
                    <TextView
                        android:id="@+id/day3_name"
                        android:text="Wed"
                        android:textColor="@android:color/white"
                        android:fontFamily="sans-serif-light"
                        android:layout_gravity="center_horizontal"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content" />
                    <ImageView
                        android:id="@+id/day3_icon"
                        android:textColor="@android:color/white"
                        android:fontFamily="sans-serif-light"
                        android:layout_gravity="center_horizontal"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content" />
                </LinearLayout>
            </TableRow>
        </TableLayout>

The important piece here is the stretchColumns attribute on the TableLayout widget. This is a list of zero-indexed columns that is stretched – in this case, all my columns are stretched. I’m still using the LinearLayout for the individual single-day conditions to handle the stacked day name / condition.

I could do the same thing with the controls at the bottom:

    <!-- Purple Zone -->
    <TableLayout
        android:id="@+id/purple_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:stretchColumns="1"
        android:gravity="bottom">
        <TableRow>
            <TextView
                android:id="@+id/info_icon"
                android:text="i"
                android:paddingLeft="6dp"
                android:paddingRight="6dp"
                android:textColor="@android:color/white"
                android:textSize="24dp"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
    
            <TextView
                android:id="@+id/switcher"
                android:text="..."
                android:paddingLeft="6dp"
                android:paddingRight="6dp"
                android:textColor="@android:color/white"
                android:textSize="24dp"
                android:gravity="center_horizontal"
                android:layout_height="wrap_content" />
    
            <TextView
                android:id="@+id/add_button"
                android:text="+"
                android:textSize="24dp"
                android:paddingLeft="6dp"
                android:paddingRight="6dp"
                android:textColor="@android:color/white"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />
        </TableRow>
    </TableLayout>

The TableLayout is great in these stacked layouts, where layouts are embedded in other layouts. They area good for equally distributing the widgets into columns and rows.

Testing Your Layout

In Android Studio, you will see both the XML and a pictorial representation of the layout which is adjusted in real time as you adjust your layout code. You will also see a control for adjusting the type of device that the layout will run on. You should cycle through as many of the devices as you feel appropriate to ensure that the layout works on all of the Android devices you want to support.

I also place example text and images (reminiscent of the actual fonts, text and images that I would see in the app) to ensure my page looks good on every single device. I have not added the icons for the weather conditions yet, but will be doing that before publication. In short, there should be no or little difference between the page in the preview window and the page on an actual device in a running application.

Wrap Up

Using LinearLayout and TableLayout is probably considered an anti-pattern at this point as there are better layout managers, but it’s useful to understand HOW the page is laid out in these cases. These are simple and easily understood layout managers and will serve well for specific scenarios. In fact, most pages can be laid out with just these two layout managers. I like the modularity of this approach, but reading the XML is problematic. The deeper the tree, the more likely you are to mix up where you are on the page.

The best practice is to make your layouts as shallow as possible. Even this simple layout is too deep and has problems with readability. We need to find ways to better position the elements on the page. In the next article, I’m going to look at two more layout managers: the RelativeLayout and the ConstraintLayout and look at alternative ways of representing the same layout as I’ve done here.

Until then, you can check out the code for this example on my GitHub repository.