Showing dynamic data in a TableLayout in Android

OVERVIEW

Showing tabular data is a standard requirement in a lot of applications. The best way is to use a TableLayout, which will automatically arrange the inner Views into columns across multiple TableRows. If you have complex requirements in which some rows may have different layouts, then its best not to use the TableLayout as it doesnt handle varying column counts very well.

In the sample application, we have a table which shows invoices. We have created classes for generating the invoice listing. In a real world case, this data would come from database. Here we populate the list with dummy data.

The TableLayout is wrapped in a ScrollView so that we can scroll the table up and down when the rows overflow the screen height.

FILES

MainActivity.java – the main Layout class

activity_main.xml – the main layout xml

dimens.xml  – define font sizes

InvoiceData.java – Data structure for an invoice

Invoices.java – Class to generate invoice list

 

SOURCE LISTING

AndroidManifest.xml

01<?xml version="1.0" encoding="utf-8"?>
02<manifest xmlns:android="http://schemas.android.com/apk/res/android"
03    package="table.test.com.dynamictable">
04 
05    <application
06        android:allowBackup="true"
07        android:icon="@mipmap/ic_launcher"
08        android:label="@string/app_name"
09        android:supportsRtl="true"
10        android:theme="@style/AppTheme">
11        <activity android:name=".MainActivity">
12            <intent-filter>
13                <action android:name="android.intent.action.MAIN" />
14 
15                <category android:name="android.intent.category.LAUNCHER" />
16            </intent-filter>
17        </activity>
18    </application>
19 
20</manifest>

dimens.xml

1<resources>
2    <!-- Default screen margins, per the Android Design guidelines. -->
3    <dimen name="activity_horizontal_margin">16dp</dimen>
4    <dimen name="activity_vertical_margin">16dp</dimen>
5    <dimen name="font_size_verysmall">10sp</dimen>
6    <dimen name="font_size_small">12sp</dimen>
7    <dimen name="font_size_medium">20sp</dimen>
8</resources>

activity_main.xml

01<?xml version="1.0" encoding="utf-8"?>
02<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
03    xmlns:tools="http://schemas.android.com/tools"
04    android:layout_width="match_parent"
05    android:layout_height="match_parent"
06    android:paddingBottom="@dimen/activity_vertical_margin"
07    android:paddingLeft="@dimen/activity_horizontal_margin"
08    android:paddingRight="@dimen/activity_horizontal_margin"
09    android:paddingTop="@dimen/activity_vertical_margin"
10    android:orientation="vertical"
11    android:id="@+id/invoices_layout"
12    tools:context="table.test.com.dynamictable.MainActivity">
13 
14 
15    <ScrollView
16        android:layout_width="match_parent"
17        android:layout_height="match_parent" >
18 
19        <TableLayout
20            android:id="@+id/tableInvoices"
21            android:layout_width="match_parent"
22            android:layout_height="match_parent"
23            android:padding="0dp"
24            android:stretchColumns="*"
25            >
26        </TableLayout>
27 
28    </ScrollView>
29 
30</LinearLayout>

InvoiceData.java

01package table.test.com.dynamictable;
02 
03import java.math.BigDecimal;
04import java.util.Date;
05 
06//
07// Data structure for invoices
08//
09 
10public class InvoiceData {
11 
12        public int id;
13        public int invoiceNumber;
14        public Date invoiceDate;
15        public String customerName;
16        public String customerAddress;
17        public BigDecimal invoiceAmount;
18        public BigDecimal amountDue;
19 
20 
21}

Invoices.java

