Maintain/Save/Restore scroll position when returning to a ListView
Solution 1:
Try this:
// save index and top position
int index = mList.getFirstVisiblePosition();
View v = mList.getChildAt(0);
int top = (v == null) ? 0 : (v.getTop() - mList.getPaddingTop());
// ...
// restore index and position
mList.setSelectionFromTop(index, top);
Explanation:ListView.getFirstVisiblePosition()
returns the top visible list item. But this item may be partially scrolled out of view, and if you want to restore the exact scroll position of the list you need to get this offset. So ListView.getChildAt(0)
returns the View
for the top list item, and then View.getTop() - mList.getPaddingTop()
returns its relative offset from the top of the ListView
. Then, to restore the ListView
's scroll position, we call ListView.setSelectionFromTop()
with the index of the item we want and an offset to position its top edge from the top of the ListView
.
Solution 2:
Parcelable state;
@Override
public void onPause() {
// Save ListView state @ onPause
Log.d(TAG, "saving listview state");
state = listView.onSaveInstanceState();
super.onPause();
}
...
@Override
public void onViewCreated(final View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Set new items
listView.setAdapter(adapter);
...
// Restore previous state (including selected item index and scroll position)
if(state != null) {
Log.d(TAG, "trying to restore listview state");
listView.onRestoreInstanceState(state);
}
}
Solution 3:
I adopted the solution suggested by @(Kirk Woll), and it works for me. I have also seen in the Android source code for the "Contacts" app, that they use a similar technique. I would like to add some more details: On top on my ListActivity-derived class:
private static final String LIST_STATE = "listState";
private Parcelable mListState = null;
Then, some method overrides:
@Override
protected void onRestoreInstanceState(Bundle state) {
super.onRestoreInstanceState(state);
mListState = state.getParcelable(LIST_STATE);
}
@Override
protected void onResume() {
super.onResume();
loadData();
if (mListState != null)
getListView().onRestoreInstanceState(mListState);
mListState = null;
}
@Override
protected void onSaveInstanceState(Bundle state) {
super.onSaveInstanceState(state);
mListState = getListView().onSaveInstanceState();
state.putParcelable(LIST_STATE, mListState);
}
Of course "loadData" is my function to retrieve data from the DB and put it onto the list.
On my Froyo device, this works both when you change the phone orientation, and when you edit an item and go back to the list.
Solution 4:
A very simple way:
/** Save the position **/
int currentPosition = listView.getFirstVisiblePosition();
//Here u should save the currentPosition anywhere
/** Restore the previus saved position **/
listView.setSelection(savedPosition);
The method setSelection will reset the list to the supplied item. If not in touch mode the item will actually be selected if in touch mode the item will only be positioned on screen.
A more complicated approach:
listView.setOnScrollListener(this);
//Implements the interface:
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
mCurrentX = view.getScrollX();
mCurrentY = view.getScrollY();
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
//Save anywere the x and the y
/** Restore: **/
listView.scrollTo(savedX, savedY);
Solution 5:
I found something interesting about this.
I tried setSelection and scrolltoXY but it did not work at all, the list remained in the same position, after some trial and error I got the following code that does work
final ListView list = (ListView) findViewById(R.id.list);
list.post(new Runnable() {
@Override
public void run() {
list.setSelection(0);
}
});
If instead of posting the Runnable you try runOnUiThread it does not work either (at least on some devices)
This is a very strange workaround for something that should be straight forward.