Advertisement

Android User Interface Design: Table Layouts

This Cyber Monday Tuts+ courses will be reduced to just $3 (usually $15). Don't miss out.

This post is part of a series called Android User Interface Design.
Android UI Fundamentals Challenge: RelativeLayout
Android User Interface Design: Frame Layouts

Table layouts can be used for displaying tabular data or neatly aligning screen contents in a way similar to an HTML table on a web page. Learn how to create them with layout XML files and through code.

Understanding layouts is important for good Android application design. In this tutorial, you learn all about table layouts, which organize user interface controls, or widgets, on the screen in neatly defined rows and columns. When used correctly, table layouts can be the powerful layout paradigm upon which Android applications can design their screens or display tabular data.

What Is A Table Layout?

A table layout is exactly what you might expect: a grid of made up of rows and columns, where a cell can display a view control. From a user interface design perspective, a TableLayout is comprised of TableRow controls—one for each row in your table. The contents of a TableRow are simply the view controls that will go in each “cell” of the table grid.

The appearance of a TableLayout is governed by several additional rules. First, the number of columns of the entire table matches the number of columns in the row with the most columns. Second, the width of each column is defined as the width of the widest content in the column. The TableLayout’s child rows and cells layout_width attributes are always MATCH_PARENT -- although they can be put in an XML file, the actual value can't be overridden. The TableLayout’s layout_height of a cell can be defined, but a TableRow attribute for layout_height is always WRAP_CONTENT. Cells can span columns, but not rows. This is done through the layout_span attribute of the child view of a TableRow. A cell is a single child view within a TableRow. If you want a more complex cell with multiple views, use a layout view to encapsulate the other views.

That said, some rules can be modified. Columns can be marked as stretchable, which means that the width can expand to the size of the parent container. Columns can also be marked as shrinkable, which means that they can be reduced in width so the whole row will fit in the space provided by the parent container. You can also collapse an entire column.

For the complete documention for table layouts, see the Android SDK documentation for the TableLayout class. The associated XML attributes for use in XML resources are also defined in the documentation.

Designing a Simple Table Layout

Layouts are best explained by example, and table layouts are no different. Let’s say we want to design a screen that shows the extended weather forecast. A table layout might be a good choice for organizing this information:

  • In the first TableRow, we can display a title for the screen.
  • In the second TableRow, we can display the dates in a familiar calendar-like format.
  • In the third TableRow, we can display a Daily High temperature information.
  • In the fourth TableRow, we can display a Daily Low temperature information.
  • In the fifth TableRow, we can display graphics to identify the weather conditions, such as rain, snow, sun, or cloudy with a chance of meatballs.

This first figure shows an early look at the table inside the layout editor:

Android SDK - Table Layouts - Figure 1

Defining an XML Layout Resource with a Table Layout

The most convenient and maintainable way to design application user interfaces is by creating XML layout resources. This method greatly simplifies the UI design process, moving much of the static creation and layout of user interface controls and definition of control attributes, to the XML, instead of littering the code.

