Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ListView/CursorAdapter generating list headers on the fly

I have one db table like this:

ID
NAME
COUNTRY_NAME

And i want a list like this:

+ Italy
    - Potato
    - Tomato
+ France
    - Fromage
    - Baguette

And i wrote a CursorAdapter that, every time requery is called, read ALL items on the table and map it in a object that is used to map position of every item (real item or header).

private  static class PersonEntry {
    boolean isHeader;
    String countryName;
    int realPos;
    int id;
}

Code is this:

 /* cursor generated by querying whole table */

 public void readHeaders(Cursor cursor ) {
    Log.d(this.getClass().getSimpleName(),"readHeaders init");
    items = new ArrayList<PersonEntry >();
    this.mCursor = cursor;

    cursor.moveToFirst();
    int i = 0;
    String previousCountry = "";
    String currentCountry;
    while(cursor.isAfterLast() == false)  {
        int id = cursor.getInt(cursor.getColumnIndexOrThrow(Match.ROW_ID));
        currentCountry = cursor.getString(cursor.getColumnIndexOrThrow(Person.ROW_COUNTRY_NAME));
        if (!currentCountry.equals(previousCountry)) {
            // ho incontrato una nuova nazione
            // rispetto alla precedente, aggiungiamola
            items.add(new PersonEntry(... define isHeader=true ..));

        }
        // stiamo comunque scorrendo gli elementi, aggiungiamo quello appena trovato
        items.add(new PersonEntry( ... defile isHeader = false ....);
        previousCountry = currentCountry;
        i++;
        cursor.moveToNext();
    }
    cursor.close();
    Log.d(this.getClass().getSimpleName(),"readHeaders end");


}

So i rewrote getView, bindView and newView to inflate the right layout and binding the view basing it on realPos-position Cursor.

The method works but it is REALLY expensive: it need to elaborate the whole table and i have got many records. What i am searching for is a simply method to map realPosition -> fakePosition while scrolling the ListView, but methods i thinked are too much complicated and i think that they will break if getView isn't linear (fast scrolling?).

SOLUTIONS: 1) Query ordering by COUNTRY_NAME. While scrolling down real_cursor_position = (requested position - "country changes # (?)"). If the requested position translated in real_position come after a item with a different country_name, it is a header. It will broke when scrolling fist down and after up, i think, unless tricky solutions. ... nothing more

Any other solution?

Edit: Another problem is that i can't predict the number of views returned by adapter.getCount() without scanning the whole table.

like image 772
max_s Avatar asked Feb 17 '23 14:02

max_s


1 Answers

I've written the getView method of simple adapter to show you how you might build a ListView with headers grouping the items that have the same country. This will assume the header view is in each row's layout showing/hiding it as the current row demands. The same thing could be done by using the getItemViewType method with a bit more work on binding the various parts of the adapter.

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if (convertView == null) {
            convertView = mInflater.inflate(
                    R.layout.rowlayout, parent, false);
        }
        // for simplicity, the header it's just a TextView
        TextView header = (TextView) convertView
                .findViewById(R.id.headerPart);
        // also for simplicity, the row content it's just a TextView
        TextView rowText = (TextView) convertView
                .findViewById(R.id.normalPart);
        // set the data for the row
        mCursor.moveToPosition(position);
        rowText.setText(mCursor.getString(mCursor.getColumnIndex("name")));
        // this is not the first position               
        if (position - 1 >= 0) {
            // if there is a previous position see if it has the same
            // country(in which case you already had set the header)
            String currentCountry = mCursor.getString(mCursor
                    .getColumnIndex("country"));
            mCursor.moveToPosition(position - 1);
            String previousCountry = mCursor.getString(mCursor
                    .getColumnIndex("country"));
            if (currentCountry.equalsIgnoreCase(previousCountry)) {
                // the countries are the same so abort everything as we
                // already set the header on one of previous rows
                header.setVisibility(View.GONE);
            } else {
                // this is the first occurrence of this country so show the
                // header
                header.setVisibility(View.VISIBLE);
                header.setText(currentCountry);
            }
        } else {
            // this is position 0 and we need a header here
            header.setVisibility(View.VISIBLE);
            header.setText(mCursor.getString(mCursor
                    .getColumnIndex("country")));
        }
        return convertView;
    }
like image 112
user Avatar answered Mar 03 '23 06:03

user