Maintain/Save/Restore scroll position when returning to a ListView
Asked Answered
O

20

414

I have a long ListView that the user can scroll around before returning to the previous screen. When the user opens this ListView again, I want the list to be scrolled to the same point that it was previously. Any ideas on how to achieve this?

Ottava answered 10/6, 2010 at 11:56 Comment(1)
I think the solutions mentioned by Eugene Mymrin / Giorgio Barchiesi are better than the accepted answerPeacock
I
643

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.

Ito answered 14/6, 2010 at 7:11 Comment(14)
Not 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? :)Plaid
So 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?Ito
This 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.Exoenzyme
Is this supposed to work for ExpandableListViews as well? I'm calling it but it doesn't scroll.Bombazine
This 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.Echidna
Although this answer works, I find the answer #3014589 from Eugene more elegant as it REALLY saves and restores the complete list view state.Shanda
If 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).Paraguay
@Exoenzyme your simplistic solution doesn't work to restore the exact scrolling position.Danczyk
I 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.Fogle
as @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...Occasion
Does not work if the listview has header and footer.Kaleykaleyard
Sweet. For those using it.sephiroth.android.library.widget.HListView (github.com/sephiroth74/HorizontalVariableListView) it is (v.getLeft() - mListView.getPaddingLeft()); to get the padding value.Tribasic
While this might work and give you the behaviour you want, you should consider @Eugene Mymrins answer and use onSaveInstanceState() and onRestoreInstanceState() on the listviewRobison
top no need to minus mList.getPaddingTop(). your idea is right, but top is a mistake. If you check the method setSelectionFromTop(int position, int y), the introduce of y:The distance from the top edge of the ListView (plus padding) that the item will be positioned. I tried myself, your top is wrong. Your solution only work properly when ListView not set paddingTop, if your listview have paddingTop, you can not restore listview position exactly.Apiarian
B
553
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);
    }
}
Butanone answered 16/4, 2011 at 18:6 Comment(18)
I think this is the best answer as this method restores the exact scroll position and not only the first visible item.Peacock
Great answer but won't work when dynamically loading content and you want to save state in the onPause() method.Stans
great 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 worksDownstage
This doesn't work properly if your list items don't all have unique ids. ian's solution does.Nimble
Didn'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.Danczyk
nice, don't forget to call listView.requestFocus(); before calling onRestoreInstanceStateGuadalcanal
@Downstage Did you ever find a solution the list jumping back to the top?Classroom
does it supposed to work when listview has a headerview?Monge
As aaronvargas said, this couldn't work when ListView.getFirstVisiblePosition() is 0.Mellisa
thanks its good example and you used originally android method of codeSafir
This works perfectly and is much cleaner and more reliable than checking first visible position.Helyn
This 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.Walrus
If 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.Avogadro
If your list view depends on CursorLoader then wait until you call ListAdapter.swapCursor(Cursor) before calling ListView.onRestoreInstanceState(Parcelable)Footplate
If 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. #15314098Revocation
Would your code above help me with a RecyclerView list where instancestate is not being saved between activities? I posted the following question here: #35413995?Docker
It work's only when I set the Parcelable state as staticAdolfo
state is allways null on onViewCreated because it is not restoredHighborn
H
54

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.

Holocrine answered 17/4, 2011 at 15:38 Comment(13)
Great Code worked very well with my list view thank you so much..really appreciate itOvercritical
This 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?Echidna
What changes would you make if listview is inside a fragment ?Lottie
FYI 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.Tribrach
onRestoreInstanceState is never called :(Gustatory
@GiorgioBarchiesi Who is @(Kirk Wool) whose solution works for you? The user apparently changed his/her name.Reese
Yes, 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 :-)Holocrine
what if onRestoreInstanceState() is never called !!??Worldly
Would your code above help me with a RecyclerView list where instancestate is not being saved between activities? I posted the following question here: #35413995?Docker
Code 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.Allergist
I dont understand why it needs programmatically calling the ListView's methods onSaveInstanceState() and onRestoreInstanceState(), are not them automatically called by the framework?Thirteenth
@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.Holocrine
@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.Thirteenth
A
26

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);
Amoebocyte answered 10/6, 2010 at 12:38 Comment(6)
there was an error in your answer. The method is called setSelectionHonorarium
The 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.Ottava
Please take a look at my second option. I have just added it to my first answerAmoebocyte
view.getScrollX() and view.getScrollY() always return 0 !Ottava
Returns 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?Plaid
Take 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.Ito
K
17

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.

