androidlistviewsectionssectionindexer

Android refresh ListView sections-overlay not working in 4.4


Hi Stackoverflow community,

I am currently playing around with Android ListViewsections capabilties, but there seems to be an issue in Android 4.4 API. I have a CursorAdapter which populates the items from a SQLite database. When the adapter gets created, it sections his items. The user has the posssiblity to change the sorting of the ListView items from A-Z to Z-A. When he does this, a new CursorAdapter will be created and set, which of course indexes his items again. I used the following code to set the new Adapter, which seemed to force the Listview to call getSections() and refreshed the sections when fast scrolling:

//A new instance of adapter is created here with the new cursor, see code at bottom

listView.setFastScrollEnabled(false);
listView.setAdapter(adapter);
listView.setFastScrollEnabled(true);

The above code works great up to Android 4.3, but stopped working in 4.4. In 4.4, the ListView shows the old sections while fast scrolling: So, when the user changed the sorting to Z-A, the ListView still shows this sections-overlay in order A-Z when fast scrolling. I hope you know what I mean.

Is this a known bug and is there a workaround for this?

Thank you very much.

EDIT: This is the code of the adapter I use. It is based on basically 2 cursors. The first one holds the data to show and the second one the indices.

public class SectionCheckableCursorAdapter extends CursorAdapter implements SectionIndexer {

private LayoutInflater mInflater;
private Map<Integer, Object> mSectionIndices = new TreeMap<Integer, Object>();
private Object[] mSections;
private Integer[] mSectionPositions;

public SectionCheckableCursorAdapter(Context context, Cursor c, Cursor indexCursor) {
    super(context, c, false);
    mInflater = LayoutInflater.from(context);   
    buildIndex(indexCursor);
}

private void buildIndex(Cursor indexCursor) {
    //init
    mSectionIndices = new TreeMap<Integer, Object>();
    List<Integer> sectionPositionsList = new ArrayList<Integer>();

    //get column indices from cursor
    int indexFirstLetter = indexCursor.getColumnIndex("firstLetter");
    int indexCount = indexCursor.getColumnIndex("count");

    //build sections
    int currentCount = 0;
    while (indexCursor.moveToNext()) {
        final int count = indexCursor.getInt(indexCount);
        String firstChar = indexCursor.getString(indexFirstLetter);

        if (firstChar == null || firstChar.isEmpty()) {
            firstChar = " ";
        }

        if (StringUtils.isNumber(firstChar)) {
            firstChar = "#";
        }

        if (!mSectionIndices.containsValue(firstChar)) {
            mSectionIndices.put(currentCount, firstChar);
            sectionPositionsList.add(currentCount);
        }

        currentCount += count;
    }

    mSectionPositions = new Integer[sectionPositionsList.size()];
    sectionPositionsList.toArray(mSectionPositions);

    Collection<Object> sections_collection = mSectionIndices.values();
    mSections = new Object[sections_collection.size()];
    sections_collection.toArray(mSections);
}

@Override
public int getPositionForSection(int section) {
    if (section > mSectionPositions.length - 1) {
        if (getCursor() != null)
            return getCursor().getCount() - 1;
        return 0;
    } else {
        return mSectionPositions[section];
    }
}

@Override
public int getSectionForPosition(int position) {
    for (int i = 0; i < mSectionPositions.length-1; i++) {
        if (position >= mSectionPositions[i] && position < mSectionPositions[i+1]) {
            return i;
        }
    }
    return mSectionPositions.length-1;
}

@Override
public Object[] getSections() {
    return mSections;
}

}

This is how the Cursors and Adapter are created, before the Adapter gets set like in the code at top:

//Query for "real" data and the indices. The indices are created with a query because
//this is much faster than iterating through all entries in the "data" cursor
mCursor = mDatabase.rawQuery(query, null);
mIndexCursor = mDatabase.rawQuery(indexQuery, null);
if (mCursor != null) {
    adapter = new SectionCheckableCursorAdapter(getSherlockActivity(), mCursor, mIndexCursor);
}

Solution

  • The only "workaround" I've found so far is to completely remove the old ListView and add a new one to the layout. So before setting the adapter like in my initial post, you could check if the user runs KitKat and then add a new ListView to the layout.

    listView.setFastScrollEnabled(false);
    if (android.os.Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.KITKAT) {
        swapListView();
    }
    listView.setAdapter(adapter);
    listView.setFastScrollEnabled(true);
    

    swapListView() method is simple:

    private void swapListView() {
        //save layout params
        ViewGroup.LayoutParams listViewParams = null;
        if (listView != null) {
            listViewParams = listView.getLayoutParams();
        } else {
            listViewParams = new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        }
    
        //frame is a FrameLayout around the ListView
        frame.removeView(listView);
    
        listView = new ListView(this);
        listView.setLayoutParams(listViewParams);
        //other ListView initialization code like divider settings
    
        frame.addView(listView);
    }
    

    This forces the index/sections to get refreshed after setting the adapter, but it seems like dirty code to me and does not feel like a good solution.