Custom Controls / Layouts / TableLayout

Scrolling Table – Part 3

In Scrolling Table – Part 2 we looked at a technique for getting a scrolling table with a static header row by using a custom control to link the column widths of two tables, but there was no support for cells which span multiple columns. In this final part we’ll add column spanning.

We need to modify the onLayout() method of our ScrollingTable class. In the first loop, which determines the column width from the body table, we’ll simply ignore the width when there is a span of anything but 1. If we apply a column span to the same cell in every row, TableLayout will get confused, and the column widths calculation will get messed up. But this would be the case in a standard TableLayout not just this solution. So it iit is safe assumption that each column will have an explicit width set in at least one of the rows. In the second loop, which applies the previously determined width to the header table, will sum the column widths if the header contains a cell which spans multiple columns:

[java] @Override
protected void onLayout( boolean changed, int l, int t, int r, int b )
{
super.onLayout( changed, l, t, r, b );
List colWidths = new LinkedList();

TableLayout header = (TableLayout) findViewById( R.id.HeaderTable );
TableLayout body = (TableLayout) findViewById( R.id.BodyTable );

for ( int rownum = 0; rownum < body.getChildCount(); rownum++ ) { TableRow row = (TableRow) body.getChildAt( rownum ); for ( int cellnum = 0; cellnum < row.getChildCount(); cellnum++ ) { View cell = row.getChildAt( cellnum ); TableRow.LayoutParams params = (TableRow.LayoutParams)cell.getLayoutParams(); Integer cellWidth = params.span == 1 ? cell.getWidth() : 0; if ( colWidths.size() <= cellnum ) { colWidths.add( cellWidth ); } else { Integer current = colWidths.get( cellnum ); if( cellWidth > current )
{
colWidths.remove( cellnum );
colWidths.add( cellnum, cellWidth );
}
}
}
}

for ( int rownum = 0; rownum < header.getChildCount(); rownum++ ) { TableRow row = (TableRow) header.getChildAt( rownum ); for ( int cellnum = 0; cellnum < row.getChildCount(); cellnum++ ) { View cell = row.getChildAt( cellnum ); TableRow.LayoutParams params = (TableRow.LayoutParams)cell.getLayoutParams(); params.width = 0; for( int span = 0; span < params.span; span++ ) { params.width += colWidths.get( cellnum + span ); } } } } [/java] This will now handle spans because of the changes to lines 42-45. If you need to use a span in the header table, then you will need to add one dummy line of 0dp high items below it otherwise TableLayout will get confused and not layout the columns correctly. This is because of how TableLayout calculates the column widths, and nothing to do with how we are linking the column widths of the two tables together.

We can change out layout to show this working:

[xml]




























.
.
.









[/xml]

If we now run this, we can see the correct span behaviour:

Scrolling Table with column spans
Scrolling Table with column spans

So there we have it – a Scrolling Table where the body cells can scroll independently from the header, while the columns all line up. While this has certainly solved the problem that we set out to solve it is only really useful in cases where we have a relatively small number of rows. Without our core requirement to have a header row, then it should be clear that if the number of rows grows above a relatively small number, then a ListView would be much more efficient. The same logic applies here. Possibly a future article will cover how to achieve the same result using a ListView instead of a TableLayout.

It is worth mentioning that, for the sake of clarity, I have omitted much of the error checking needed to use this code in a production context. I would strongly suggest adding some robustness to this before using it in a real project.

The final source code or this article can be found here.

© 2011, Mark Allison. All rights reserved.

Copyright © 2011 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.

10 Comments

  1. Very well explained and ingeniously created. Great work.
    With TextView this is possible as it’s height can be set to zero as and when required. However, if a table row consists of different controls (views) such as CheckBox, Spinner and EditText then I think it is not possible, because we cannot set the height of Spinner to zero. I may be wrong.
    Can you solve this? I have following specific requirement
    —————————————————————————
    Select All (CheckBox) Vendor Product Quantity —–>Stable
    —————————————————————————
    CheckBox Spinner Spinner EditText —->Scroll

    Thank you, in advance.

    Nitin

    1. Hi Nitin,

      Sorry, I simply don’t have the bandwidth to provide developer support on this site.

      I would suggest trying stackoverflow.com. When I have any spare time I sometimes hang out there.

      Regards,

      Mark

  2. Hi,
    Thanks a million for the article!
    Isn’t it better to use tag instead of id in ScrollingTable (here: TableLayout header = (TableLayout) findViewById( R.id.HeaderTable );)?
    IDs should be unique whereas tags don’t so it’s easier to use your table on many screens. Am I right?

    1. But there *is* only one control with an ID of HeaderTable in this example. It is therefore unique. So where’s the problem?

      1. In this example it’s correct, but if you want to make it more general – the problem is when you want to have the ScrollingTable on many layouts with different headers on them (e.g Activity1 with one table and Activity2 with the other). Aren’t tags better then?

        1. As you say “In this example it’s correct”. What more do you expect?

          I cannot design examples to work outside the context of the examples themselves. If people want to use any of the examples from the blog they may require adaptation to meet their own specific requirements.

  3. Great work. I’m currently using it in one of my android applications, but I’ve changed a little bit the onLayout() method since there is a small logical error regarding the cell span property. I’m not very good in English, but I’ll try to explain it using an example. If you have table with two rows – the first one contains three cells with the following widths(50,80,130) and the second one contains two cells, the first one spanned two cells thus having widths(130,130) you will end up with header cells widths(50,130,130) and they should be(50,80,130). The onLayout() method compares the first row second cell width to the second row second cell width, but this is not correct due to the span. The second row second cell should be compared to the first row third cell. I hope that I was helpful. If you have any questions, feel free to contact me.

  4. Hi this is very useful tutorial
    I had question, suppose I had lot of column so can we apply any horizontal scrolling and displaying all column data????

Leave a Reply to Mark Allison Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.