Some thoughts:
1) If you are determined to use ListView, skip this point. Else, you might be interested in GRIDVIEW that natively support a table structure.
2) Your idea is consistent. ListView only knows about ROWS, so your adapter will be called for you to display a ROW, and it's up to you to transform the array in that row into an element with multiple cells. You'll do that in getView()
3) You'll make use of the Item Types (getViewTypeCount
and getItemViewType
) to declare you have different item types. Each type will be a row with a given number of cells: 1,2,3,4...
- you will override
getViewTypeCount()
to return the maximum number of cells in a row
- you will either inflate a static layout for the number of cells a row has, or generate it dynamically
Let's get started ... First of all in the adapter we override the Type methods to declare
our rows will be of different types:
@Override
public int getViewTypeCount() {
return 4;
// you have 4 types of rows.
// SUPER IMPORTANT: No row in the array can have more cells than this number
// or getView will crash (you'd have to define additional layouts)
}
@Override
public int getItemViewType(int position) {
// for a given position, you need to return what type is it. This number ranges
// from 0 to itemtypecount-1. We return the length of the array (number of cells)
// this function is called by the View Recycler to appropriately pass you the
// correct view to reuse in convertView
return myDataArray[position].length - 1;
}
And then we need to implement getView(). The typical implementation will be the first one, where you create different XMLs, and the second one is a more advanced implementation where we dynamically create the layouts without any xml.
First Case: Static Layouts
- Ideal if you limit the Row Array Length to say 3 or 4, to avoid creating dozens of layouts. So you define 4 xmls (ie.
row_1_childs
, row_2_childs
, row_3_childs
, row_4_childs
) that will be the templates of rows with that number of children. Then,
and then in GetView:
// we define an array of layout ids to quickly select the layout to inflate depending on
// the number of rows:
private final static int[] sLayouts=new int[] {
R.layout.row_1_childs,
R.layout.row_2_childs,
R.layout.row_3_childs,
R.layout.row_4_childs
};
public View getView (int position, View convertView, ViewGroup parent) {
int maxcells=myDataArray[position].length;
if (convertView == null) {
// generate the appropriate type
if (maxcells<=sLayout.length) {
// just check we are in bounds
convertView=LayoutInflater.from(parent.getContext()).inflate(sLayout[maxcells-1], null);
} else {
// you have a row with too many elements, need to define additional layouts
throw new RuntimeException ("Need to define more layouts!!");
}
}
// At this point, convertView is a row of the correct type, either just created,
// or ready to recycle. Just fill in the cells
// for example something like this
ViewGroup container=(ViewGroup)convertView;
for (int i=0; i<maxcells; i++) {
// We assume each row is a (linear)layout whose only children are textviews,
// one for each cell
TextView cell=(TextView)container.getChildAt(i); // get textview for cell i
cell.setText(myDataArray[position][i]);
cell.setTag( new PositionInfo(position, i)); // we store the cell number and row inside the TextView
cell.setOnClickListener(mCellClickListener);
}
return convertView;
}
Second Case: Dynamic Layouts
Another solution would be to dynamically generate the rows, and dynamically generate as many text views as you might need. To do so, keep overriding getViewTypeCount()
to return the Maximum number of children, and define getView
like this:
public View getView (int position, View convertView, ViewGroup parent) {
String rowData=myDataArray[position];
if (convertView==null) {
// generate a LinearLayout for number of children:
LinearLayout row=new LinearLayout(context);
for (int i=0, len=rowData.length(); i<len; i++) {
// generate a textview for each cell
TextView cell=new TextView(parent.getContext());
// we will use the same clicklistener (very efficient)
cell.setOnClickListener(mCellClickListener);
row.addView(cell, new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1)); // same width for each cell
}
convertView=row;
}
// here convertView has the correct number of children, same as before:
ViewGroup container=(ViewGroup)convertView;
for (int i=0, len=rowData.length(); i<len; i++) {
TextView cell=(TextView)container.getChildAt(i);
cell.setText(rowData[i]);
cell.setTag( new PositionInfo(position, i)); // we store the cell number and row inside the TextView
}
return convertView;
}
// auxiliar class to store row and col in each textview for the clicklistener
private class PositionInfo {
public int row, col;
public PositionInfo(int row, int col) { this.row=row; this.col=col; }
}
// trick: only one clicklistener for millions of cells
private View.OnClickListener mCellClickListener=new View.OnClickListener() {
@Override
public void onClick(View v) {
PositionInfo position=(PositionInfo)v.getTag(); // we stored this previously
// you pressed position.row and position.col
}
}
Solution (1) is cool to manually create the layouts and configure them a lot.
Solution (2) is cool to programmatically support any number of cells, in case they are very different
Both solutions are pretty efficient, because they play nice with the View recycler: If you fail to use View Types and you constantly inflate layouts, your ListView will be laggy and waste a lot of memory and resources.