XML layout resources must be stored in the /res/layout project directory hierarchy. Let’s take a look at the table layout introduced in the previous section. This layout resource file, aptly named /res/layout/table.xml, is defined in XML as follows:

 
<?xml version="1.0" encoding="utf-8"?>
<TableLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/tableLayout1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:shrinkColumns="*"
    android:stretchColumns="*">
    <TableRow
        android:id="@+id/tableRow4"
        android:layout_height="wrap_content"
        android:layout_width="match_parent"
        android:gravity="center_horizontal">
        <TextView
            android:id="@+id/textView9"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textStyle="bold"
            android:typeface="serif"
            android:textSize="18dp"
            android:text="Weather Table"
            android:gravity="center"
            android:layout_span="6"></TextView>
    </TableRow>
    <TableRow
        android:id="@+id/tableRow1"
        android:layout_height="wrap_content"
        android:layout_width="match_parent">
        <TextView
            android:id="@+id/TextView04"
            android:text=""></TextView>
        <TextView
            android:id="@+id/TextView04"
            android:text="Feb 7"
            android:textStyle="bold"
            android:typeface="serif"></TextView>
        <TextView
            android:id="@+id/TextView03"
            android:text="Feb 8"
            android:textStyle="bold"
            android:typeface="serif"></TextView>
        <TextView
            android:id="@+id/TextView02"
            android:text="Feb 9"
            android:textStyle="bold"
            android:typeface="serif"></TextView>
        <TextView
            android:id="@+id/TextView01"
            android:text="Feb 10"
            android:textStyle="bold"
            android:typeface="serif"></TextView>
        <TextView
            android:text="Feb 11"
            android:id="@+id/textView1"
            android:textStyle="bold"
            android:typeface="serif"></TextView>
    </TableRow>
    <TableRow
        android:layout_height="wrap_content"
        android:id="@+id/tableRow2"
        android:layout_width="match_parent">
        <TextView
            android:text="Day High"
            android:id="@+id/textView2"
            android:textStyle="bold"></TextView>
        <TextView
            android:id="@+id/textView3"
            android:text="28°F"
            android:gravity="center_horizontal"></TextView>
        <TextView
            android:text="26°F"
            android:id="@+id/textView4"
            android:gravity="center_horizontal"></TextView>
        <TextView
            android:text="23°F"
            android:id="@+id/textView5"
            android:gravity="center_horizontal"></TextView>
        <TextView
            android:text="17°F"
            android:id="@+id/textView6"
            android:gravity="center_horizontal"></TextView>
        <TextView
            android:text="19°F"
            android:id="@+id/textView7"
            android:gravity="center_horizontal"></TextView>
    </TableRow>
    <TableRow
        android:layout_height="wrap_content"
        android:id="@+id/tableRow2"
        android:layout_width="match_parent">
        <TextView
            android:text="Day Low"
            android:id="@+id/textView2"
            android:textStyle="bold"></TextView>
        <TextView
            android:text="15°F"
            android:id="@+id/textView3"
            android:gravity="center_horizontal"></TextView>
        <TextView
            android:text="14°F"
            android:id="@+id/textView4"
            android:gravity="center_horizontal"></TextView>
        <TextView
            android:text="3°F"
            android:id="@+id/textView5"
            android:gravity="center_horizontal"></TextView>
        <TextView
            android:text="5°F"
            android:id="@+id/textView6"
            android:gravity="center_horizontal"></TextView>
        <TextView
            android:text="6°F"
            android:id="@+id/textView7"
            android:gravity="center_horizontal"></TextView>
    </TableRow>
    <TableRow
        android:id="@+id/tableRow3"
        android:layout_height="wrap_content"
        android:layout_width="match_parent"
        android:gravity="center">
        <TextView
            android:id="@+id/textView8"
            android:text="Conditions"
            android:textStyle="bold"></TextView>
        <ImageView
            android:id="@+id/imageView1"
            android:src="@drawable/hot"></ImageView>
        <ImageView
            android:id="@+id/imageView2"
            android:src="@drawable/pt_cloud"></ImageView>
        <ImageView
            android:id="@+id/imageView3"
            android:src="@drawable/snow"></ImageView>
        <ImageView
            android:id="@+id/imageView4"
            android:src="@drawable/lt_snow"></ImageView>
        <ImageView
            android:id="@+id/imageView5"
            android:src="@drawable/pt_sun"></ImageView>
    </TableRow>
</TableLayout>

Recall that, from within the Activity, only a single line of code within the onCreate() method is necessary to load and display a layout resource on the screen. If the layout resource was stored in the /res/layout/table.xml file, that line of code would be:

 
setContentView(R.layout.table);

This table layout has all its columns set to both shrink and stretch by using a "*" in the value. If just certain columns should shrink or stretch, the values would be a comma seperated list (using 0-based indexes for columns).

The table now looks like the following to screenshots when in portrait and landscape mode.

Android SDK - Table Layouts - Figure 2 - Landscape
Android SDK - Table Layouts - Figure 2 - Portrait

Defining a Table Layout Programmatically

You can also programmatically create and configure table layouts in Java. This is done using the TableLayout and TableRow classes (android.widget.TableLayout and android.widget.TableRow). You’ll find the unique display parameters for each control in the TableLayout.LayoutParams and TableRow.LayoutParams classes. Also, the typical layout parameters (android.view.ViewGroup.LayoutParams), such as layout_height and layout_width, as well as margin parameters (ViewGroup.MarginLayoutParams), still apply to TableLayout and TableRow objects, but not necessarily table cells. For table cells (any View inside the TableRow), the width is always MATCH_PARENT. The height can be defined, but defaults to WRAP_CONTENT and need not be specified.
Instead of loading a layout resource directly using the setContentView() method as shown earlier, if you create a layout programmatically, you must instead build up the screen contents in Java and then supply a parent layout object which contains all the control contents to display as child views to the setContentView() method. In this case, your parent layout used would be the table layout created.

