The Classic Container Classes

Containers — sometimes referred to as layout managers — organize widgets on the screen. Containers position and size widgets based upon rules that you supply along with key device characteristics, such as available screen size.

Three containers have dominated Android app development since Android’s introduction in late 2007: LinearLayout, RelativeLayout, and TableLayout. This chapter focuses on those. Later chapters will explore other container classes, such as 2016’s ConstraintLayout.

Introducing the Sampler App

The Containers/Sampler sample project has a bunch of layout resources that this chapter will use to illustrate how these containers work.

If you were to run this sample app, you would see a series of tabs, with one layout displayed per tab:

Sampler App, As Initially Launched
Figure 106: Sampler App, As Initially Launched

We are not going to get into the Java code associated with this sample app in this chapter. That code relies on other topics, like fragments and the ViewPager widget, that we have not gotten to yet. We will come back to this sample app and see how the tabs were implemented then. For now, the focus is on the layout files showing specific techniques for using these classic containers.

RTL and Your Layouts

Most of the world’s languages are written left-to-right. So, in this paragraph, you read the letters and words starting from the left edge of a line across to the right edge.

Arabic and Hebrew, among others, are written right-to-left. The abbreviation “RTL” refers to these languages.

Originally, Android was focused purely on left-to-right (LTR) languages. As a result, you see attributes referring to things with respect to left and right (e.g., android:paddingLeft).

Slowly, Android improved its RTL support. In particular, starting with API Level 17 (Android 4.2), analogue attributes were added, replacing “left” with “start” and “right” with “end”. When using “start”/“end” attributes (e.g., android:paddingStart), “start” refers to where you start reading a line of text, and “end” refers to where you end reading a line of text:

Language Direction “Start” Means… “End” Means…
LTR Left Right
RTL Right Left

In general, we want the GUI to flow with the language direction. Things that you might have on the left with an LTR language usually go on the right with an RTL language, and so forth.

However, since these “start” and “end” attributes are new to API Level 17, you cannot use them on older devices. Sometimes, we will wind up either using the old “left”/“right” attributes or using both types of attributes, to cover all device versions.

We will revisit RTL language support a bit later in this chapter.

LinearLayout and the Box Model

LinearLayout represents Android’s approach to a box model — widgets or child containers are lined up in a column or row, one after the next.

Concepts and Attributes

To configure a LinearLayout, you have four main areas of control besides the container’s contents: the orientation, the fill model, the weight, the gravity.

Orientation

Orientation indicates whether the LinearLayout represents a row or a column. Just add the android:orientation property to your LinearLayout element in your XML layout, setting the value to be horizontal for a row or vertical for a column.

The orientation can be modified at runtime by invoking setOrientation() on the LinearLayout, supplying it either HORIZONTAL or VERTICAL.

Fill Model

The point behind a LinearLayout — or any of the Android container classes – is to organize multiple widgets. Part of organizing those widgets is determining how much space each gets.

LinearLayout takes an “eldest child wins” approach towards allocating space. So, if we have a LinearLayout with three children, the first child will get its requested space. The second child will get its requested space, if there is enough room remaining, and likewise for the third child. So if the first child asks for all the space (e.g., this is a horizontal LinearLayout and the first child has android:layout_width="match_parent"), the second and third children will wind up with zero width.

Weight

But, what happens if we have two or more widgets that should split the available free space? For example, suppose we have two multi-line fields in a column, and we want them to take up the remaining space in the column after all other widgets have been allocated their space.

To make this work, in addition to setting android:layout_width (for rows) or android:layout_height (for columns), you must also set android:layout_weight. This property indicates what proportion of the free space should go to that widget. If you set android:layout_weight to be the same non-zero value for a pair of widgets (e.g., 1), the free space will be split evenly between them. If you set it to be 1 for one widget and 2 for another widget, the second widget will use up twice the free space that the first widget does. And so on.

The weight for a widget is zero by default.

Another pattern for using weights is if you want to allocate sizes on a percentage basis. To use this technique for, say, a horizontal layout:

  1. Set all the android:layout_width values to be 0 for the widgets in the layout
  2. Set the android:layout_weight values to be the desired percentage size for each widget in the layout
  3. Make sure all those weights add up to 100

If you want to have space left over, not allocated to any widget, you can add an android:weightSum attribute to the LinearLayout, and ensure that the sum of the android:layout_weight attributes of the children are less than that sum. The children will each get space allocated based upon the ratio of their android:layout_weight compared to the android:weightSum, not compared to the sum of the weights. And there will be empty space that takes up the rest of the room not allocated to the children.

