Search code examples
androidlistviewandroid-cursoradapterandroid-listfragment

ListView Requires 2 setSelection's to Scroll to Item


Peculiar problem I'm having with a cursoradapter in a listfragment.

In my onLoadFinished I select the previously selected item in order to scroll the listview to the previous position (and then highlight that item).

This works splendidly, except for the scrolling part.

If I just use one post and delay it (even say 5 seconds), the item gets selected but the list will not scroll (the selected item may out of view at this time) With or without delay same behavior with just one post.

I have to post setSelection AGAIN to get the listview to scroll so the selected item is in view.

It doesn't matter how long I delay the initial or second scroll post.

Here's my grubby workaround, but I'm not pleased with it. Any ideas?

@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
    mAdapter.swapCursor(data);
    getListView().post(new Runnable() {
        @Override
        public void run() {
            getListView().requestFocusFromTouch();
            getListView().setSelection(selectedposition);
            getListView().performItemClick(getListView().getAdapter().getView(selectedposition, null, null), selectedposition, selectedid);
            getListView().clearFocus();
        }    
    });
    getListView().postDelayed(new Runnable() {
        @Override
        public void run() {
            getListView().requestFocusFromTouch();
            getListView().setSelection(selectedposition);
            getListView().performItemClick(getListView().getAdapter().getView(selectedposition, null, null), selectedposition, selectedid);
            getListView().clearFocus();
        }    
    }, 500);
}

Solution

  • Rewrite:

    If I take them out of the runnable it works with calling setselection just once. However then I don't get a delayed scrolling effect (to show the user it scrolled), which I guess I sorta like.

    When I took a closer look at your code I noticed that you were calling getView() in the Adapter manually. Adapters recycle their views in a very particular but unpredictable order and attempting to call getView() yourself might create unwanted behavior... You should avoid doing this, try a different tactic:

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        mAdapter.swapCursor(data); // or changeCursor(data) as explained below
        getListView().postDelayed(new Runnable() {
            @Override
            public void run() {
                ListView listView = getListView(); // Save a local reference rather than calling `getListView()` three times
                listView.setSelection(selectedposition);
                listView.performItemClick(listView.getChildAt(0), selectedposition, selectedposition);
            }
        }, 500);
    }