01package table.test.com.dynamictable;
02 
03import java.math.BigDecimal;
04import java.util.Date;
05 
06/**
07 * Created by amit on 1/5/16.
08 */
09public class Invoices {
10 
11    public InvoiceData[] getInvoices() {
12        InvoiceData[] data = new InvoiceData[20];
13 
14        for(int i = 0; i < 20; i ++) {
15            InvoiceData row = new InvoiceData();
16            row.id = (i+1);
17            row.invoiceNumber = row.id;
18            row.amountDue = BigDecimal.valueOf(20.00 * i);
19            row.invoiceAmount = BigDecimal.valueOf(120.00 * (i+1));
20            row.invoiceDate = new Date();
21            row.customerName =  "Thomas John Beckett";
22            row.customerAddress = "1112, Hash Avenue, NYC";
23 
24            data[i] = row;
25        }
26        return data;
27 
28    }
29}

MainActivity.java

001package table.test.com.dynamictable;
002 
003import android.app.ProgressDialog;
004import android.graphics.Color;
005import android.os.AsyncTask;
006import android.support.v7.app.AppCompatActivity;
007import android.os.Bundle;
008import android.util.TypedValue;
009import android.view.Gravity;
010import android.view.View;
011import android.widget.FrameLayout;
012import android.widget.LinearLayout;
013import android.widget.TableLayout;
014import android.widget.TableRow;
015import android.widget.TextView;
016 
017import java.math.BigDecimal;
018import java.text.DecimalFormat;
019import java.text.SimpleDateFormat;
020 
021public class MainActivity extends AppCompatActivity {
022 
023    private TableLayout mTableLayout;
024    ProgressDialog mProgressBar;
025 
026    @Override
027    protected void onCreate(Bundle savedInstanceState) {
028        super.onCreate(savedInstanceState);
029        setContentView(R.layout.activity_main);
030 
031        mProgressBar = new ProgressDialog(this);
032 
033        // setup the table
034        mTableLayout = (TableLayout) findViewById(R.id.tableInvoices);
035    
036        mTableLayout.setStretchAllColumns(true);
037 
038 
039        startLoadData();
040    }
041 
042    public void startLoadData() {
043 
044        mProgressBar.setCancelable(false);
045        mProgressBar.setMessage("Fetching Invoices..");
046        mProgressBar.setProgressStyle(ProgressDialog.STYLE_SPINNER);
047        mProgressBar.show();
048        new LoadDataTask().execute(0);
049 
050    }
051 
052    public void loadData() {
053 
054        int leftRowMargin=0;
055        int topRowMargin=0;
056        int rightRowMargin=0;
057        int bottomRowMargin = 0;
058        int textSize = 0, smallTextSize =0, mediumTextSize = 0;
059 
060        textSize = (int) getResources().getDimension(R.dimen.font_size_verysmall);
061        smallTextSize = (int) getResources().getDimension(R.dimen.font_size_small);
062        mediumTextSize = (int) getResources().getDimension(R.dimen.font_size_medium);
063 
064        Invoices invoices = new Invoices();
065        InvoiceData[] data = invoices.getInvoices();
066 
067        SimpleDateFormat dateFormat = new SimpleDateFormat("dd MMM, yyyy");
068        DecimalFormat decimalFormat = new DecimalFormat("0.00");
069 
070        int rows = data.length;
071        getSupportActionBar().setTitle("Invoices (" + String.valueOf(rows) + ")");
072        TextView textSpacer = null;
073 
074        mTableLayout.removeAllViews();
075 
076        // -1 means heading row
077        for(int i = -1; i < rows; i ++) {
078            InvoiceData row = null;
079            if (i > -1)
080                row = data[i];
081            else {
082                textSpacer = new TextView(this);
083                textSpacer.setText("");
084 
085            }
086            // data columns
087            final TextView tv = new TextView(this);
088            tv.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT,
089                    TableRow.LayoutParams.WRAP_CONTENT));
090 
091            tv.setGravity(Gravity.LEFT);
092 
093            tv.setPadding(5, 15, 0, 15);
094            if (i == -1) {
095                tv.setText("Inv.#");
096                tv.setBackgroundColor(Color.parseColor("#f0f0f0"));
097                tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, smallTextSize);
098            } else {
099                tv.setBackgroundColor(Color.parseColor("#f8f8f8"));
100                tv.setText(String.valueOf(row.invoiceNumber));
101                tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
102            }
103 
104            final TextView tv2 = new TextView(this);
105            if (i == -1) {
106                tv2.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT,
107                        TableRow.LayoutParams.WRAP_CONTENT));
108                tv2.setTextSize(TypedValue.COMPLEX_UNIT_PX, smallTextSize);
109            } else {
110                tv2.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT,
111                        TableRow.LayoutParams.MATCH_PARENT));
112                tv2.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
113            }
114 
115            tv2.setGravity(Gravity.LEFT);
116 
117            tv2.setPadding(5, 15, 0, 15);
118            if (i == -1) {
119                tv2.setText("Date");
120                tv2.setBackgroundColor(Color.parseColor("#f7f7f7"));
121            }else {
122                tv2.setBackgroundColor(Color.parseColor("#ffffff"));
123                tv2.setTextColor(Color.parseColor("#000000"));
124                tv2.setText(dateFormat.format(row.invoiceDate));
125            }
126 
127 
128            final LinearLayout layCustomer = new LinearLayout(this);
129            layCustomer.setOrientation(LinearLayout.VERTICAL);
130            layCustomer.setPadding(0, 10, 0, 10);
131            layCustomer.setBackgroundColor(Color.parseColor("#f8f8f8"));
132 
133            final TextView tv3 = new TextView(this);
134            if (i == -1) {
135                tv3.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT,
136                        TableRow.LayoutParams.MATCH_PARENT));
137                tv3.setPadding(5, 5, 0, 5);
138                tv3.setTextSize(TypedValue.COMPLEX_UNIT_PX, smallTextSize);
139            } else {
140                tv3.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT,
141                        TableRow.LayoutParams.MATCH_PARENT));
142                tv3.setPadding(5, 0, 0, 5);
143                tv3.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
144            }
145 
146            tv3.setGravity(Gravity.TOP);
147 
148 
149            if (i == -1) {
150                tv3.setText("Customer");
151                tv3.setBackgroundColor(Color.parseColor("#f0f0f0"));
152            } else {
153                tv3.setBackgroundColor(Color.parseColor("#f8f8f8"));
154                tv3.setTextColor(Color.parseColor("#000000"));
155                tv3.setTextSize(TypedValue.COMPLEX_UNIT_PX, smallTextSize);
156                tv3.setText(row.customerName);
157            }
158            layCustomer.addView(tv3);
159 
160 
161            if (i > -1) {
162                final TextView tv3b = new TextView(this);
163                tv3b.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT,
164                        TableRow.LayoutParams.WRAP_CONTENT));
165 
166                tv3b.setGravity(Gravity.RIGHT);
167                tv3b.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
168                tv3b.setPadding(5, 1, 0, 5);
169                tv3b.setTextColor(Color.parseColor("#aaaaaa"));
170                tv3b.setBackgroundColor(Color.parseColor("#f8f8f8"));
171                tv3b.setText(row.customerAddress);
172                layCustomer.addView(tv3b);
173            }
174 
175            final LinearLayout layAmounts = new LinearLayout(this);
176            layAmounts.setOrientation(LinearLayout.VERTICAL);
177            layAmounts.setGravity(Gravity.RIGHT);
178            layAmounts.setPadding(0, 10, 0, 10);
179            layAmounts.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT,
180                    TableRow.LayoutParams.MATCH_PARENT));
181 
182 
183 
184            final TextView tv4 = new TextView(this);
185            if (i == -1) {
186                tv4.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT,
187                        TableRow.LayoutParams.MATCH_PARENT));
188                tv4.setPadding(5, 5, 1, 5);
189                layAmounts.setBackgroundColor(Color.parseColor("#f7f7f7"));
190            } else {
191                tv4.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT,
192                        TableRow.LayoutParams.WRAP_CONTENT));
193                tv4.setPadding(5, 0, 1, 5);
194                layAmounts.setBackgroundColor(Color.parseColor("#ffffff"));
195            }
196 
197            tv4.setGravity(Gravity.RIGHT);
198 
199            if (i == -1) {
200                tv4.setText("Inv.Amount");
201                tv4.setBackgroundColor(Color.parseColor("#f7f7f7"));
202                tv4.setTextSize(TypedValue.COMPLEX_UNIT_PX, smallTextSize);
203            } else {
204                tv4.setBackgroundColor(Color.parseColor("#ffffff"));
205                tv4.setTextColor(Color.parseColor("#000000"));
206                tv4.setText(decimalFormat.format(row.invoiceAmount));
207                tv4.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
208            }
209 
210            layAmounts.addView(tv4);
211 
212 
213            if (i > -1) {
214                final TextView tv4b = new TextView(this);
215                tv4b.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT,
216                        TableRow.LayoutParams.WRAP_CONTENT));
217 
218                tv4b.setGravity(Gravity.RIGHT);
219                tv4b.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
220                tv4b.setPadding(2, 2, 1, 5);
221                tv4b.setTextColor(Color.parseColor("#00afff"));
222                tv4b.setBackgroundColor(Color.parseColor("#ffffff"));
223 
224                String due = "";
225                if (row.amountDue.compareTo(new BigDecimal(0.01)) == 1) {
226                    due = "Due:" + decimalFormat.format(row.amountDue);
227                    due = due.trim();
228                }
229                tv4b.setText(due);
230                layAmounts.addView(tv4b);
231            }
232 
233 
234            // add table row
235            final TableRow tr = new TableRow(this);
236            tr.setId(i + 1);
237            TableLayout.LayoutParams trParams = new TableLayout.LayoutParams(TableLayout.LayoutParams.MATCH_PARENT,
238                    TableLayout.LayoutParams.WRAP_CONTENT);
239            trParams.setMargins(leftRowMargin, topRowMargin, rightRowMargin, bottomRowMargin);
240            tr.setPadding(0,0,0,0);
241            tr.setLayoutParams(trParams);
242 
243 
244 
245            tr.addView(tv);
246            tr.addView(tv2);
247            tr.addView(layCustomer);
248            tr.addView(layAmounts);
249 
250            if (i > -1) {
251 
252                tr.setOnClickListener(new View.OnClickListener() {
253                    public void onClick(View v) {
254                        TableRow tr = (TableRow) v;
255                        //do whatever action is needed
256 
257                    }
258                });
259 
260 
261            }
262            mTableLayout.addView(tr, trParams);
263 
264            if (i > -1) {
265 
266                // add separator row
267                final TableRow trSep = new TableRow(this);
268                TableLayout.LayoutParams trParamsSep = new TableLayout.LayoutParams(TableLayout.LayoutParams.MATCH_PARENT,
269                        TableLayout.LayoutParams.WRAP_CONTENT);
270                trParamsSep.setMargins(leftRowMargin, topRowMargin, rightRowMargin, bottomRowMargin);
271 
272                trSep.setLayoutParams(trParamsSep);
273                TextView tvSep = new TextView(this);
274                TableRow.LayoutParams tvSepLay = new TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT,
275                        TableRow.LayoutParams.WRAP_CONTENT);
276                tvSepLay.span = 4;
277                tvSep.setLayoutParams(tvSepLay);
278                tvSep.setBackgroundColor(Color.parseColor("#d9d9d9"));
279                tvSep.setHeight(1);
280 
281                trSep.addView(tvSep);
282                mTableLayout.addView(trSep, trParamsSep);
283            }
284 
285 
286        }
287    }
288 
289    //////////////////////////////////////////////////////////////////////////////
290 
291    //
292    // The params are dummy and not used
293    //
294    class LoadDataTask extends AsyncTask<Integer, Integer, String> {
295        @Override
296        protected String doInBackground(Integer... params) {
297 
298            try {
299                Thread.sleep(2000);
300 
301            } catch (InterruptedException e) {
302                e.printStackTrace();
303            }
304 
305            return "Task Completed.";
306        }
307        @Override
308        protected void onPostExecute(String result) {
309            mProgressBar.hide();
310            loadData();
311        }
312        @Override
313        protected void onPreExecute() {
314        }
315        @Override
316        protected void onProgressUpdate(Integer... values) {
317 
318        }
319    }
320 
321}

 

 