Gravity

By default, everything in a LinearLayout is start- and top-aligned. So, if you create a row of widgets via a horizontal LinearLayout, the row will be flush on the start side of the screen (e.g., left in a LTR language).

If that is not what you want, you need to specify a gravity. Unlike the physical world, Android has two types of gravity: the gravity of a widget within a LinearLayout, and the gravity of the contents of a widget or container.

The android:gravity property of some widgets and containers — which also can be defined via setGravity() in Java — tells Android to slide the contents of the widget or container in a particular direction. For example, android:gravity="right" says to slide the contents of the widget to the right; android:gravity="right|bottom" says to slide the contents of the widget to the right and the bottom.

Here, “contents” varies. TextView supports android:gravity, and the “contents” is the text held within the TextView. LinearLayout supports android:gravity, and the “contents” are the widgets inside the container. And so on.

Children of a LinearLayout also have the option of specifying android:layout_gravity. Here, the child is telling the LinearLayout “if there is room, please slide me (and me alone) in this direction”. However, this only works in the direction opposite the orientation of the LinearLayout – the children of a vertical LinearLayout can use android:layout_gravity to control their positioning horizontally (start or end), but not vertically.

For a row of widgets, the default is for them to be aligned so their texts are aligned on the baseline (the invisible line that letters seem to “sit on”), though you may wish to specify a gravity of center_vertical to center the widgets along the row’s vertical midpoint.

Android Studio Graphical Layout Editor

The LinearLayout container can be found in the “Layouts” portion of the Palette of the Android Studio graphical layout editor:

Layouts Palette in Android Studio Graphical Layout Editor
Figure 107: Layouts Palette in Android Studio Graphical Layout Editor

You can drag either the “LinearLayout (vertical)” or “LinearLayout (horizontal)” into a layout XML resource, then start dragging in children to go into the container.

When your LinearLayout is the selected widget, a few new toolbar buttons will appear over the preview:

LinearLayout Toolbar Buttons
Figure 108: LinearLayout Toolbar Buttons

The only one of these that works is the left hand one, which toggles the LinearLayout between vertical and horizontal orientation.

When one of the children of the LinearLayout is the selected widget, the toolbar changes:

LinearLayout Toolbar Buttons, For Selected Child
Figure 109: LinearLayout Toolbar Buttons, For Selected Child

From left to right, the buttons:

Example: Bottom-then-Top

Those rules will make more sense once we work through some examples.

The first example is where we have two widgets. One widget should take up its “natural” amount of space, anchored to the bottom of the screen. The other widget should fill all the remaining space:

Bottom-then-Top Layout, Using LinearLayout
Figure 110: Bottom-then-Top Layout, Using LinearLayout

The XML

The layout XML that generated that screenshot consists of a vertical LinearLayout and two Button widgets:

<?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="vertical"
  android:baselineAligned="false">

  <Button
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight="1"
    android:text="@string/button" />

  <Button
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@string/another_button" />
</LinearLayout>
(from Containers/Sampler/app/src/main/res/layout/bottom_then_top_ll.xml)

The LinearLayout has its sizes (android:layout_width, android:layout_height) set each to match_parent, so it will fill up all space available to it. In this sample app, that will be everything below the tabs.

Both Button widgets have their android:layout_width attributes also set to match_parent, so they will fill up all available space on the screen horizontally.

The bottom Button has its android:layout_height set to wrap_content, so it will only take up as much space as is needed to render its caption and background around it.

The top Button has android:layout_height set to 0dp. If we did not do anything else, that would result in an impossibly-short button. However, we also have android:layout_weight="1".

When this gets rendered on the screen, LinearLayout will make two passes through its children. On the first pass, it asks for how much space each child wants, based upon the android:layout_height values (since this is a vertical LinearLayout). The first Button will ask for 0 pixels of space, while the bottom Button will ask for however much it needs for its content. Then, the second pass of the LinearLayout through its children asks for their weights. The first Button has a weight of 1, while the bottom Button has the default weight of 0. As a result, the top Button gets 1/1 = 100% of all pixels left over from what the first pass used. This causes the top Button to become tall, shoving the bottom Button to the bottom of the screen.

Android Studio Graphical Layout Editor

You can create a new layout resource by right-clicking over the res/layout/ directory and choosing “New > Layout resource file” from the right-mouse context menu. You then get a dialog where you can give the new resource a name and choose the root container for that layout:

New Layout Resource Dialog
Figure 111: New Layout Resource Dialog

What you get by default depends a bit on your project:

As you drag Button widgets into the layout, they are initially given a width of match_parent and a height of wrap_content:

Vertical LinearLayout, with Two Button Widgets
Figure 112: Vertical LinearLayout, with Two Button Widgets

Clicking the top button, then setting the layout_weight attribute to 1 in the Attributes pane, gives the desired look:

Vertical LinearLayout, with Weighted Button Widgets
Figure 113: Vertical LinearLayout, with Weighted Button Widgets

Note that layout_weight may only appear for you in the “View all attributes” perspective of the Attributes pane, rather than in the simplified list.

Example: Stacked-Percent

Weights can get more elaborate than just giving all extra room to one widget.

The stacked-percent scenario allocates space on a percentage basis to all of the children of the LinearLayout. In the sample app, we have a layout file with three buttons, taking up 50%, 30%, and 20% of the space in a vertical LinearLayout:

Stacked-Percent Layout, Using LinearLayout
Figure 114: Stacked-Percent Layout, Using LinearLayout

The XML

This time, the layout resource XML has all three Button widgets with heights of 0:

<?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="vertical">

  <Button
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight="50"
    android:text="@string/fifty_percent"/>

  <Button
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight="30"
    android:text="@string/thirty_percent"/>

  <Button
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight="20"
    android:text="@string/twenty_percent"/>

</LinearLayout>
(from Containers/Sampler/app/src/main/res/layout/stacked_percent_ll.xml)

So, the first pass that the LinearLayout makes through its children, each child asks for 0 pixels of height, meaning that all of the LinearLayout space is available for the second pass.

On the second pass, the children report weights of 50, 30, and 20, respectively. 50+30+20=100, so the top Button gets 50/100 (50%) of the space, the middle Button gets 30/100 (30%) of the space, and the bottom Button gets 20/100 (20%) of the space.

Note that you would get the same results with weights of 5, 3, and 2. Really, it is the ratios of the weights that matter. However, if you are used to thinking in terms of percentages — perhaps due to past experience with other GUI toolkits — you can use integer percentages if you want.

Android Studio Graphical Layout Editor

Constructing this using the graphical layout editor is similar to the previous example:

Vertical LinearLayout, with Full Attributes List
Figure 115: Vertical LinearLayout, with Full Attributes List

Example: URL Dialog

Of course, you are not limited to one axis. You can nest LinearLayout widgets to structure things along both the X and the Y axis.

For example, you might be aiming for a dialog-style form like this:

URL-Dialog Layout, Using LinearLayout
Figure 116: URL-Dialog Layout, Using LinearLayout

The XML

This layout resource is a bit more complicated, as we are now up to four widgets plus three LinearLayout containers:

<?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">

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

    <TextView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_marginLeft="4dp"
      android:layout_marginStart="4dp"
      android:text="@string/url" />

    <EditText
      android:id="@+id/entry"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:inputType="text" />

  </LinearLayout>

  <LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="end"
    android:orientation="horizontal">

    <Button
      android:id="@+id/cancel"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="@string/cancel" />

    <Button
      android:id="@+id/ok"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="@string/ok" />
  </LinearLayout>

</LinearLayout>
(from Containers/Sampler/app/src/main/res/layout/url_dialog_ll.xml)

The outer LinearLayout is vertical, serving to stack the two horizontal LinearLayout rows. Each of those horizontal LinearLayout containers has a height of wrap_content and a width of match_parent, so they span the width of the screen but only need as much height as is required to render their widget contents.

The first of our four widgets is the TextView. Since it is in the first horizontal LinearLayout, it will be flush on the one side (left in a LTR language like English). However, we have 4dp of margin, using both the older android:layout_marginLeft and the newer, RTL-capable android:layout_marginStart attributes. So, the widget is inset a bit from the edge.

The second widget is the EditText. It has a width of match_parent, so it will take over all remaining space after the TextView.

The bottom LinearLayout has android:gravity="end", which will cause its contents to slide towards the end side of the LinearLayout (right in a LTR language like English). It contains the two Button widgets, and that is why the two Button widgets are slid over to the opposite end of the form.

Android Studio Graphical Layout Editor

Not surprisingly, the more complex the layout you want to create, the more work is required to create it. The IDE only helps to a point.

So, to construct this layout, you would need to:

Vertical LinearLayout Holding Horizontal LinearLayout
Figure 117: Vertical LinearLayout Holding Horizontal LinearLayout

8dp Padding on the Horizontal LinearLayout
Figure 118: 8dp Padding on the Horizontal LinearLayout

