Scrolling Table – Part 1

I recently had a requirement to create a table layout which had a header row at the top. On the face of it this is relatively easy using TableLayout, but there was a further requirement: The header row should remain static while the data rows scroll. The problem here is that the standard TableLayout does not support a scrolling body area, so how can we achieve this?

Let’s start by creating a 2.3.3 project named “ScrollingTable” with a package name of “com.stylingandroid.ScrollingTable” and an Activity named “ScrollingTableActivity”.

Let’s add a drawable to res/drawable/border.xml:

<?xml version="1.0" encoding="utf-8"?>
<inset xmlns:android="http://schemas.android.com/apk/res/android"
	android:insetLeft="@dimen/border_offset" 
	android:insetTop="@dimen/border_offset">
	<shape android:shape="rectangle">
		<stroke android:width="@dimen/border_width" 
			android:color="#7F000000" />
		<solid android:color="@android:color/transparent" />
	</shape>
</inset>

Then some dimensions to res/values/dimensions.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <dimen name="border_width">0.5dp</dimen>
    <dimen name="border_offset">-0.5dp</dimen>
</resources>

And some styles to res/values/styles.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android">
	<style name="HeaderRow">
		<item name="android:background">#7FAF7F</item>
		<item name="android:layout_width">wrap_content</item>
		<item name="android:layout_height">wrap_content</item>
	</style>
	<style name="HeaderText">
		<item name="android:textColor">#0F7F0F</item>
		<item name="android:shadowColor">#AFFFFFFF</item>
		<item name="android:shadowDx">1</item>
		<item name="android:shadowDy">1</item>
		<item name="android:shadowRadius">1.0</item>
		<item name="android:padding">5dp</item>
		<item name="android:gravity">center</item>
		<item name="android:textSize">14sp</item>
		<item name="android:textStyle">bold</item>
		<item name="android:background">@drawable/border</item>
		<item name="android:layout_width">wrap_content</item>
		<item name="android:layout_height">wrap_content</item>
	</style>
	<style name="BodyRow">
		<item name="android:background">#DFDFDF</item>
		<item name="android:layout_width">wrap_content</item>
		<item name="android:layout_height">wrap_content</item>
	</style>
	<style name="BodyText">
		<item name="android:textColor">#7F7F7F</item>
		<item name="android:shadowColor">#AFFFFFFF</item>
		<item name="android:shadowDx">1</item>
		<item name="android:shadowDy">1</item>
		<item name="android:shadowRadius">1.0</item>
		<item name="android:padding">5dp</item>
		<item name="android:gravity">center</item>
		<item name="android:textSize">14sp</item>
		<item name="android:textStyle">bold</item>
		<item name="android:background">@drawable/border</item>
		<item name="android:layout_width">wrap_content</item>
		<item name="android:layout_height">wrap_content</item>
	</style>
</resources>

To try and solve this we could create two tables, one outside the ScrollView which contains the header, and the other inside the ScrollView which contains the body:

<?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="match_parent">
	<TableLayout android:layout_height="wrap_content"
		android:layout_width="match_parent">
		<TableRow style="@style/HeaderRow">
			<TextView android:text="Col 1" 
				style="@style/HeaderText" />
			<TextView android:text="Col 2" 
				style="@style/HeaderText"
				android:layout_weight="1" />
			<TextView android:text="Col 3" 
				style="@style/HeaderText" />
			<TextView android:text="Col 4" 
				style="@style/HeaderText" />
		</TableRow>
	</TableLayout>
	<ScrollView android:layout_width="match_parent"
		android:layout_height="200dp">
		<TableLayout android:layout_height="wrap_content"
			android:layout_width="match_parent">
			<TableRow style="@style/BodyRow">
				<TextView android:text="Cell 1,1" 
					style="@style/BodyText" />
				<TextView android:text="Cell 1,2" 
					style="@style/BodyText"
					android:layout_weight="1" />
				<TextView android:text="Cell 1,3" 
					style="@style/BodyText" />
				<TextView android:text="Cell 1,4" 
					style="@style/BodyText" />
			</TableRow>
			<TableRow style="@style/BodyRow">
				<TextView android:text="Cell 2,1" 
					style="@style/BodyText" />
				<TextView android:text="Cell 2,2" 
					style="@style/BodyText"
					android:layout_weight="1" />
				<TextView android:text="Cell 2,3" 
					style="@style/BodyText" />
				<TextView android:text="Cell 2,4" 
					style="@style/BodyText" />
			</TableRow>
			<TableRow style="@style/BodyRow">
				<TextView android:text="Cell 3,1" 
					style="@style/BodyText" />
				<TextView android:text="Cell 3,2" 
					style="@style/BodyText"
					android:layout_weight="1" />
				<TextView android:text="Cell 3,3" 
					style="@style/BodyText" />
				<TextView android:text="Cell 3,4" 
					style="@style/BodyText" />
			</TableRow>
			<TableRow style="@style/BodyRow">
				<TextView android:text="Cell 4,1" 
					style="@style/BodyText" />
				<TextView android:text="Cell 4,2" 
					style="@style/BodyText"
					android:layout_weight="1" />
				<TextView android:text="Cell 4,3" 
					style="@style/BodyText" />
				<TextView android:text="Cell 4,4" 
					style="@style/BodyText" />
			</TableRow>
			<TableRow style="@style/BodyRow">
				<TextView android:text="Cell 5,1" 
					style="@style/BodyText" />
				<TextView android:text="Cell 5,2" 
					style="@style/BodyText"
					android:layout_weight="1" />
				<TextView android:text="Cell 5,3" 
					style="@style/BodyText" />
				<TextView android:text="Cell 5,4" 
					style="@style/BodyText" />
			</TableRow>
			<TableRow style="@style/BodyRow">
				<TextView android:text="Cell 6,1" 
					style="@style/BodyText" />
				<TextView android:text="Cell 6,2" 
					style="@style/BodyText"
					android:layout_weight="1" />
				<TextView android:text="Cell 6,3" 
					style="@style/BodyText" />
				<TextView android:text="Cell 6,4" 
					style="@style/BodyText" />
			</TableRow>
			<TableRow style="@style/BodyRow">
				<TextView android:text="Cell 7,1" 
					style="@style/BodyText" />
				<TextView android:text="Cell 7,2" 
					style="@style/BodyText"
					android:layout_weight="1" />
				<TextView android:text="Cell 7,3" 
					style="@style/BodyText" />
				<TextView android:text="Cell 7,4" 
					style="@style/BodyText" />
			</TableRow>
			<TableRow style="@style/BodyRow">
				<TextView android:text="Cell 8,1" 
					style="@style/BodyText" />
				<TextView android:text="Cell 8,2" 
					style="@style/BodyText"
					android:layout_weight="1" />
				<TextView android:text="Cell 8,3" 
					style="@style/BodyText" />
				<TextView android:text="Cell 8,4" 
					style="@style/BodyText" />
			</TableRow>
		</TableLayout>
	</ScrollView>
</LinearLayout>

if we run this, we can see that we get the scrolling behaviour that we require, but the column widths of the body cells do not match those of the header cells:

The column widths problem

The column widths problem

One solution that we could use is to duplicate the entire table, and effectively hide much of it by setting the heights of some of the rows to 0dp. This will effectively hide some of the rows, while maintaining their content which enables TableLayout to correctly calculate the cell widths:

<?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="match_parent">
	<TableLayout android:layout_height="wrap_content"
		android:layout_width="match_parent">
		<TableRow style="@style/HeaderRow">
			<TextView android:text="Col 1" 
				style="@style/HeaderText" />
			<TextView android:text="Col 2" 
				style="@style/HeaderText"
				android:layout_weight="1" />
			<TextView android:text="Col 3" 
				style="@style/HeaderText" />
			<TextView android:text="Col 4" 
				style="@style/HeaderText" />
		</TableRow>
		<TableRow style="@style/BodyRow">
			<TextView android:text="Cell 1,1" 
				android:layout_height="0dp"
				style="@style/BodyText" />
			<TextView android:text="Cell 1,2" 
				android:layout_height="0dp"
				style="@style/BodyText"
				android:layout_weight="1" />
			<TextView android:text="Cell 1,3" 
				android:layout_height="0dp"
				style="@style/BodyText" />
			<TextView android:text="Cell 1,4" 
				android:layout_height="0dp"
				style="@style/BodyText" />
		</TableRow>
		.
		.
		.
		<TableRow style="@style/BodyRow">
			<TextView android:text="Cell 8,1" 
				android:layout_height="0dp"
				style="@style/BodyText" />
			<TextView android:text="Cell 8,2" 
				android:layout_height="0dp"
				style="@style/BodyText"
				android:layout_weight="1" />
			<TextView android:text="Cell 8,3" 
				android:layout_height="0dp"
				style="@style/BodyText" />
			<TextView android:text="Cell 8,4" 
				android:layout_height="0dp"
				style="@style/BodyText" />
		</TableRow>
	</TableLayout>
	<ScrollView android:layout_width="match_parent"
		android:layout_height="200dp">
		<TableLayout android:layout_height="wrap_content"
			android:layout_width="match_parent">
			<TableRow style="@style/HeaderRow">
				<TextView android:text="Col 1" 
					android:layout_height="0dp"
					style="@style/HeaderText" />
				<TextView android:text="Col 2" 
					android:layout_height="0dp"
					style="@style/HeaderText"
					android:layout_weight="1" />
				<TextView android:text="Col 3" 
					android:layout_height="0dp"
					style="@style/HeaderText" />
				<TextView android:text="Col 4" 
					android:layout_height="0dp"
					style="@style/HeaderText" />
		</TableRow>
			<TableRow style="@style/BodyRow">
				<TextView android:text="Cell 1,1" 
					style="@style/BodyText" />
				<TextView android:text="Cell 1,2" 
					style="@style/BodyText"
					android:layout_weight="1" />
				<TextView android:text="Cell 1,3" 
					style="@style/BodyText" />
				<TextView android:text="Cell 1,4" 
					style="@style/BodyText" />
			</TableRow>
			.
			.
			.
			<TableRow style="@style/BodyRow">
				<TextView android:text="Cell 8,1" 
					style="@style/BodyText" />
				<TextView android:text="Cell 8,2" 
					style="@style/BodyText"
					android:layout_weight="1" />
				<TextView android:text="Cell 8,3" 
					style="@style/BodyText" />
				<TextView android:text="Cell 8,4" 
					style="@style/BodyText" />
			</TableRow>
		</TableLayout>
	</ScrollView>
</LinearLayout>

If we run this, we can see that it gives us the scrolling behaviour that we require, and also lines up the columns correctly:

It works!

It works!

Although this does what we require, it is pretty inefficient because we are duplicating the entire table. It works where we have a small number of rows, but is rather hacky, and could run in to memory problems where we have a large number of rows.

After coming up with this approach, I found an article on the Android Adventures blog which offers a similar approach to mine. It is slightly more efficient because it does not fully duplicate the data, but is slightly less flexible as a result because it assumes that the header titles will always be longer than the text in the body cells.

In the second part of this article, we’ll look at an alternative approach which is a little more complex to implement, but should give us the behaviour that we require while being a little better behaved in terms of memory usage.

The source code for this article can be found here.

© 2011, Mark Allison. All rights reserved. This article originally appeared on Styling Android.

Portions of this page are modifications based on work created and shared by Google and used according to terms described in the Creative Commons 3.0 Attribution License

Send the author to the moon!

Creative Commons License
Scrolling Table – Part 1 by Styling Android, unless otherwise expressly stated, is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. Terms and conditions beyond the scope of this license may be available at blog.stylingandroid.com.

Tags: ,

6 Responses to “Scrolling Table – Part 1”

  1. Srihari says:

    great work Mark. Thanks

  2. wow why didn’t I know about this blog.

    Starting from helloworld today, will catch u soon.

  3. Par5Eagles says:

    Very nice, works great. Is this type of “scrolloing” solution only possible on android 2.3 & greater? Obviously the code breaks on 2.1 and 2.2…

    • Mark Allison says:

      The only possible reason for the code not working on earlier versions of Android is that we created a 2.3.3 project and haven’t set a “minSdk” attribute in the Manifest. The techniques here are not specific to an particular version of Android, and should be backwardly compatible.

  4. Lorenzo says:

    And if i want to populate cells with child of an xml table?? Thank you you’re a boss!! :)

  5. Fran says:

    Hi,
    The black 3D effect below the header line and the bottom of the main body that appears when scrolling hides the content, how is it made? Does android it automatically?
    Thanks

Leave a Reply