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
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="table.test.com.dynamictable"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
dimens.xml
<resources> <!-- Default screen margins, per the Android Design guidelines. --> <dimen name="activity_horizontal_margin">16dp</dimen> <dimen name="activity_vertical_margin">16dp</dimen> <dimen name="font_size_verysmall">10sp</dimen> <dimen name="font_size_small">12sp</dimen> <dimen name="font_size_medium">20sp</dimen> </resources>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:orientation="vertical" android:id="@+id/invoices_layout" tools:context="table.test.com.dynamictable.MainActivity"> <ScrollView android:layout_width="match_parent" android:layout_height="match_parent" > <TableLayout android:id="@+id/tableInvoices" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="0dp" android:stretchColumns="*" > </TableLayout> </ScrollView> </LinearLayout>
InvoiceData.java
package table.test.com.dynamictable; import java.math.BigDecimal; import java.util.Date; // // Data structure for invoices // public class InvoiceData { public int id; public int invoiceNumber; public Date invoiceDate; public String customerName; public String customerAddress; public BigDecimal invoiceAmount; public BigDecimal amountDue; }
Invoices.java
package table.test.com.dynamictable; import java.math.BigDecimal; import java.util.Date; /** * Created by amit on 1/5/16. */ public class Invoices { public InvoiceData[] getInvoices() { InvoiceData[] data = new InvoiceData[20]; for(int i = 0; i < 20; i ++) { InvoiceData row = new InvoiceData(); row.id = (i+1); row.invoiceNumber = row.id; row.amountDue = BigDecimal.valueOf(20.00 * i); row.invoiceAmount = BigDecimal.valueOf(120.00 * (i+1)); row.invoiceDate = new Date(); row.customerName = "Thomas John Beckett"; row.customerAddress = "1112, Hash Avenue, NYC"; data[i] = row; } return data; } }
MainActivity.java
package table.test.com.dynamictable; import android.app.ProgressDialog; import android.graphics.Color; import android.os.AsyncTask; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.TypedValue; import android.view.Gravity; import android.view.View; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.TableLayout; import android.widget.TableRow; import android.widget.TextView; import java.math.BigDecimal; import java.text.DecimalFormat; import java.text.SimpleDateFormat; public class MainActivity extends AppCompatActivity { private TableLayout mTableLayout; ProgressDialog mProgressBar; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mProgressBar = new ProgressDialog(this); // setup the table mTableLayout = (TableLayout) findViewById(R.id.tableInvoices); mTableLayout.setStretchAllColumns(true); startLoadData(); } public void startLoadData() { mProgressBar.setCancelable(false); mProgressBar.setMessage("Fetching Invoices.."); mProgressBar.setProgressStyle(ProgressDialog.STYLE_SPINNER); mProgressBar.show(); new LoadDataTask().execute(0); } public void loadData() { int leftRowMargin=0; int topRowMargin=0; int rightRowMargin=0; int bottomRowMargin = 0; int textSize = 0, smallTextSize =0, mediumTextSize = 0; textSize = (int) getResources().getDimension(R.dimen.font_size_verysmall); smallTextSize = (int) getResources().getDimension(R.dimen.font_size_small); mediumTextSize = (int) getResources().getDimension(R.dimen.font_size_medium); Invoices invoices = new Invoices(); InvoiceData[] data = invoices.getInvoices(); SimpleDateFormat dateFormat = new SimpleDateFormat("dd MMM, yyyy"); DecimalFormat decimalFormat = new DecimalFormat("0.00"); int rows = data.length; getSupportActionBar().setTitle("Invoices (" + String.valueOf(rows) + ")"); TextView textSpacer = null; mTableLayout.removeAllViews(); // -1 means heading row for(int i = -1; i < rows; i ++) { InvoiceData row = null; if (i > -1) row = data[i]; else { textSpacer = new TextView(this); textSpacer.setText(""); } // data columns final TextView tv = new TextView(this); tv.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT, TableRow.LayoutParams.WRAP_CONTENT)); tv.setGravity(Gravity.LEFT); tv.setPadding(5, 15, 0, 15); if (i == -1) { tv.setText("Inv.#"); tv.setBackgroundColor(Color.parseColor("#f0f0f0")); tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, smallTextSize); } else { tv.setBackgroundColor(Color.parseColor("#f8f8f8")); tv.setText(String.valueOf(row.invoiceNumber)); tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); } final TextView tv2 = new TextView(this); if (i == -1) { tv2.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.WRAP_CONTENT)); tv2.setTextSize(TypedValue.COMPLEX_UNIT_PX, smallTextSize); } else { tv2.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT, TableRow.LayoutParams.MATCH_PARENT)); tv2.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); } tv2.setGravity(Gravity.LEFT); tv2.setPadding(5, 15, 0, 15); if (i == -1) { tv2.setText("Date"); tv2.setBackgroundColor(Color.parseColor("#f7f7f7")); }else { tv2.setBackgroundColor(Color.parseColor("#ffffff")); tv2.setTextColor(Color.parseColor("#000000")); tv2.setText(dateFormat.format(row.invoiceDate)); } final LinearLayout layCustomer = new LinearLayout(this); layCustomer.setOrientation(LinearLayout.VERTICAL); layCustomer.setPadding(0, 10, 0, 10); layCustomer.setBackgroundColor(Color.parseColor("#f8f8f8")); final TextView tv3 = new TextView(this); if (i == -1) { tv3.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.MATCH_PARENT)); tv3.setPadding(5, 5, 0, 5); tv3.setTextSize(TypedValue.COMPLEX_UNIT_PX, smallTextSize); } else { tv3.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.MATCH_PARENT)); tv3.setPadding(5, 0, 0, 5); tv3.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); } tv3.setGravity(Gravity.TOP); if (i == -1) { tv3.setText("Customer"); tv3.setBackgroundColor(Color.parseColor("#f0f0f0")); } else { tv3.setBackgroundColor(Color.parseColor("#f8f8f8")); tv3.setTextColor(Color.parseColor("#000000")); tv3.setTextSize(TypedValue.COMPLEX_UNIT_PX, smallTextSize); tv3.setText(row.customerName); } layCustomer.addView(tv3); if (i > -1) { final TextView tv3b = new TextView(this); tv3b.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT, TableRow.LayoutParams.WRAP_CONTENT)); tv3b.setGravity(Gravity.RIGHT); tv3b.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); tv3b.setPadding(5, 1, 0, 5); tv3b.setTextColor(Color.parseColor("#aaaaaa")); tv3b.setBackgroundColor(Color.parseColor("#f8f8f8")); tv3b.setText(row.customerAddress); layCustomer.addView(tv3b); } final LinearLayout layAmounts = new LinearLayout(this); layAmounts.setOrientation(LinearLayout.VERTICAL); layAmounts.setGravity(Gravity.RIGHT); layAmounts.setPadding(0, 10, 0, 10); layAmounts.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.MATCH_PARENT)); final TextView tv4 = new TextView(this); if (i == -1) { tv4.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.MATCH_PARENT)); tv4.setPadding(5, 5, 1, 5); layAmounts.setBackgroundColor(Color.parseColor("#f7f7f7")); } else { tv4.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.WRAP_CONTENT)); tv4.setPadding(5, 0, 1, 5); layAmounts.setBackgroundColor(Color.parseColor("#ffffff")); } tv4.setGravity(Gravity.RIGHT); if (i == -1) { tv4.setText("Inv.Amount"); tv4.setBackgroundColor(Color.parseColor("#f7f7f7")); tv4.setTextSize(TypedValue.COMPLEX_UNIT_PX, smallTextSize); } else { tv4.setBackgroundColor(Color.parseColor("#ffffff")); tv4.setTextColor(Color.parseColor("#000000")); tv4.setText(decimalFormat.format(row.invoiceAmount)); tv4.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); } layAmounts.addView(tv4); if (i > -1) { final TextView tv4b = new TextView(this); tv4b.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.WRAP_CONTENT)); tv4b.setGravity(Gravity.RIGHT); tv4b.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); tv4b.setPadding(2, 2, 1, 5); tv4b.setTextColor(Color.parseColor("#00afff")); tv4b.setBackgroundColor(Color.parseColor("#ffffff")); String due = ""; if (row.amountDue.compareTo(new BigDecimal(0.01)) == 1) { due = "Due:" + decimalFormat.format(row.amountDue); due = due.trim(); } tv4b.setText(due); layAmounts.addView(tv4b); } // add table row final TableRow tr = new TableRow(this); tr.setId(i + 1); TableLayout.LayoutParams trParams = new TableLayout.LayoutParams(TableLayout.LayoutParams.MATCH_PARENT, TableLayout.LayoutParams.WRAP_CONTENT); trParams.setMargins(leftRowMargin, topRowMargin, rightRowMargin, bottomRowMargin); tr.setPadding(0,0,0,0); tr.setLayoutParams(trParams); tr.addView(tv); tr.addView(tv2); tr.addView(layCustomer); tr.addView(layAmounts); if (i > -1) { tr.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { TableRow tr = (TableRow) v; //do whatever action is needed } }); } mTableLayout.addView(tr, trParams); if (i > -1) { // add separator row final TableRow trSep = new TableRow(this); TableLayout.LayoutParams trParamsSep = new TableLayout.LayoutParams(TableLayout.LayoutParams.MATCH_PARENT, TableLayout.LayoutParams.WRAP_CONTENT); trParamsSep.setMargins(leftRowMargin, topRowMargin, rightRowMargin, bottomRowMargin); trSep.setLayoutParams(trParamsSep); TextView tvSep = new TextView(this); TableRow.LayoutParams tvSepLay = new TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.WRAP_CONTENT); tvSepLay.span = 4; tvSep.setLayoutParams(tvSepLay); tvSep.setBackgroundColor(Color.parseColor("#d9d9d9")); tvSep.setHeight(1); trSep.addView(tvSep); mTableLayout.addView(trSep, trParamsSep); } } } ////////////////////////////////////////////////////////////////////////////// // // The params are dummy and not used // class LoadDataTask extends AsyncTask<Integer, Integer, String> { @Override protected String doInBackground(Integer... params) { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } return "Task Completed."; } @Override protected void onPostExecute(String result) { mProgressBar.hide(); loadData(); } @Override protected void onPreExecute() { } @Override protected void onProgressUpdate(Integer... values) { } } }
SCREENSHOT
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.
your is awesome. can we filter by selecting from date and to date!!!
@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.
Its awesome.
Thanks for sharing this tutorial.
Can I implement swipe refresh in this tutorial?
Thanks
@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.
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 🙂
what is the value of the previous table row in a dynamic added table?
Hi,
I had a set of array list data, how can I implement in above code.
Hi,
I had a set of array list data, how can I implement in above code.
Thanks
IS there any video tutorial available??
Thanks for the explanation
If user changes a cell data, how can we dynamically update in database and then show it ui later.
Will this work ,as Fragment table can on accessed in main activty
Thank you very much Amit!
It worked amazingly in the first touch.
Have a good time!
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.
Ciao Amit, thank you for you kind answer. I will think about and try what you told me and i’ll come back!
Got it!
Thankyou again Amit, and all the best!
Claudio
@Claudio Awesome.
Thanks Amit really nice tutorial