-1

I have a String[][] of data and I am trying to make a custom listView from it.

Here is the data

String[][] myDataArray = {{"cat1","cat2","cat3","cat4"},
                          {"dog1","dog2","dog3"},
                          {"lion1"},
                          {"monkey1","monkey2"}};

And now here is how I am trying to display this data in my listView. I want each array within the array to have its own row. So all the cats will be in one row (cell), all the dogs will be in another row and so on. Here is a picture to make it clear each item in the row, is a textView.

enter image description here

I have made cell_4.xml, cell_3.xml, cell_2.xml, cell_1.xml layout file for each of the rows. And then in the activity that I am trying to show this, I just have a plain old listView.

Now I am not quite sure how to edit/ adapt the data. I have to display it in this way. So that it uses the correct cell layout for each array within the String[]. I was thinking about using a switch statement to get the number of items in each inner array. But having some trouble with the ArrayAdapter. To get it set up.

I have looked at a couple of examples on stackoverflow like this one Custom ListView Android to try and figure this out but can't get it.

EDIT

Here is trying to set up adapter and call MyListViewAdapter, but I don't know what to set as context.

here is the code:

private void handleData(String[][] data){
    BaseAdapter adapter = MyListAdapter(context, data);
    ListView list = (ListView) findViewById(R.id.mealsListView);
    list.setAdapter(adapter);

}
Community
  • 1
  • 1
iqueqiorio
  • 1,149
  • 2
  • 35
  • 78

3 Answers3

1

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.

rupps
  • 9,712
  • 4
  • 55
  • 95
  • so do both ur solution 1 and 2 use grid view? I am open to another type... I just need to to do what is in the picture and be able to set onClickListerners for each textView. And for my usage there is a maximum of 4 textviews per cell and as low as 0. So which would be best to use? And there will always be seven rows, in the picture above I have 4 but I will have 7 – iqueqiorio Dec 12 '14 at 01:19
  • nah they both for Listview. You can use any of them, the first one allows you to maybe customize a bit the layouts, but you have to define a layout for each number of cells. The second one generates the layout automatically. See my edit for assigning a clicklistener. – rupps Dec 12 '14 at 01:22
  • okay great, I see that but I am still trying to set up the BaseAdapter? for my data. I have this `BaseAdapter adapter = new MyListAdapter();` but i gives compile error `ListElementAdapter (Context, String[][]) in MyListAdapter cannot be applied to ()` – iqueqiorio Dec 12 '14 at 01:25
  • looks like you need to pass context and data to the constructor isnt it? something like BAseAdapter adapter=new MyListAdapter(context, myDataArray)? can't tell without seeing your adapter ! anyway just fight a little to get it running then try to implement what I tell you, it's a pretty proper way to do this and get it running very smooth! – rupps Dec 12 '14 at 01:33
  • hehe .. `Context` is a valid context. You can get a context from: The activity itself (`Activity` extends `Context` so `"this"` is a valid context) or any existing view, calling `view.getContext();` . As you are using `findViewById`, probably you are inside an activity, so `"this"` will be a valid context – rupps Dec 12 '14 at 01:45
  • @rupps I was thinking about the recycler, but I wasn't sure exactly how it worked. If you call notifyDataSetChanged(), it will reinflate all the layouts? – tachyonflux Dec 12 '14 at 02:07
  • It will re-create all the (visible) rows, but if the recycler is working properly and the list has been already created, in `convertview` you will get passed an old row (with old content) so you can update it with new content instead of the ultra-costly inflation. That's why it's very important in your case to override getItemViewType, so ListView will make sure that the row passed in convertView is of the correct number of cells. – rupps Dec 12 '14 at 02:14
  • Hey I have been playing around with your code trying to get case 2 to work but I get a compile error `cannot resolve method getChild(i)` on this convertView.getChild(i); – iqueqiorio Dec 12 '14 at 23:27
  • my mistake, look at the edit, it's getChildAt() but there's a cast to ViewGroup too. – rupps Dec 13 '14 at 09:45
0

You will need to make your own adapter by extending BaseAdapter. You can check the data's size the getView() method, and inflate the correct layout.

UPDATE:

public class MyListAdapter extends BaseAdapter{
    String[][] mData;
    LayoutInflater mLayoutInflater;

    public MyListAdapter(Context context, String[][] data) {
        mData = data;
        mLayoutInflater = LayoutInflater.from(context);
    }

    @Override
    public int getCount() {
        return mData.length;
    }

    @Override
    public Object getItem(int position) {
        return null;
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        String data[] = mData.get(position);
        switch(data.length){
            case 4:
                convertView = mLayoutInflater.inflate(R.layout.cell_4, parent, false);
                TextView t1 = (TextView) convertView.findViewById(R.id.one);
                t1.setText(data[0]);
                break;
            case 3:
                convertView = mLayoutInflater.inflate(R.layout.cell_3, parent, false);
                break;
            case 2:
                convertView = mLayoutInflater.inflate(R.layout.cell_2, parent, false);
                break;
            case 1:
                convertView = mLayoutInflater.inflate(R.layout.cell_1, parent, false);
                break;
            default:
                convertView = mLayoutInflater.inflate(R.layout.blank, parent, false);
        }
        return convertView;
    }
}
tachyonflux
  • 20,103
  • 7
  • 48
  • 67
0

You might run into problems if the size of each string in the row varies and you might then have to push data onto the next line. Try using an alternate view, if your aim is categorization of similar data, expandable listview is an option to consider.

TechnoBlahble
  • 633
  • 5
  • 14