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.
rantravee
Updated on April 16, 2021Comments
-
rantravee about 3 years
I have a long
ListView
that the user can scroll around before returning to the previous screen. When the user opens thisListView
again, I want the list to be scrolled to the same point that it was previously. Any ideas on how to achieve this? -
SHRISH M almost 14 yearsthere was an error in your answer. The method is called setSelection
-
rantravee almost 14 yearsThe idea works well, but there is a pseudo problem.The first visible position might be shown only a little bit, (maybe just a small part of the row) , and setSelection() set this row to be completely visible when the restore is made.
-
Francesco Laurita almost 14 yearsPlease take a look at my second option. I have just added it to my first answer
-
rantravee almost 14 yearsview.getScrollX() and view.getScrollY() always return 0 !
-
HXCaine over 13 yearsReturns 0 for me too, I've been trying to do the second method for ages but always get 0. The first method is insufficient for large list items. Anybody?
-
HXCaine over 13 yearsNot really understanding how this works. I'm using listView.getFirstVisiblePosition() only and understand that, but not sure what's going on here. Any chance of a brief explanation? :)
-
ian over 13 yearsSo 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() 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. All clear?
-
ian over 13 yearsTake a look at my (accepted) solution above using View.getChildAt() and View.getTop(). You can't use View.getScrollY() on a ListView because a ListView maintains its scrolling internally.
-
Phil over 12 yearsThis can be made even simpler be using one line to save:
int index = mList.getFirstVisiblePosition();
and only one line to restore:mList.setSelectionFromTop(index, 0);
. Great answer though (+1)! I have been looking for an elegant solution to this problem. -
ofavre about 12 yearsI experienced the same issue! And
setSelectionFromTop()
doesn't work. -
Mira Weller almost 12 yearsI think this is the best answer as this method restores the exact scroll position and not only the first visible item.
-
Gio over 11 yearsGreat answer but won't work when dynamically loading content and you want to save state in the onPause() method.
-
Cal over 11 yearsIs this supposed to work for ExpandableListViews as well? I'm calling it but it doesn't scroll.
-
Omar Rehman over 11 years+1. listnew.post(), doesn't work on some devices, and on some other devices it does not work in certain cases, but runOnUIThread works fine.
-
Dan J over 11 years@Omar, can you give some examples of devices and OSes that this doesn't work on? I tried an HTC G2 (2.3.4) and a Galaxy Nexus (4.1.2) and it worked on both. None of the other answers in this thread worked for any of my devices.
-
Omar Rehman over 11 yearslistview.post works fine on my Galaxy Note with 4.0.4, but doesn't work on my Nexus One with 2.3.6. However, onOnUIThread(action) works fine on both devices.
-
user1106888 about 11 yearsGreat Code worked very well with my list view thank you so much..really appreciate it
-
Bryan about 11 yearsThis isn't working for me. I'm using a listview with data from an SQLite database. When I first load the listview activity, and scroll down 10-20 items, then I change to another application on my device. When I open my listview application again, the listview is at the very top of the list. The 'index' and 'top' variables are both 0 in the onRestore method.
-
Bryan about 11 yearsThis solution worked for me. The others did not. How does saving/restoring the ListView instance state behave with regards to deleting and inserting items in the ListView?
-
Lars Blumberg about 11 yearsAlthough this answer works, I find the answer stackoverflow.com/questions/3014089/… from Eugene more elegant as it REALLY saves and restores the complete list view state.
-
JesseBuesking almost 11 yearsIf the technique in the link that @LarsBlumberg added doesn't work for you, I've created a gist that contains ian's technique, but supports lists that dynamically grow (reaching bottom gets more items).
-
passsy almost 11 yearsgreat solution. Just one Problem. If the items are big and you scroll, but the first item is still visible the list jumps back to top. if you scroll to the second item or anywhere else everything works
-
Scott Kennedy over 10 yearsThis doesn't work properly if your list items don't all have unique ids. ian's solution does.
-
User over 10 years@Phil your simplistic solution doesn't work to restore the exact scrolling position.
-
User over 10 yearsDidn't work in my case, I save in onPause and restore in onCreateView() (using the list in a fragment). When I navigate back to the fragment this state is restored and then the list is not visible.
-
RightHandedMonkey about 10 yearsI was using a loader on a ListFragment and I added this code to the ListFragment.onLoadFinished(...) method. Worked like a champ. Still feels like a hack though. It seems that the ArrayAdapter setData(..) is calling clear() which appears the culprit for me. It may be possible to correct it by not calling clear, but that makes the code more complicated. It also may be possible to correct this using a CursorLoader instead of a Custom Loader.
-
l0gg3r about 10 yearsnice, don't forget to call listView.requestFocus(); before calling onRestoreInstanceState
-
Papajohn000 about 10 years@passsy Did you ever find a solution the list jumping back to the top?
-
Shakti Malik about 10 yearsWhat changes would you make if listview is inside a fragment ?
-
Mgamerz about 10 yearsFYI if you use this in a fragment (I'm using a list fragment) and navigate to other fragments, this will crash because the content view is not created when calling
mListState = getListView().onSaveInstanceState().
Mainly on device rotation. -
Piotr almost 10 yearsdoes it supposed to work when listview has a headerview?
-
Brad almost 10 yearsandroid:saveEnabled is set to true by default. This doesn't do anything.
-
VinceStyling almost 10 yearsAs aaronvargas said, this couldn't work when ListView.getFirstVisiblePosition() is 0.
-
jdowdell almost 10 yearsas @nbarraille said, the code should read more like "v.getTop() - mList.getPaddingTop()." Otherwise you'll spend an hour like I did trying to figure out why it always restores just a hair off...
-
Rahul Mandaliya over 9 yearsthanks its good example and you used originally android method of code
-
Rahul Mandaliya over 9 yearsno. that's not enough, you have to follow stackoverflow.com/questions/3014089/… OR stackoverflow.com/questions/3014089/…
-
virsir over 9 yearsDoes not work if the listview has header and footer.
-
Dmitry over 9 years
onRestoreInstanceState
is never called :( -
Piovezan about 9 years@GiorgioBarchiesi Who is @(Kirk Wool) whose solution works for you? The user apparently changed his/her name.
-
speedynomads about 9 yearsThis works perfectly and is much cleaner and more reliable than checking first visible position.
-
Aritra Roy about 9 yearsThis is the best solution to this problem. Less lines of code and works flawlessly. I didn't have any problem with this. Thanks a lot.
-
Giorgio Barchiesi about 9 yearsYes, that's the official religion. But in my case the data is loaded locally, has very limited length, and loads in a split second, so I went erethic :-)
-
eselk almost 9 yearsIf the view is destroyed, like when rotating, the state variable will be null when recreated. If you experience this issue, like I did, see Giorgio's answer which saves/restores the variable.
-
st_bk over 8 yearsSweet. For those using it.sephiroth.android.library.widget.HListView (github.com/sephiroth74/HorizontalVariableListView) it is (v.getLeft() - mListView.getPaddingLeft()); to get the padding value.
-
hidro over 8 yearsIf your list view depends on
CursorLoader
then wait until you callListAdapter.swapCursor(Cursor)
before callingListView.onRestoreInstanceState(Parcelable)
-
Someone Somewhere about 8 yearswhat if
onRestoreInstanceState()
is never called !!?? -
Thupten about 8 yearsIf you press back from activity B and your fragment was in activity A. Then activity A might have already been destroyed and so your fragment and the above listview's
state
is gone too. So the fragment's state need to be stored/restored on activity A. check this link. stackoverflow.com/questions/15313598/… -
AJW about 8 yearsWould your code above help me with a RecyclerView list where instancestate is not being saved between activities? I posted the following question here: stackoverflow.com/questions/35413495/…?
-
AJW about 8 yearsWould your code above help me with a RecyclerView list where instancestate is not being saved between activities? I posted the following question here: stackoverflow.com/questions/35413495/…?
-
AJW about 8 yearsHi, would your code above help me with a RecyclerView list where instancestate is not being saved between activities? I posted the following question here: stackoverflow.com/questions/35413495/…?
-
BMB about 8 yearsCode worked perfectly for my needs, doesn't crash on orientation change either (at least on my devices, two different phones and a tablet), as some have suggested.
-
GPack almost 8 yearsI dont understand why it needs programmatically calling the ListView's methods onSaveInstanceState() and onRestoreInstanceState(), are not them automatically called by the framework?
-
Giorgio Barchiesi almost 8 years@GPack: my solution was tested on Froyo, and needed those calls, I have kept them as-is in more recent Android versions, and never tested if you can avoid the mentioned calls. But I don't think the O.S. does it automatically as you suggest.
-
GPack almost 8 years@GiorgioBarchiesi I tried it on Android 4/5/6, I can confirm that the ListView's callbacks are always automatically called by the framework. Not tried on Android 2.2., but I think that are called on it too. onRestoreInstanceState() is called before the onResume() callback and restores the selected item and scroll position.
-
tafi almost 8 yearsIt works like a charm but I don't fully understand it. If the first item is still visible, how does it make sense to get the next child? Shouldn't setSelectionFromTop with the new index cause the list to be shown starting from the second child? How am I still seeing the first one?
-
tmele54 almost 8 years@tafi, The first item could be 'partially' visible. So the top of that item would be above the screen, and thus would be a negative number. (This causes other issues that I don't recall...) So we use the 'top' of the second Item for placement, which should always(?) be available, or there wouldn't be any scrolling in the first place!
-
Ajay Sivan over 7 yearsIt work's only when I set the
Parcelable state
asstatic
-
xxtesaxx over 7 yearsWhile this might work and give you the behaviour you want, you should consider @Eugene Mymrins answer and use onSaveInstanceState() and onRestoreInstanceState() on the listview
-
Roel about 7 yearsstate is allways null on onViewCreated because it is not restored
-
Loyea over 4 years
top
no need to minusmList.getPaddingTop()
. your idea is right, buttop
is a mistake. If you check the methodsetSelectionFromTop(int position, int y)
, the introduce ofy
:The distance from the top edge of the ListView (plus padding) that the item will be positioned. I tried myself, yourtop
is wrong. Your solution only work properly when ListView not setpaddingTop
, if your listview havepaddingTop
, you can not restore listview position exactly. -
NizarETH over 3 yearsnot working if you load data from a server