For example, the following code illustrates how to programmatically have an Activity instantiate a TableLayout layout parameters and reproduce the example shown earlier in XML:

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        TableLayout table = new TableLayout(this);

        table.setStretchAllColumns(true);
        table.setShrinkAllColumns(true);

        TableRow rowTitle = new TableRow(this);
        rowTitle.setGravity(Gravity.CENTER_HORIZONTAL);

        TableRow rowDayLabels = new TableRow(this);
        TableRow rowHighs = new TableRow(this);
        TableRow rowLows = new TableRow(this);
        TableRow rowConditions = new TableRow(this);
        rowConditions.setGravity(Gravity.CENTER);

        TextView empty = new TextView(this);

        // title column/row
        TextView title = new TextView(this);
        title.setText("Java Weather Table");

        title.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18);
        title.setGravity(Gravity.CENTER);
        title.setTypeface(Typeface.SERIF, Typeface.BOLD);

        TableRow.LayoutParams params = new TableRow.LayoutParams();
        params.span = 6;

        rowTitle.addView(title, params);

        // labels column
        TextView highsLabel = new TextView(this);
        highsLabel.setText("Day High");
        highsLabel.setTypeface(Typeface.DEFAULT_BOLD);

        TextView lowsLabel = new TextView(this);
        lowsLabel.setText("Day Low");
        lowsLabel.setTypeface(Typeface.DEFAULT_BOLD);

        TextView conditionsLabel = new TextView(this);
        conditionsLabel.setText("Conditions");
        conditionsLabel.setTypeface(Typeface.DEFAULT_BOLD);

        rowDayLabels.addView(empty);
        rowHighs.addView(highsLabel);
        rowLows.addView(lowsLabel);
        rowConditions.addView(conditionsLabel);

        // day 1 column
        TextView day1Label = new TextView(this);
        day1Label.setText("Feb 7");
        day1Label.setTypeface(Typeface.SERIF, Typeface.BOLD);

        TextView day1High = new TextView(this);
        day1High.setText("28°F");
        day1High.setGravity(Gravity.CENTER_HORIZONTAL);

        TextView day1Low = new TextView(this);
        day1Low.setText("15°F");
        day1Low.setGravity(Gravity.CENTER_HORIZONTAL);

        ImageView day1Conditions = new ImageView(this);
        day1Conditions.setImageResource(R.drawable.hot);

        rowDayLabels.addView(day1Label);
        rowHighs.addView(day1High);
        rowLows.addView(day1Low);
        rowConditions.addView(day1Conditions);

        // day2 column
        TextView day2Label = new TextView(this);
        day2Label.setText("Feb 8");
        day2Label.setTypeface(Typeface.SERIF, Typeface.BOLD);

        TextView day2High = new TextView(this);
        day2High.setText("26°F");
        day2High.setGravity(Gravity.CENTER_HORIZONTAL);

        TextView day2Low = new TextView(this);
        day2Low.setText("14°F");
        day2Low.setGravity(Gravity.CENTER_HORIZONTAL);

        ImageView day2Conditions = new ImageView(this);
        day2Conditions.setImageResource(R.drawable.pt_cloud);

        rowDayLabels.addView(day2Label);
        rowHighs.addView(day2High);
        rowLows.addView(day2Low);
        rowConditions.addView(day2Conditions);

        // day3 column
        TextView day3Label = new TextView(this);
        day3Label.setText("Feb 9");
        day3Label.setTypeface(Typeface.SERIF, Typeface.BOLD);

        TextView day3High = new TextView(this);
        day3High.setText("23°F");
        day3High.setGravity(Gravity.CENTER_HORIZONTAL);

        TextView day3Low = new TextView(this);
        day3Low.setText("3°F");
        day3Low.setGravity(Gravity.CENTER_HORIZONTAL);

        ImageView day3Conditions = new ImageView(this);
        day3Conditions.setImageResource(R.drawable.snow);

        rowDayLabels.addView(day3Label);
        rowHighs.addView(day3High);
        rowLows.addView(day3Low);
        rowConditions.addView(day3Conditions);

        // day4 column
        TextView day4Label = new TextView(this);
        day4Label.setText("Feb 10");
        day4Label.setTypeface(Typeface.SERIF, Typeface.BOLD);

        TextView day4High = new TextView(this);
        day4High.setText("17°F");
        day4High.setGravity(Gravity.CENTER_HORIZONTAL);

        TextView day4Low = new TextView(this);
        day4Low.setText("5°F");
        day4Low.setGravity(Gravity.CENTER_HORIZONTAL);

        ImageView day4Conditions = new ImageView(this);
        day4Conditions.setImageResource(R.drawable.lt_snow);

        rowDayLabels.addView(day4Label);
        rowHighs.addView(day4High);
        rowLows.addView(day4Low);
        rowConditions.addView(day4Conditions);

        // day5 column
        TextView day5Label = new TextView(this);
        day5Label.setText("Feb 11");
        day5Label.setTypeface(Typeface.SERIF, Typeface.BOLD);

        TextView day5High = new TextView(this);
        day5High.setText("19°F");
        day5High.setGravity(Gravity.CENTER_HORIZONTAL);

        TextView day5Low = new TextView(this);
        day5Low.setText("6°F");
        day5Low.setGravity(Gravity.CENTER_HORIZONTAL);

        ImageView day5Conditions = new ImageView(this);
        day5Conditions.setImageResource(R.drawable.pt_sun);

        rowDayLabels.addView(day5Label);
        rowHighs.addView(day5High);
        rowLows.addView(day5Low);
        rowConditions.addView(day5Conditions);

        table.addView(rowTitle);
        table.addView(rowDayLabels);
        table.addView(rowHighs);
        table.addView(rowLows);
        table.addView(rowConditions);

        setContentView(table);

    }