Horizontal LinearLayout with TextView
Figure 119: Horizontal LinearLayout with TextView

Horizontal LinearLayout with TextView and EditText
Figure 120: Horizontal LinearLayout with TextView and EditText

Component Tree with Two Horizontal LinearLayouts
Figure 121: Component Tree with Two Horizontal LinearLayouts

Two Buttons in Lower Horizontal LinearLayout
Figure 122: Two Buttons in Lower Horizontal LinearLayout

Two Smaller Buttons in Lower Horizontal LinearLayout
Figure 123: Two Smaller Buttons in Lower Horizontal LinearLayout

Two Smaller Right-Flush Buttons in Lower Horizontal LinearLayout
Figure 124: Two Smaller Right-Flush Buttons in Lower Horizontal LinearLayout

Buttons with Desired Captions
Figure 125: Buttons with Desired Captions

Now, having done all of that, the right-most toolbar button above the preview and blueprint will now be a yellow triangle, indicating some warnings about the layout that we built. Tapping that icon will display a pane with messages explaining what Android Studio does not like:

Layout Warning Messages
Figure 126: Layout Warning Messages

Each of the warnings in the list usually has an explanation, some suggested fixes, and other explanatory information. Clicking the button for a suggested fix will apply that fix and clear up that warning.

Some of the warnings may come from the captions for the TextView and Button widgets. If you just type a string into the Attributes pane, that fills in the literal string into the layout, and ideally we use string resources. For those, the “Extract string resource” suggested fix will allow you to define those as string resources.

Some of those warnings will be about certain things about styles (e.g., “Buttons in button bars should be borderless”). While in the long run you might care about those warnings, in the short term, while you are learning Android, ignoring the warnings using the suggested fix is a good idea.

One of those warnings will be about a missing android:labelFor attribute, which is part of the accessibility support system in Android, which we will explore later. Another is about the order of the buttons, which Google disagrees with.

Example: A Bigger Form

Not everything in a form has to be inside a horizontal LinearLayout which is itself inside a vertical LinearLayout. You only need a horizontal LinearLayout when you have a row that contains two or more widgets. For single-widget rows, they can just be simple children of vertical LinearLayout.

For example, perhaps you have a more elaborate form in mind, with several fields and other widgets, like this one:

Form Layout, Using LinearLayout
Figure 127: Form Layout, Using LinearLayout

The XML

The form’s layout resource resembles the URL-dialog scenario from earlier:

<?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="vertical"
  android:padding="8dp">

  <LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">
    <TextView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="@string/name" />
    <EditText
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:inputType="text" />
  </LinearLayout>

  <LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">
    <TextView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="@string/home_planet" />
    <EditText
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:inputType="text" />
  </LinearLayout>

  <CheckBox
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@string/android_programmer" />

  <LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">
    <TextView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="@string/favorite_food" />
    <EditText
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:inputType="text" />
  </LinearLayout>

  <Button
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@string/do_something" />

</LinearLayout>
(from Containers/Sampler/app/src/main/res/layout/form_ll.xml)

However, note that the CheckBox and the Button are not themselves wrapped in horizontal LinearLayout containers. They are merely direct children of the vertical LinearLayout. In this case, both are set to have a width of match_parent, though that is not required — you could have them set to wrap_content if you prefer.

We will look at CheckBox in greater detail in an upcoming chapter.

Android Studio Graphical Layout Editor

Setting up this form follows the same basic recipe as was used for the simpler dialog form from earlier in this chapter:

Bigger Form, As Seen in Android Studio
Figure 128: Bigger Form, As Seen in Android Studio

The Problem

If you look back at the screenshot, you will notice that the labeled EditText widgets are ragged, in terms of their layout. Each EditText immediately follows the TextView label, without regard to any sort of “columns”. That is because each LinearLayout is largely independent. You cannot readily have one row depend upon the other rows.

A TableLayout would be a better choice for this sort of a form, as there we can have distinct columns of labels and fields. We will see how TableLayout handles this structure later in this chapter.

All Things Are Relative

RelativeLayout, as the name suggests, lays out widgets based upon their relationship to other widgets in the container and the parent container. You can place Widget X below and to the left of Widget Y, or have Widget Z’s bottom edge align with the bottom of the container, and so on.

Concepts and Attributes

To make all this work, we need ways to reference other widgets within an XML layout file, plus ways to indicate the relative positions of those widgets.

Positions Relative to Container