Krystynakshatriya answered 28/12, 2011 at 13:48 Comment(4)
I experienced the same issue! And setSelectionFromTop() doesn't work.Cartoon
+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.Macro
@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.Bystander
listview.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.Macro
D
11

CAUTION!! There is a bug in AbsListView that doesn't allow the onSaveState() to work correctly if the ListView.getFirstVisiblePosition() is 0.

So If you have large images that take up most of the screen, and you scroll to the second image, but a little of the first is showing, the scroll position Won't be saved...

from AbsListView.java:1650 (comments mine)

// this will be false when the firstPosition IS 0
if (haveChildren && mFirstPosition > 0) {
    ...
} else {
    ss.viewTop = 0;
    ss.firstId = INVALID_POSITION;
    ss.position = 0;
}

But in this situation, the 'top' in the code below will be a negative number which causes other issues that prevent the state to be restored correctly. So when the 'top' is negative, get the next child

// save index and top position
int index = getFirstVisiblePosition();
View v = getChildAt(0);
int top = (v == null) ? 0 : v.getTop();

if (top < 0 && getChildAt(1) != null) {
    index++;
    v = getChildAt(1);
    top = v.getTop();
}
// parcel the index and top

// when restoring, unparcel index and top
listView.setSelectionFromTop(index, top);
Displace answered 25/5, 2013 at 20:35 Comment(2)
It 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?Shorter
@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!Displace
B
7

For some looking for a solution to this problem, the root of the issue may be where you are setting your list views adapter. After you set the adapter on the listview, it resets the scroll position. Just something to consider. I moved setting the adapter into my onCreateView after we grab the reference to the listview, and it solved the problem for me. =)

Bronchoscope answered 25/8, 2016 at 20:25 Comment(0)
L
6
private Parcelable state;
@Override
public void onPause() {
    state = mAlbumListView.onSaveInstanceState();
    super.onPause();
}

@Override
public void onResume() {
    super.onResume();

    if (getAdapter() != null) {
        mAlbumListView.setAdapter(getAdapter());
        if (state != null){
            mAlbumListView.requestFocus();
            mAlbumListView.onRestoreInstanceState(state);
        }
    }
}

That's enough

Loralorain answered 27/2, 2015 at 4:26 Comment(1)
Hi, would your code above help me with a RecyclerView list where instancestate is not being saved between activities? I posted the following question here: #35413995?Docker
L
1

Am posting this because I am surprised nobody had mentioned this.

After user clicks the back button he will return to the listview in the same state as he went out of it.

This code will override the "up" button to behave the same way as the back button so in the case of Listview -> Details -> Back to Listview (and no other options) this is the simplest code to maintain the scrollposition and the content in the listview.

 public boolean onOptionsItemSelected(MenuItem item) {
     switch (item.getItemId()) {
         case android.R.id.home:
             onBackPressed();
             return(true);
     }
     return(super.onOptionsItemSelected(item)); }

Caution: If you can go to another activity from the details activity the up button will return you back to that activity so you will have to manipulate the backbutton history in order for this to work.

Lovato answered 5/7, 2013 at 11:18 Comment(0)
R
1

Isn't simply android:saveEnabled="true" in the ListView xml declaration enough?

Rowdy answered 7/2, 2014 at 23:27 Comment(2)
android:saveEnabled is set to true by default. This doesn't do anything.Alcott
no. that's not enough, you have to follow #3014589 OR #3014589Safir
A
1

BEST SOLUTION IS:

// 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.post(new Runnable() {
    @Override
    public void run() {
      mList.setSelectionFromTop(index, top);
   }
});

YOU MUST CALL IN POST AND IN THREAD!

Auburn answered 13/8, 2016 at 17:55 Comment(0)
D
1

You can maintain the scroll state after a reload if you save the state before you reload and restore it after. In my case I made a asynchronous network request and reloaded the list in a callback after it completed. This is where I restore state. Code sample is Kotlin.