SCREENSHOT

Screenshot_2016-05-01-13-53-31

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

EXPLANATION

The core logic of generating the table is in the loadData() function. A single loop iterates through the invoice data and creates columns and rows to fill up the table. The first row is for the heading, so the loop runs from -1 instead of zero. The UI display of the header columns is slightly different than the data columns, which is why there is a check before creating the View for each column.

For the Customer column we are using a LinearLayout comprising of two TextViews – one for the customer name and the second for the customer address. An alternative way would have been to do just use a single TextView and assign it both the name and addresses, separate by a newline character “\r\n”. In this case, however, we wanted the address to look different than the name, so its better to use a different View for the address.

For each TableRow that is created, we add a click listener, so that you can detect when a column within a row has been tapped.

We use another TableRow to act as a separator between two data TableRows. Note the use of the span property. We set it to the number of columns in the data TableRow as there is only one column being added to the separator TableRow.

 

 

 

19 Comments

    • @vinod. Sorting can be done in the array using a CustomComparator class.
      But in a real world case, the data will come from a database, so sorting should happen at the database level.

  1. @Umar Fadil,

    You can add swipe gesture detection to the table for upswipe and downswipe. You have to first add UIScrollViewDelegate declaration to the ViewController,

    Example code:

    let upSwipe = UISwipeGestureRecognizer(target: self, action: Selector(“swipeUp:”))
    let downSwipe = UISwipeGestureRecognizer(target: self, action: Selector(“swipeDown:”))

    upSwipe.direction = .Up
    downSwipe.direction = .Down

    tableView.addGestureRecognizer(upSwipe)
    tableView.addGestureRecognizer(downSwipe)

    To detect the swipe, you hook the

    scrollViewDidScroll(scrollView: UIScrollView!) { }

    function.

  2. Hi @vinod, if you want to show your data inside a table and make it sortable for the user have a look at the [SortableTableView](https://github.com/ISchwarz23).

    Your code will look like this:

    List flights = new ArrayList();
    myData.add(new Flight(…));
    myData.add(new Flight(…));
    myData.add(new Flight(…));

    TableView table = findViewById(R.id.table);
    table.setHeaderAdapter(new SimpleHeaderAdapter(“Time”, “Airline”, “Flight”, “Destination”));
    table.setDataAdapter(new FlightDataAdapter(flights));

    The result could look like this:

    ![Example](https://i.stack.imgur.com/OHaYB.jpg)

    Best regards 🙂

  3. Thanks for the explanation

    If user changes a cell data, how can we dynamically update in database and then show it ui later.

  4. Thank you very much Amit! I am a 70 years “old boy” former Cobol programmer, just approaching Java and Android programming, to spend some of Covid19 tyme… 🙁
    Your work is the best i’ve seen up to now, looking for info into the web. The only thing i do not clearly undestand is how to specify the number of columns in a row; i would like to have only one column, to show every textview to a new line. I can obtain it whith \n\r new line separator but i think there is a better way of doing it and you surely know it!

    Ciao e Grazie 1000!
    Claudio
    (sorry for “maccheroni” english…

    • @Claudio Thanks for the kind words. I learnt Cobol in the early 90s. Did a couple of small projects in it, but never really worked much in it. I understand its still a valuable skillset to have, but its very hard to get experienced programmers in that language.

      To reply to your question, the number of columns in a table will always be the row with the max columns. In other words in the sample above, if you just add Customer field to the TableRowLayout and nothing else then it will have only one column.

Leave a Reply to amit Cancel reply

Your email address will not be published.


*