The easiest relations to set up are tying a widget’s position to that of its container:

  1. android:layout_alignParentTop says the widget’s top should align with the top of the container
  2. android:layout_alignParentBottom says the widget’s bottom should align with the bottom of the container
  3. android:layout_alignParentStart says the widget’s start side should align with the start side of the container
  4. android:layout_alignParentEnd says the widget’s end side should align with the end side of the container
  5. android:layout_centerHorizontal says the widget should be positioned horizontally at the center of the container
  6. android:layout_centerVertical says the widget should be positioned vertically at the center of the container
  7. android:layout_centerInParent says the widget should be positioned both horizontally and vertically at the center of the container

All of these attributes take a simple boolean value (true or false). Also, there are android:layout_alignParentLeft and android:layout_alignParentRight attributes, for pre-Android 4.2 devices or for cases where you want to position irrespective of language direction.

Note that the padding of the widget is taken into account when performing these various alignments. The alignments are based on the widget’s overall cell (combination of its natural space plus the padding).

Relative Notation in Attributes

The remaining attributes of relevance to RelativeLayout take as a value the identity of a widget in the container. To do this:

The first occurrence of an id value should have the plus sign (@+id/widget_a); the second and subsequent times that id value is used in the layout file should drop the plus sign (@id/widget_a). This allows the build tools to better help you catch typos in your widget id values — if you do not have a plus sign for a widget id value that has not been seen before, that will be caught at compile time. For example, if Widget A appears in the RelativeLayout before Widget B, and Widget A is identified as @+id/widget_a, Widget B can refer to Widget A in one of its own attributes via the identifier @id/widget_a.

Positions Relative to Other Widgets

There are four attributes that control position of a widget vis-à-vis other widgets:

  1. android:layout_above indicates that the widget should be placed above the widget referenced in the property
  2. android:layout_below indicates that the widget should be placed below the widget referenced in the property
  3. android:layout_toStartOf indicates that the widget should be placed to the start of the widget referenced in the property
  4. android:layout_toEndOf indicates that the widget should be placed to the end of the widget referenced in the property

There are also android:layout_toLeftOf and android:layout_toRightOf attributes for use with older devices.

Beyond those four, there are five additional attributes that can control one widget’s alignment relative to another:

  1. android:layout_alignTop indicates that the widget’s top should be aligned with the top of the widget referenced in the property
  2. android:layout_alignBottom indicates that the widget’s bottom should be aligned with the bottom of the widget referenced in the property
  3. android:layout_alignStart indicates that the widget’s starting edge should be aligned with the starting edge of the widget referenced in the property
  4. android:layout_alignEnd indicates that the widget’s ending edge should be aligned with the ending edge of the widget referenced in the property
  5. android:layout_alignBaseline indicates that the baselines of the two widgets should be aligned (where the “baseline” is that invisible line that text appears to sit on)

The last one is useful for aligning labels and fields so that the text appears “natural”. Since fields have a box around them and labels do not, android:layout_alignTop would align the top of the field’s box with the top of the label, which will cause the text of the label to be higher on-screen than the text entered into the field.

Android Studio Graphical Layout Editor

You will find RelativeLayout in the “Legacy” section of the Palette in the Android Studio Graphical Layout editor. You can drag that into your layout XML resource.

Legacy Section of Palette, RelativeLayout Highlighted
Figure 129: Legacy Section of Palette, RelativeLayout Highlighted

As you drag other widgets into your RelativeLayout, you will see arrows hinting at the rules that will be applied if you drop the widget at the current mouse location:

Dragging a Widget in a RelativeLayout
Figure 130: Dragging a Widget in a RelativeLayout

Getting the rules that you want may or may not be possible purely through drag-and-drop. You may need to just drop the widget into the RelativeLayout and manually adjust the rules, whether by using the Attributes pane or by editing the XML directly.

Example: Bottom-then-Top

Earlier in the chapter, we saw how to implement the bottom-then-top pattern using a LinearLayout, where we had a small button on the bottom and a large button on the top. The large button was set to take up all space that was not required by the small button.

We can achieve the same result using a RelativeLayout.

The XML

As with the LinearLayout scenario, we have one container plus the two Button widgets. In this case, the container is a RelativeLayout:

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

  <Button
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_above="@+id/another_button"
    android:layout_alignParentTop="true"
    android:text="@string/button" />

  <Button
    android:id="@id/another_button"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:text="@string/another_button" />
</RelativeLayout>
(from Containers/Sampler/app/src/main/res/layout/bottom_then_top_rl.xml)