val state = myList.layoutManager.onSaveInstanceState()

getNewThings() { newThings: List<Thing> ->

    myList.adapter.things = newThings
    myList.layoutManager.onRestoreInstanceState(state)
}
Dumm answered 17/7, 2017 at 19:44 Comment(0)
W
0

If you're using fragments hosted on an activity you can do something like this:

public abstract class BaseFragment extends Fragment {
     private boolean mSaveView = false;
     private SoftReference<View> mViewReference;

     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
          if (mSaveView) {
               if (mViewReference != null) {
                    final View savedView = mViewReference.get();
                    if (savedView != null) {
                         if (savedView.getParent() != null) {
                              ((ViewGroup) savedView.getParent()).removeView(savedView);
                              return savedView;
                         }
                    }
               }
          }

          final View view = inflater.inflate(getFragmentResource(), container, false);
          mViewReference = new SoftReference<View>(view);
          return view;
     }

     protected void setSaveView(boolean value) {
           mSaveView = value;
     }
}

public class MyFragment extends BaseFragment {
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
          setSaveView(true);
          final View view = super.onCreateView(inflater, container, savedInstanceState);
          ListView placesList = (ListView) view.findViewById(R.id.places_list);
          if (placesList.getAdapter() == null) {
               placesList.setAdapter(createAdapter());
          }
     }
}
Willing answered 24/9, 2014 at 16:21 Comment(0)
C
0

If you are saving/restoring scroll position of ListView yourself you are essentially duplicating the functionality already implemented in android framework. The ListView restores fine scroll position just well on its own except one caveat: as @aaronvargas mentioned there is a bug in AbsListView that won't let to restore fine scroll position for the first list item. Nevertheless the best way to restore scroll position is not to restore it. Android framework will do it better for you. Just make sure you have met the following conditions:

  • make sure you have not called setSaveEnabled(false) method and not set android:saveEnabled="false" attribute for the list in the xml layout file
  • for ExpandableListView override long getCombinedChildId(long groupId, long childId) method so that it returns positive long number (default implementation in class BaseExpandableListAdapter returns negative number). Here are examples:

.

@Override
public long getChildId(int groupPosition, int childPosition) {
    return 0L | groupPosition << 12 | childPosition;
}

@Override
public long getCombinedChildId(long groupId, long childId) {
    return groupId << 32 | childId << 1 | 1;
}

@Override
public long getGroupId(int groupPosition) {
    return groupPosition;
}

@Override
public long getCombinedGroupId(long groupId) {
    return (groupId & 0x7FFFFFFF) << 32;
}
  • if ListView or ExpandableListView is used in a fragment do not recreate the fragment on activity recreation (after screen rotation for example). Obtain the fragment with findFragmentByTag(String tag) method.
  • make sure the ListView has an android:id and it is unique.

To avoid aforementioned caveat with first list item you can craft your adapter the way it returns special dummy zero pixels height view for the ListView at position 0. Here is the simple example project shows ListView and ExpandableListView restore their fine scroll positions whereas their scroll positions are not explicitly saved/restored. Fine scroll position is restored perfectly even for the complex scenarios with temporary switching to some other application, double screen rotation and switching back to the test application. Please note, if you are explicitly exiting the application (by pressing the Back button) the scroll position won't be saved (as well as all other Views won't save their state). https://github.com/voromto/RestoreScrollPosition/releases

Centiare answered 6/9, 2015 at 16:48 Comment(0)
L
0

For an activity derived from ListActivity that implements LoaderManager.LoaderCallbacks using a SimpleCursorAdapter it did not work to restore the position in onReset(), because the activity was almost always restarted and the adapter was reloaded when the details view was closed. The trick was to restore the position in onLoadFinished():

in onListItemClick():

// save the selected item position when an item was clicked
// to open the details
index = getListView().getFirstVisiblePosition();
View v = getListView().getChildAt(0);
top = (v == null) ? 0 : (v.getTop() - getListView().getPaddingTop());

in onLoadFinished():

// restore the selected item which was saved on item click
// when details are closed and list is shown again
getListView().setSelectionFromTop(index, top);

in onBackPressed():

