As part of the app I developed for AngelHack I wanted to display some data in a classic grid style. Android offers a GridView as part of the basic SDK, however if you look at all the samples they only show how to use it as a grid of images, essentially they only demonstrate usages where all columns show the same thing, usually columns of photographs. It’s not too tricky to use the GridView as classic “data grid” but it did require a wee bit of thinking, so I thought I put together a quick example to show how it worked.
In many ways working with Android controls reminds me of working with WFP. They both allow a user to either create controls using an xml based designer or create them directly in code. The way data-binding works is similar too, for Android controls you plug in a “data adapt” which controls the creation of the child controls that will handle displaying the data. To use the GridView we need to create an adapter to which is responsible for telling the GridView how the data source should be rendered. Initially for the data source I created mine own “DataTable like object”, but then after saying d’oh and slapping my head a couple of times I realized there was no need to create “DataTable like object” when I could just use a DataTable.
The first step is to create the adapter, to do this you derive from BaseAdapter<T> where T is the type of element that will appear in the data cells, for simplicity I made T string. Next you need to provide a constructor that takes a Activity (which is an Androidy type thing for accessing the gui context) and the data source itself, in our case a DataTable. This means the top of the adapter class looks something like:
public class DataTableGridAdapter : BaseAdapter<string> {
private Activity context = null;
private DataTable itemTable = null;
public DataTableGridAdapter(Activity context, DataTable itemTable) : base ()
{
this.context = context;
this.itemTable = itemTable;
}
Next you need to override the count property to return the number of cells (the rows plus one gives room for column headers):
public override int Count
{
get { return (itemTable.Rows.Count + 1) * itemTable.Columns.Count; }
}
Then you need to override the GetView method, which is the method that creates the control that will be displayed for the cell:
public override Android.Views.View GetView (int position, Android.Views.View convertView, Android.Views.ViewGroup parent)
{
// find the current row number, need to test we're in the header
int row = GetRow(position);
// Get our text for position
var item = PositionToString(position);// find if we have a control for the cell, if not create it
var txtName = convertView as EditText ?? new EditText (context);
// if the row is a header give it a different colour
if (row == 0) {
txtName.SetBackgroundColor( new Color(0x0d,0x12,0xf5));
txtName.SetTextColor (new Color(0x0,0x0,0x0));
}
//Assign item's values to the various subviews
txtName.SetText (item, TextView.BufferType.Normal);
//Finally return the view
return txtName;
}
The important line is '”var txtName = convertView as EditText ?? new EditText (context);” here we see if the control to display the cell has been created or not, if not we create one. In this case we use an EditView, a control build-in to the Android SDK for editing text. This is because we want our grid to be editable, if we wanted a readonly grid we’d use a TextView instead. We we wanted to display complex, more structured data we could return a user defined control instead. The rest of the method is just about setting up the various properties of this control, including it’s content, which we obtain from our data source via the PositionToString method. The position is the identifier of the cell in the grid, the data grid doesn’t provide a row and column id, just one continuous position id, fortunate with just a little maths we can translate a position to a row and column pair. Below shows how we do this and also the implementation of “PositionToString”.
private int GetCol(int pos){
int col = pos % (itemTable.Columns.Count);
return col;
}
private int GetRow(int pos){
int row = pos / (itemTable.Columns.Count);
return row;
}
private string PositionToString(int pos)
{
int row = GetRow(pos);
int col = GetCol(pos);
if (row == 0) {
return itemTable.Columns[col].ColumnName;
}
if (itemTable.Rows [row - 1][col] != DBNull.Value) {
return itemTable.Rows [row - 1][col].ToString();
}
return "";
}
There’s a few wall-dressing method that need to be implement, but I’m put the code on github, so you can take a look at them here.
To use the “DataTableGridAdapter”, simply add a GridView to one of you’re layouts, a .axml file, then you’ll need to configure the data adapter in the Activity that uses the layout. To do this first recover a reference to the GridView and store it as a member:
gridView = FindViewById<GridView> (Resource.Id.offerGridView);
Once you’ve created your DataTable and populated it, binding to the grid as so:
var itemListAdapter = new Adapters.DataTableGridAdapter (this, dataTable);
gridView.NumColumns = dataTable.Columns.Count;
//Hook up our adapter to our ListView
gridView.Adapter = itemListAdapter;
The slightly annoying thing about this approach is you need to explicitly set the number of columns the grid has and you do need to keep updating the number of columns each time a new column is added or removed.
And that’s basically it, take a look at the full working example here, and feel free to send a pull request if you have an improvements.
Feedback:
Feedback was imported from my only blog engine, it's no longer possible to post feedback here.
re: Using the Android GridView as a DataGrid - Stefan
Thanks for the article. That's exactly what I was looking for and it worked perfectly :)
Now I'm trying to add on it to auto refresh the grid every few seconds since I'm streaming data from a server.
But I cannot managed. Basically, I created a new thread with a timer and made sure it runs on the main UI thread. It updates the datatable and then I set the adapter NotifyDataSetChanged() but nothing so far.
Any ideas!? Thanks :)
re: Using the Android GridView as a DataGrid - Tone
Very consistent and logical thinking. Respect and uvazhuha!
Thank you for a practical example!
Greetings from the Bear of Western Siberia ;)
re: Using the Android GridView as a DataGrid - Cinue
This is the missing thing in GridView when migrate from .Net to Mono for Android.