The bottom Button has the same size as before, with android:layout_width set to match_parent and android:layout_height set to wrap_content. However, it also has android:layout_alignParentBottom="true", anchoring it to the bottom of the RelativeLayout. Since the RelativeLayout fills all available space, the bottom Button is anchored to the bottom of the screen, in effect.

The top Button has two RelativeLayout positioning attributes:

Given these rules, it does not matter what the android:layout_height attribute value is. The Button will be stretched between those two anchor points: the top of the RelativeLayout and the top of the bottom Button.

Android Studio Graphical Layout Editor

By default, when creating a new layout resource file, you get either a ConstraintLayout or a vertical LinearLayout as the root element. You can change that by replacing the “Root element” value with any other widget or container class name, such as RelativeLayout:

New Layout Resource with RelativeLayout
Figure 131: New Layout Resource with RelativeLayout

However, from there, using the drag-and-drop capabilities will start to be more of a pain than they are worth.

You can drag a Button into the RelativeLayout, for example:

RelativeLayout and Button
Figure 132: RelativeLayout and Button

However, you get no visual feedback after you drop the widget of what the rules are that the IDE chose, based on your drag-and-drop location. If you rummage through the Attributes pane, you will see that the rules are reflected in some checkbox attributes for the boolean RelativeLayout android:layout_ attributes, plus margins based on how far from the RelativeLayout edges you placed the widget:

Button Attributes in RelativeLayout
Figure 133: Button Attributes in RelativeLayout

Fixing those through the Attributes pane is no easier than is fixing them through the XML editor. Arguably, using the Attributes pane is slower.

Example: URL Dialog

We also used LinearLayout to create the “URL dialog” UI, where we had the labeled field along with “OK” and “Cancel” buttons. That same structure can also be built using a RelativeLayout.

With LinearLayout, we needed three containers: one vertical LinearLayout wrapped around two horizontal LinearLayouts. RelativeLayout is simpler from that standpoint, as we only need one RelativeLayout. The complexity moves into the widgets instead:

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

  <TextView
    android:id="@+id/label"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignBaseline="@+id/entry"
    android:layout_alignParentLeft="true"
    android:layout_alignParentStart="true"
    android:layout_marginLeft="4dp"
    android:layout_marginStart="4dp"
    android:text="@string/url"/>

  <EditText
    android:id="@id/entry"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentTop="true"
    android:layout_toRightOf="@id/label"
    android:layout_toEndOf="@id/label"
    android:inputType="text"/>

  <Button
    android:id="@+id/ok"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignRight="@id/entry"
    android:layout_alignEnd="@id/entry"
    android:layout_below="@id/entry"
    android:text="@string/ok"/>

  <Button
    android:id="@+id/cancel"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignTop="@id/ok"
    android:layout_toLeftOf="@id/ok"
    android:text="@string/cancel"/>

</RelativeLayout>
(from Containers/Sampler/app/src/main/res/layout/url_dialog_rl.xml)

All the RelativeLayout itself needs is its size, set the same as with vertical LinearLayout in the earlier version of this sample — a width of match_parent and a height of wrap_content.

Other than android:layout_width and android:layout_height, all of the widget attributes with layout_ are rules used by children of a RelativeLayout for positioning. So, we have:

Widget Horizontal Anchor Vertical Anchor
TextView left/start side of the RelativeLayout, with 4dp of margin baseline of the EditText
EditText right/end side of the TextView top of the RelativeLayout
“OK” Button right/end edge of the EditText bottom of the EditText
“Cancel” Button left/start edge of the “OK” Button top of the “OK” Button

Since the EditText width is set to match_parent, it will fill all the space in the “row” after the TextView. Since the “OK” Button horizontal position is tied to the EditText, the Button slides over to the edge of the screen, dragging along the connected “Cancel” Button.

As a result, we get the same basic UI:

URL-Dialog Layout, Using RelativeLayout
Figure 134: URL-Dialog Layout, Using RelativeLayout

Example: Overlap

RelativeLayout also has a feature that LinearLayout lacks — the ability to have widgets overlap one another. Later children of a RelativeLayout are “higher in the Z axis” than are earlier children, meaning that later children will overlap earlier children if they are set up to occupy the same space in the layout.

Here, we have two buttons, where the “I am small” button overlaps the “I am big” button:

Overlap Layout, Using RelativeLayout
Figure 135: Overlap Layout, Using RelativeLayout

The layout is fairly simple:

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

  <Button
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:text="@string/big"
    android:textSize="120dip"
    android:textStyle="bold"/>

  <Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerInParent="true"
    android:text="@string/small"/>

</RelativeLayout>
(from Containers/Sampler/app/src/main/res/layout/overlap_rl.xml)