// Show the top item at next start of the app
index = 0;
top = 0;
Lamoreaux answered 17/8, 2016 at 12:46 Comment(0)
T
0

Neither of the solutions offered here seemed to work for me. In my case, I have a ListView in a Fragment which I'm replacing in a FragmentTransaction, so a new Fragment instance is created each time the fragment is shown, which means that the ListView state can not be stored as a member of the Fragment.

Instead, I ended up storing the state in my custom Application class. The code below should give you an idea how this works:

public class MyApplication extends Application {
    public static HashMap<String, Parcelable> parcelableCache = new HashMap<>();


    /* ... code omitted for brevity ... */
}

 

public class MyFragment extends Fragment{
    private ListView mListView = null;
    private MyAdapter mAdapter = null;


    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        mAdapter = new MyAdapter(getActivity(), null, 0);
        mListView = ((ListView) view.findViewById(R.id.myListView));

        Parcelable listViewState = MyApplication.parcelableCache.get("my_listview_state");
        if( listViewState != null )
            mListView.onRestoreInstanceState(listViewState);
    }


    @Override
    public void onPause() {
        MyApplication.parcelableCache.put("my_listview_state", mListView.onSaveInstanceState());
        super.onPause();
    }

    /* ... code omitted for brevity ... */

}

The basic idea is that you store the state outside the fragment instance. If you don't like the idea of having a static field in your application class, I guess you could do it by implementing a fragment interface and storing the state in your activity.

Another solution would be to store it in SharedPreferences, but it gets a bit more complicated, and you would need to make sure you clear it on application launch unless you want the state to be persisted across app launches.

 

Also, to avoid the "scroll position not saved when first item is visible", you can display a dummy first item with 0px height. This can be achieved by overriding getView() in your adapter, like this:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    if( position == 0 ) {
        View zeroHeightView = new View(parent.getContext());
        zeroHeightView.setLayoutParams(new ViewGroup.LayoutParams(0, 0));
        return zeroHeightView;
    }
    else
        return super.getView(position, convertView, parent);
}
Tychonn answered 5/4, 2018 at 12:54 Comment(0)
P
0

My answer is for Firebase and position 0 is a workaround

Parcelable state;

DatabaseReference everybody = db.getReference("Everybody Room List");
    everybody.addValueEventListener(new ValueEventListener() {
        @Override
        public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
            state = listView.onSaveInstanceState(); // Save
            progressBar.setVisibility(View.GONE);
            arrayList.clear();
            for (DataSnapshot messageSnapshot : dataSnapshot.getChildren()) {
                Messages messagesSpacecraft = messageSnapshot.getValue(Messages.class);
                arrayList.add(messagesSpacecraft);
            }
            listView.setAdapter(convertView);
            listView.onRestoreInstanceState(state); // Restore
        }

        @Override
        public void onCancelled(@NonNull DatabaseError databaseError) {
        }
    });

and convertView

position 0 a add a blank item that you are not using

public class Chat_ConvertView_List_Room extends BaseAdapter {

private ArrayList<Messages> spacecrafts;
private Context context;

@SuppressLint("CommitPrefEdits")
Chat_ConvertView_List_Room(Context context, ArrayList<Messages> spacecrafts) {
    this.context = context;
    this.spacecrafts = spacecrafts;
}

@Override
public int getCount() {
    return spacecrafts.size();
}

@Override
public Object getItem(int position) {
    return spacecrafts.get(position);
}

@Override
public long getItemId(int position) {
    return position;
}

@SuppressLint({"SetTextI18n", "SimpleDateFormat"})
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
    if (convertView == null) {
        convertView = LayoutInflater.from(context).inflate(R.layout.message_model_list_room, parent, false);
    }

    final Messages s = (Messages) this.getItem(position);

    if (position == 0) {
        convertView.getLayoutParams().height = 1; // 0 does not work
    } else {
        convertView.getLayoutParams().height = RelativeLayout.LayoutParams.WRAP_CONTENT;
    }

    return convertView;
}
}

I have seen this work temporarily without disturbing the user, I hope it works for you

Peer answered 21/9, 2018 at 13:43 Comment(0)
Y
0

use this below code :

int index,top;

