21

I'm having a bit of trouble preserving the scroll position of a list view when changing it's adapter's data.

What I'm currently doing is to create a custom ArrayAdapter (with an overridden getView method) in the onCreate of a ListFragment, and then assign it to its list:

mListAdapter = new CustomListAdapter(getActivity());
mListAdapter.setNotifyOnChange(false);
setListAdapter(mListAdapter);

Then, when I receive new data from a loader that fetches everything periodically, I do this in its onLoadFinished callback:

mListAdapter.clear();
mListAdapter.addAll(data.items);
mListAdapter.notifyDataSetChanged();

The problem is, calling clear() resets the listview's scroll position. Removing that call preserves the position, but it obviously leaves the old items in the list.

What is the proper way to do this?

urandom
  • 1,147
  • 2
  • 12
  • 21

4 Answers4

34

As you pointed out yourself, the call to 'clear()' causes the position to be reset to the top.

Fiddling with scroll-position, etc. is a bit of a hack to get this working.

If your CustomListAdapter subclasses from ArrayAdapter, this could be the issue:

The call to clear(), calls 'notifyDataSetChanged()'. You can prevent this:

mListAdapter.setNotifyOnChange(false); // Prevents 'clear()' from clearing/resetting the listview
mListAdapter.clear();
mListAdapter.addAll(data.items);
// note that a call to notifyDataSetChanged() implicitly sets the setNotifyOnChange back to 'true'!
// That's why the call 'setNotifyOnChange(false) should be called first every time (see call before 'clear()').
mListAdapter.notifyDataSetChanged(); 

I haven't tried this myself, but try it :)

Streets Of Boston
  • 12,576
  • 2
  • 25
  • 28
  • I have turned off the notifyonchange when I created the list adapter (sorry for not mentioning it in my original post). I didn't know that it would be turned back on when I manually notify though. I will need to test this. – urandom Feb 06 '13 at 09:45
  • 1
    That's the answer. Thanks, much better than the "scroll-after-updating" hacks :) Too bad (this weird, imho) behaviour isn't documented. – urandom Feb 06 '13 at 18:48
  • where i have to use this code ? please let me know this – Prasad Jul 13 '15 at 12:00
  • How is this done in c#? It seems this is Java? Can't find the same methods in c#. – JP Hochbaum Nov 21 '16 at 15:02
6

Check out: Maintain/Save/Restore scroll position when returning to a ListView

Use this to save the position in the ListView before you call .clear(), .addAll(), and . notifyDataSetChanged().

int index = mList.getFirstVisiblePosition();
View v = mList.getChildAt(0);
int top = (v == null) ? 0 : v.getTop();

After updating the ListView adapter, the Listview's items will be changed and then set the new position:

mList.setSelectionFromTop(index, top);

Basically you can save you position and scroll back to it, save the ListView state or the entire application state.

Other helpful links:

Save Position: How to save and restore ListView position in Android

Save State: Android ListView y position

Regards,

Please let me know if this helps!

Community
  • 1
  • 1
Jared Burrows
  • 54,294
  • 25
  • 151
  • 185
  • This kind of works, but it feels a bit hacky. Mostly because, even though the listview itself doesn't move, the scrollbar flashes whenever the update occurs. With updates every second or so, it looks like a giant grey blinking light. I could hide the scrollbar itself, but it is quite useful. – urandom Feb 05 '13 at 23:49
  • This question has been answered many times on here. I was simply giving one example :) – Jared Burrows Feb 05 '13 at 23:51
  • I'll add this to your answer in order to fix my scrollbar blink: `mList.setVerticalScrollBarEnabled(false)` at the beginning and `mList.setVerticalScrollBarEnabled(true)` at the end. Though I'm really hoping for something a bit less hackish :) – urandom Feb 06 '13 at 00:03
  • Ok, check the links. That is really the only 3 solutions possible. – Jared Burrows Feb 06 '13 at 02:20
0

There is one more use-case I came across recently (Android 8.1) - caused by bug in Android code. If I use mouse-wheel to scroll list view - consecutive adapter.notifyDataSetChanged() resets scroll position to zero. Use this workaround until bug gets fixed in Android

        listView.onTouchModeChanged(true); // workaround
        adapter.notifyDataSetChanged();

More details is here: https://issuetracker.google.com/u/1/issues/130103876

Xtra Coder
  • 3,389
  • 4
  • 40
  • 59
-1

In your Expandable/List Adapter, put this method

public void refresh(List<MyDataClass> dataList) {
    mDataList.clear();
    mDataList.addAll(events);
    notifyDataSetChanged();
}

And from your activity, where you want to update the list, put this code

if (mDataListView.getAdapter() == null) {
    MyDataAdapter myDataAdapter = new MyDataAdapter(mContext, dataList);
    mDataListView.setAdapter(myDataAdapter);
} else {
    ((MyDataAdapter)mDataListView.getAdapter()).refresh(dataList);
}

In case of Expandable List View, you will use mDataListView.getExpandableListAdapter() instead of mDataListView.getAdapter()

Yasir Ali
  • 1,785
  • 1
  • 16
  • 21