The problem
Let’s say you want a layout that is a grid of items with a header. Something like this:
Displaying items in a grid is easy, we have GridView for that.
Displaying a list of items with a header is easy, we have setHeaderView on ListView for that.
The problem is when we want to show items in a grid with a header, since GridView does not support headers and ListView does not support columns.
This is a common problem and there are a few suggestion on StackOverflow, but none of them goes further than some guidelines. I did implement it and I want to share it so you don’t need to reinvent the wheel
From the architectural point of view, the solution is to use a ListView with a special adapter that displays the entries as separated columns.
How the code should look like
The code at activity level when you configure the view is like this:
ListView listView = (ListView) findViewById(R.id.listView); listView.addHeaderView(createHeaderView()); adapter = new GidViewWithHeaderExampleAdapter(this); adapter.setNumColumns(2); listView.setAdapter (adapter);
Note that you add the header to the list view as a normal header, but set the number of columns to the Adapter.
Extending from the right adapter
The adapter itself has to extend from GridViewWithHeaderBaseAdapter and implement some methods that are slightly different from the ones in a normal adapter.
Integer[] mArray = new Integer[] {1,2,3,4,5,6,7,8,9,10,11,12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}; private LayoutInflater mInflater; public GridViewWithHeaderExampleAdapter(Context context) { super(context); mInflater = LayoutInflater.from(context); } @Override public Integer getItem(int position) { return mArray[position]; } @Override public long getItemId(int position) { return position; } @Override public int getItemCount() { return mArray.length; } @Override protected View getView(int position, View v) { if (v == null) { v = mInflater.inflate(R.layout.simple_list_item, null); } TextView tv = (TextView) v.findViewById(R.id.text); tv.setText(String.valueOf(getItem(position))); return v; }
So getItem and getItemId are the same as for normal adapters.
On the other hand getItemCount is a replacement method for getCount and getItemView is a replacement for getView. both are implemented on the GridViewWithHeaderBaseAdapter and are the methods that do the magic.
Handling click on items
The adapter creates a LinearLayout and adds child views to it for each row, creating a single list item for each row.
Then, of course, OnItemClickListener is broken, because each list item is a row. You have to use GridItemClickListener instead.
@Override public void onGridItemClicked(View v, int position, long itemId) { // TODO: Handle item click here }
Advantages versus TableLayout
What is the advantage of this solution versus just a TableLayout on a ScrollView?
The advantage is that the Adapter recycles the views and it is a lot more efficient in memory and in execution.
The other advantage is that the Adapter is dynamic. You can not hardcode a TableView without knowing the items beforehand. Sure, you can build it programatically, but if you are getting that far, you probably want to get one step further and build an adapter, which is what I made.
GridViewWithHeaderBaseAdapter is about 150 lines of code, so feel free to look at the source for inspiration and/or modify it for your own purposes, it is under BSD license.
You can check the project on github. It includes the example.