@Override
protected void onPause() {
    super.onPause();
    index = mList.getFirstVisiblePosition();

    View v = challengeList.getChildAt(0);
    top = (v == null) ? 0 : (v.getTop() - mList.getPaddingTop());
}

and whenever your refresh your data use this below code :

adapter.notifyDataSetChanged();
mList.setSelectionFromTop(index, top);
Ypres answered 9/4, 2019 at 12:20 Comment(0)
H
0

I'm using FirebaseListAdapter and couldn't get any of the solutions to work. I ended up doing this. I'm guessing there are more elegant ways but this is a complete and working solution.

Before onCreate:

private int reset;
private int top;
private int index;

Inside of the FirebaseListAdapter:

@Override
public void onDataChanged() {
     super.onDataChanged();

     // Only do this on first change, when starting
     // activity or coming back to it.
     if(reset == 0) {
          mListView.setSelectionFromTop(index, top);
          reset++;
     }

 }

onStart:

@Override
protected void onStart() {
    super.onStart();
    if(adapter != null) {
        adapter.startListening();
        index = 0;
        top = 0;
        // Get position from SharedPrefs
        SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
        top = sharedPref.getInt("TOP_POSITION", 0);
        index = sharedPref.getInt("INDEX_POSITION", 0);
        // Set reset to 0 to allow change to last position
        reset = 0;
    }
}

onStop:

@Override
protected void onStop() {
    super.onStop();
    if(adapter != null) {
        adapter.stopListening();
        // Set position
        index = mListView.getFirstVisiblePosition();
        View v = mListView.getChildAt(0);
        top = (v == null) ? 0 : (v.getTop() - mListView.getPaddingTop());
        // Save position to SharedPrefs
        SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
        sharedPref.edit().putInt("TOP_POSITION" + "", top).apply();
        sharedPref.edit().putInt("INDEX_POSITION" + "", index).apply();
    }
}

Since I also had to solve this for FirebaseRecyclerAdapter I'm posting the solution here for that too:

Before onCreate:

private int reset;
private int top;
private int index;

Inside of the FirebaseRecyclerAdapter:

@Override
public void onDataChanged() {
    // Only do this on first change, when starting
    // activity or coming back to it.
    if(reset == 0) {
        linearLayoutManager.scrollToPositionWithOffset(index, top);
        reset++;
    }
}

onStart:

@Override
protected void onStart() {
    super.onStart();
    if(adapter != null) {
        adapter.startListening();
        index = 0;
        top = 0;
        // Get position from SharedPrefs
        SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
        top = sharedPref.getInt("TOP_POSITION", 0);
        index = sharedPref.getInt("INDEX_POSITION", 0);
        // Set reset to 0 to allow change to last position
        reset = 0;
    }
}

onStop:

@Override
protected void onStop() {
    super.onStop();
    if(adapter != null) {
        adapter.stopListening();
        // Set position
        index = linearLayoutManager.findFirstVisibleItemPosition();
        View v = linearLayoutManager.getChildAt(0);
        top = (v == null) ? 0 : (v.getTop() - linearLayoutManager.getPaddingTop());
        // Save position to SharedPrefs
        SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
        sharedPref.edit().putInt("TOP_POSITION" + "", top).apply();
        sharedPref.edit().putInt("INDEX_POSITION" + "", index).apply();
    }
}
Hola answered 18/4, 2019 at 2:13 Comment(0)
L
0

To clarify the excellent answer of Ryan Newsom and to adjust it for fragments and for the usual case that we want to navigate from a "master" ListView fragment to a "details" fragment and then back to the "master"

    private View root;
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
        {
           if(root == null){
             root = inflater.inflate(R.layout.myfragmentid,container,false);
             InitializeView(); 
           } 
           return root; 
        }

    public void InitializeView()
    {
        ListView listView = (ListView)root.findViewById(R.id.listviewid);
        BaseAdapter adapter = CreateAdapter();//Create your adapter here
        listView.setAdpater(adapter);
        //other initialization code
    }

The "magic" here is that when we navigate back from the details fragment to the ListView fragment, the view is not recreated, we don't set the ListView's adapter, so everything stays as we left it!

Latchet answered 14/10, 2019 at 12:32 Comment(1)
not working if you load data from a serverConfect

© 2022 - 2024 — McMap. All rights reserved.