Let’s take a closer look at the Java code listing above. First we create the TableLayout control and set the shrinkable and stretchable attributes to true for all columns using the setStretchAllColumns() and setShrinkAllColumns() methods. Next, we systematically create five TableRow. Each TableRow will contain view controls (TextView controls for the title, dates, highs and low data as well as ImageView controls for the weather condition graphics). You'll see how the column span is handled with the first TableRow. Specific columns of views are created, styled, and added, in order, to the appropriate TableRow using the addView() method. Each TableRow is added to the TableLayout control, in order, using the addView() method of the TableLayout. Finally, we load the TableLayout and display it on the screen using the setContentView() method.

As you can see, the code can rapidly grow in size as more controls are added to the screen. For organization and maintainability, defining and using layouts programmatically is best left for the odd case rather than the norm. Additionally, in a case like this, the data would typically be coming from some other source than strings we type in, so a loop may be more appropriate for many applications.

The results are shown in the following figure. As you can see, they are the same as the previous results -- as expected.

Android SDK - Table Layouts - Figure 3 - Portrait

TableLayout Concerns

Although table layouts can be used to design entire user interfaces, they usually aren't the best tool for doing so, as they are derived from LinearLayout and not the most efficient of layout controls. If you think about it, a TableLayout is little more than an organized set of nested LinearLayouts, and nesting layouts too deeply is generally discouraged for performance concerns. However, for data that is already in a format suitable for a table, such as spreadsheet data, table layout may be a reasonable choice.

Also, table layout data may vary based upon screen sizes and resolutions. It’s generally a good design practice to ensure you enable scrolling when displaying large quantities of data. For example, if the weather example used earlier also included a “write-up” of the conditions, this text might be one sentence or twenty sentences, so enabling vertical and/or horizontal scrolling would be prudent.

Conclusion

Android application user interfaces are defined using layouts, and table layouts are incredibly handy for displaying view data or controls in rows and columns. Using table layouts where they are appropriate can make many screen designs simpler and faster. However, keep in mind that TableLayout is derived from LinearLayout, and has many of the same performance limitations.

About the Authors

Mobile developers Lauren Darcey and Shane Conder have coauthored several books on Android development: an in-depth programming book entitled Android Wireless Application Development and Sams TeachYourself Android Application Development in 24 Hours. When not writing, they spend their time developing mobile software at their company and providing consulting services. They can be reached at via email to androidwirelessdev+mt@gmail.com, via their blog at androidbook.blogspot.com, and on Twitter @androidwireless.

Need More Help Writing Android Apps? Check out our Latest Books and Resources!

Buy Android Wireless Application Development, 2nd Edition  Buy Sam's Teach Yourself Android Application Development in 24 Hours  Mamlambo code at Code Canyon

Advertisement