0

I contribute to an open source project, which uses a ListView to show some data, with each item containing 2 TextView items R.id.card_column1 and R.id.card_column2, so that we see a 2 column list representing some data for a large number of flashcards.

We have a fixed data structure ArrayList<HashMap<String, String>> mCards containing various key/value pairs which could be shown in the columns, and use a SimpleAdapter to map between a given key/value pair in the HashMap of the mCards elements, and the TextView items as follows:

    mCardsAdapter = new SimpleAdapter(
            this,
            mCards,
            R.layout.card_item_browser,
            new String[] {col1Key, col2Key},
            new int[] {R.id.card_column1, R.id.card_column2});

The data mapping between the fixed mCards structure and the 2 TextView elements making up the columns needs to be adjustable, i.e. we need to be able to change col1Key and col2Key. I couldn't find any methods for changing the from keys in the SimpleAdapter documentation, so I currently have implemented this by simply calling the setAdapter() method of the ListView with a new SimpleAdapter containing the updated keys, whenever the user asks to change the column. The problem with this approach is that the vertical position in the ListView is reset.

One solution could be to change the data structure itself, and call notifyDataSetChanged(), however the array can be extremely large, so this approach would be a waste of valuable RAM, and may require significant CPU time to update the data structure, so I want to avoid this approach.

So my questions are:

1) Is it possible to dynamically change the mapping in the adapter, i.e. change the from object in the constructor, without creating a new SimpleAdapter?

2) If not, what's the best way to change the mapping without losing the vertical scrolling position in the ListView? Preferably without consuming any extra RAM, or requiring more CPU than creating a new adapter.

Tim Rae
  • 2,987
  • 2
  • 25
  • 33
  • you want to change an element in your mCards list? Yes, it is possible, access the list at needed position, change its data and call notifyDataSetChanged method on the existing adapter – Boris Mocialov Apr 09 '14 at 08:05
  • 1
    If by "mapping" you mean your String array, then you make a setter method in your adapter class where you send your new "mapping" and then call notifyDataSetChanged where you handle sunch mapping in getView method – Boris Mocialov Apr 09 '14 at 11:55
  • Yes exactly. Thanks, so if I understand correctly, you're saying I would have to subclass `SimpleAdapter`, provide a set method for a new map variable, and then manually implement the mapping process by overriding `getView`? Presumably it would be cleaner to inherit from `BaseAdapter` instead... – Tim Rae Apr 09 '14 at 12:27
  • in your original question you had `SizeControlledListAdapter`, which is already should have extended a `SimpleAdapter` which in turn has `getView` method. source: https://github.com/ankidroid/Anki-Android/blob/ce185930e9550a8baa499a63f6b42139a72ecc09/src/com/ichi2/anki/CardBrowser.java#L1081 – Boris Mocialov Apr 09 '14 at 12:34
  • @MocialovBoris, thanks for your patience with this, I have posted the full answer below. – Tim Rae Apr 10 '14 at 03:35

1 Answers1

-1

Thanks to Mocialov Boris for the tip, here's the full answer for reference:

While it's not possible to update the mapping of a SimpleAdapter, you can extend BaseAdapter and manually implement the mapping. We provide a setFromMapping() method which updates the mapping between the data structure and the columns, and calls notifyDataSetChanged(). The new class MultiColumnListAdapter is then just used in the same way that a SimpleAdapter is used:

public class MultiColumnListAdapter extends BaseAdapter {
    private List<? extends Map<String, ?>> mData;
    private int mResource;
    private String[] mFrom;
    private int[] mTo;
    private LayoutInflater mInflater;


    public MultiColumnListAdapter(Context context, List<? extends Map<String, ?>> data, int resource,
            String[] from, int[] to) {            
        mData = data;
        mResource = resource;
        mFrom = from;
        mTo = to;
        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }


    public View getView(int position, View convertView, ViewGroup parent) {
        // Get the main container view if it doesn't already exist, and call bindView
        View v;            
        if (convertView == null){
            v = mInflater.inflate(mResource, parent, false);
            final int count = mTo.length;
            final View[] columns = new View[count];
            for (int i = 0; i < count; i++) {
                columns[i]=v.findViewById(mTo[i]);
            }
            v.setTag(columns);                
        } else {
            v = convertView;
        }
        bindView(position, v);
        return v;
    }

    private void bindView(int position, View v) {
        // Draw the content in the columns
        View[] columns = (View[]) v.getTag();
        final Map dataSet = mData.get(position);
        for (int i = 0; i < mTo.length; i++) {
            TextView col = (TextView) columns[i];
            col.setText((String) dataSet.get(mFrom[i]));
        }           
    }

    public void setFromMapping(String[] from){
        mFrom = from;
        notifyDataSetChanged();
    }

    public String[] getFromMapping(){
        return mFrom;
    }        

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


    @Override
    public Object getItem(int position) {
        return mData.get(position);
    }


    @Override
    public long getItemId(int position) {
        return position;
    }
}
Tim Rae
  • 2,987
  • 2
  • 25
  • 33