The first Button is set to fill the screen. The second Button is set to be centered inside the parent, but only take up as much space as is needed for its caption. Hence, the second Button will appear to “float” over the first Button.

Both Button widgets can still be clicked, though clicking on the smaller Button does not also click the bigger Button. Your clicks will be handled by the widget on top in the case of an overlap like this.

On Android 5.0 and higher, it is possible to achieve a similar effect with LinearLayout by using the android:elevation attribute to control the Z axis, where higher elevation values mean higher on the Z axis, floating over those that are lower on the Z axis.

Tabula Rasa

If you like HTML tables, you will like Android’s TableLayout. It allows you to position your widgets in a grid to your specifications. You control the number of rows and columns, which columns might shrink or stretch to accommodate their contents, and so on.

TableLayout works in conjunction with TableRow. TableLayout controls the overall behavior of the container, with the widgets themselves poured into one or more TableRow containers, one per row in the grid.

Concepts and Attributes

For all this to work, we need to figure out how widgets work with rows and columns, plus how to handle widgets that live outside of rows.

Putting Cells in Rows

Rows are declared by you, the developer, by putting widgets as children of a TableRow inside the overall TableLayout. You, therefore, control directly how many rows appear in the table.

The number of columns are determined by Android; you control the number of columns in an indirect fashion.

First, there will be at least one column per widget in your longest row. So if you have three rows, one with two widgets, one with three widgets, and one with four widgets, there will be at least four columns.

However, a widget can take up more than one column by including the android:layout_span property, indicating the number of columns the widget spans. This is akin to the colspan attribute one finds in table cells in HTML:


<TableRow>
  <TextView android:text="URL:" />
  <EditText
    android:id="@+id/entry"
    android:layout_span="3"/>
</TableRow>

In the above XML layout fragment, the field spans three columns.

Ordinarily, widgets are put into the first available column. In the above fragment, the label would go in the first column (column 0, as columns are counted starting from 0), and the field would go into a spanned set of three columns (columns 1 through 3). However, you can put a widget into a different column via the android:layout_column property, specifying the 0-based column the widget belongs to:


<TableRow>
  <Button
    android:id="@+id/cancel"
    android:layout_column="2"
    android:text="Cancel" />
  <Button android:id="@+id/ok" android:text="OK" />
</TableRow>

In the preceding XML layout fragment, the Cancel button goes in the third column (column 2). The OK button then goes into the next available column, which is the fourth column.

Non-Row Children of TableLayout

Normally, TableLayout contains only TableRow elements as immediate children. However, it is possible to put other widgets in between rows. For those widgets, TableLayout behaves a bit like LinearLayout with vertical orientation. The widgets automatically have their width set to match_parent, so they will fill the same space that the longest row does.

Stretch, Shrink, and Collapse

By default, each column will be sized according to the “natural” size of the widest widget in that column (taking spanned columns into account). Sometimes, though, that does not work out very well, and you need more control over column behavior.

You can place an android:stretchColumns property on the TableLayout. This lists the column or columns that should absorb any extra space on the row, if the natural width of the columns collectively is narrower than the available horizontal space. You can:

Conversely, you can place an android:shrinkColumns property on the TableLayout. Again, this should be a single column number, a comma-delimited list of column numbers, or * as shorthand for referring to all columns. The columns listed in this property will try to word-wrap their contents to reduce the effective width of the column — by default, widgets are not word-wrapped. This helps if you have columns with potentially wordy content that might cause some columns to be pushed off the right side of the screen.

You can also leverage an android:collapseColumns property on the TableLayout, again with a column number or comma-delimited list of column numbers (* is not documented as an available option). These columns will start out “collapsed”, meaning they will be part of the table information but will be invisible. Programmatically, you can collapse and un-collapse columns by calling setColumnCollapsed() on the TableLayout. You might use this to allow users to control which columns are of importance to them and should be shown versus which ones are less important and can be hidden.

You can also control stretching and shrinking at runtime via setColumnStretchable() and setColumnShrinkable().

Android Studio Graphical Layout Editor

You will find TableLayout and TableRow in the “Layouts” section of the Palette in the Android Studio graphical layout editor:

Layouts Section of Palette, TableLayout Highlighted
Figure 136: Layouts Section of Palette, TableLayout Highlighted

Given a TableLayout, you can drag one or more TableRow containers into it, then start dragging widgets into the rows, much as you might set up nested LinearLayout containers.

Example: A Bigger Form

One area where TableLayout excels is with forms, particularly if you are using the classic two-column “label and widget” structure for the form. This is because TableLayout can give you real columns, whereas LinearLayout and RelativeLayout cannot.

As with most TableLayout usages, the immediate children of ours are mostly TableRow containers, each providing the contents for the columns:

<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:padding="8dp"
  android:stretchColumns="1">

  <TableRow>
    <TextView android:text="@string/name" />
    <EditText android:inputType="text" />
  </TableRow>

  <TableRow>
    <TextView android:text="@string/home_planet" />
    <EditText android:inputType="text" />
  </TableRow>

  <TableRow>
    <CheckBox
      android:layout_column="1"
      android:text="@string/android_programmer" />
  </TableRow>

  <TableRow>
    <TextView android:text="@string/favorite_food" />
    <EditText android:inputType="text" />
  </TableRow>

  <Button android:text="@string/do_something" />

</TableLayout>
(from Containers/Sampler/app/src/main/res/layout/form_tl.xml)

The TableLayout itself has android:stretchColumns="1", so all leftover space in the rows will go to the second column (with the first column having an index of 0).

The first, second, and fourth TableRow each have the same structure, with a TextView label preceding the EditText where the user can fill in the data. The third TableRow, though, has only one child: the CheckBox. And, our Button lies outside of any TableRow, as a direct child of the root TableLayout. Both the CheckBox and the Button will exist on a row of their own. The difference is that the CheckBox goes in the second column, courtesy of android:layout_column="1", whereas the Button will span the entire row (the way TableRow containers span the entire width of the TableLayout).

So, compared with the original LinearLayout version of this sample, our TableLayout columns are neat and aligned:

Form Layout, Using TableLayout
Figure 137: Form Layout, Using TableLayout

Example: URL Dialog

The “URL dialog” layout, previously seen implemented using LinearLayout and RelativeLayout, can also be implemented using a TableLayout. This is not the most natural use of a TableLayout, but you can do it if you wanted.

As with the form sample above, we start with a root TableLayout having android:stretchColumns="1" to give all extra space to the second column… even though we will wind up with a total of four columns this time:

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

  <TableRow>

    <TextView
      android:layout_marginLeft="4dip"
      android:layout_marginStart="4dip"
      android:text="@string/url" />

    <EditText
      android:id="@+id/entry"
      android:layout_span="3"
      android:inputType="text" />
  </TableRow>

  <TableRow>

    <Button
      android:id="@+id/cancel"
      android:layout_column="2"
      android:text="@string/cancel" />

    <Button
      android:id="@+id/ok"
      android:text="@string/ok" />
  </TableRow>

</TableLayout>
(from Containers/Sampler/app/src/main/res/layout/url_dialog_tl.xml)

The EditText in the first TableRow has android:layout_span="3", indicating that it should span to fill three columns. That, plus our one TextView, means that the first row is set up for four columns in total.

The first Button in the second TableRow has android:layout_column="2", indicating that it should go into the third column. The other Button will go into the next column (the fourth column in this case), and the first two columns are skipped. So, this row also is set up for four columns.

So, when android:stretchColumns="1" is applied, the extra space will be given to the “contents” of the second column:

Hey, What About ConstraintLayout?

In 2016, Google introduced ConstraintLayout, with a vision of it becoming the fourth major container and perhaps the default one that you would choose. ConstraintLayout has its benefits, to be certain. However, it requires the use of a library, and we have not yet covered how to attach libraries to an Android module.

So, we will discuss ConstraintLayout a bit later in the book.

Turning Back to RTL

In order for the “start”/“end” attributes to work, you need to have android:supportsRtl="true" in your <application> element in your manifest. Most newly-created projects will have this attribute already set for you by the new-project wizard.

To see how your app behaves with RTL — without having to learn Arabic or Hebrew, if you are not literate in those languages — you can force Android to use RTL layout rules with any language on Android 4.2+ devices. To do this, go into the Settings app of the device or emulator and choose “Developer options”. In there, scroll down to the “Force RTL layout direction” item. By default, this is turned off, and so layout direction is determined by the user’s chosen language:

Developer Options in Settings, Normal Mode
Figure 138: Developer Options in Settings, Normal Mode

Tapping that switch uses RTL layout rules — with “start” referring to the right and “end” referring to the left — for all languages:

Developer Options in Settings, Forced-RTL Mode
Figure 139: Developer Options in Settings, Forced-RTL Mode

As a reminder, if “Developer options” is not in the list of Settings categories, go into the “About device” category, find the build number item, and tap on it seven times. This will enable “Developer options” back on the main list of Settings categories.