How to Maintain Scroll position of recyclerView on Screen Rotation
Asked Answered
A

9

5

I'm populating the recyclerView with gridlayoutManager. Now I want to save the scroll position on Screen Rotation.

I've tried to do so using onSaveInstanceState and onRestoreInstanceState() as shown in this post :

How to save RecyclerView's scroll position using RecyclerView.State?

Below is my code:

@Override
protected void onSaveInstanceState(Bundle outState) {
    Log.e(TAG, "onSaveInstanceState");
    super.onSaveInstanceState(outState);
    outState.putParcelable(KEY_INSTANCE_STATE_RV_POSITION, 
    gridLayoutManager.onSaveInstanceState());
 }


@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {

    Log.e(TAG, "onRestoreInstanceState");
    super.onRestoreInstanceState(savedInstanceState);
    if (savedInstanceState!= null){
        Parcelable savedState = 
    savedInstanceState.getParcelable(KEY_INSTANCE_STATE_RV_POSITION);

        movieAdapter.addAll(movieItemList);
        if (savedState!= null){
            gridLayoutManager.onRestoreInstanceState(savedState);
        }
    }
}


//this is my movieAdapter.addAll() method 

public void addAll(List<MovieItem>items){

    movieItems = items;
}


//This is the method to get lists of movies from ViewModel Class

private void loadFavMovies() {
    FavViewModel favViewModel = 
    ViewModelProviders.of(MainActivity.this).get(FavViewModel.class);
    favViewModel.getFavListLiveData().observe(MainActivity.this, new 
    Observer<List<FavlistItem>>() {
        @Override
        public void onChanged(List<FavlistItem> favlistItems) {
            if (!favlistItems.isEmpty()) {
                loadingIndicator.setVisibility(View.GONE);
                movieRecycler.setVisibility(View.GONE);
                favRecycler.setVisibility(View.VISIBLE);
                favAdapter.setFavlistItems(favlistItems);
                Toast.makeText(getApplicationContext(), "Swipe Left Or 
       Right To remove Item",Toast.LENGTH_SHORT).show();
               }else {
                loadingIndicator.setVisibility(View.GONE);
                Toast.makeText(getApplicationContext(), "No Favorite 
      Movies",Toast.LENGTH_SHORT).show();
            }
        }
    });
}

This is the link to GitHub for this project https://github.com/harshabhadra/Movies-Mela

Adjournment answered 16/7, 2019 at 16:3 Comment(3)
you can check this.... #36568668. &. #27816717Prosector
Possible duplicate of How to save RecyclerView's scroll position using RecyclerView.State?Scarcely
Approximate scroll positions are already typically saved by the layout manager during rotations. What LayoutManager are you using? If it's the stock GridLayoutManager, there shouldn't be any additional work you need to do.Aloysius
A
0

I've tried everything suggested by everyone but finally, this gist work for me

https://gist.github.com/FrantisekGazo/a9cc4e18cee42199a287

I just import this in my project and replace the recyclerView provided by default with this "StateFulRecyclerView" and it solved my problem and handle the scroll position automatically during screen rotation or any configuration change. No need to use onSaveInstanceState to maintain scroll positon.

Adjournment answered 23/7, 2019 at 7:35 Comment(0)
F
8

I know i am late but still if it helps!!

Storing the recycler view position is lot simpler than what other answers have made it look like

Here's how you can do it

First create a member variable

    Parcelable state;

Now,

@Override
protected void onPause() {
    super.onPause();
    state = recyclerView.getLayoutManager().onSaveInstanceState();
}

@Override
protected void onResume() {
    super.onResume();
        recyclerView.getLayoutManager().onRestoreInstanceState(state);
}

overide on pause and on resume methods with the above code and you are good to go!!

Folklore answered 9/1, 2021 at 17:24 Comment(0)
A
7

Now,It has simple solution.use this dependency

    implementation "androidx.recyclerview:recyclerview:1.2.0-alpha05"

And just set the stateRestorationPolicy like below

myAdapter.stateRestorationPolicy=RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY
Alden answered 21/9, 2020 at 7:3 Comment(9)
is this stateRestorationPolicy working? Because I did implement it but when my view re-created, the recyclerview didn't scroll back to its original positionDruce
It's working well with the navigation component. I haven't tried with othersAlden
do you have any example on this? I am using navigation component as well. but seems after navigate from B to A, recyclerview in A is re-created and didn't restore its positionDruce
It restores the position only on configuration changes. but, when navigating from B to A fragment navigation component will create a new instance of A fragment.Alden
Then how can I retain the recyclerview's position for this? any idea?Druce
your need is to restore recyclerview's position from the existing fragment in the stack right?Alden
Yea, I need to store that position, but I search for long time didn't find any solutionDruce
Let us continue this discussion in chat.Alden
Ensure that after you set the restoration policy that you do not recreate the adapter or layout manager. Also, adding android:configChanges="orientation" to the activity in the manifest will prevent the activity from having to restore state.Maddux
D
1

One way to do that would be to stop re-creation of activity on orientation change. You can add that to the activity in the manifest to do so.

android:configChanges="orientation|screenSize"

But that isn't always a good practice, so another alternative would be to save the adapter position before exiting in onSaveInstanceState and then scroll to that position in onCreate using scrollToPosition. So, for example, you'd do something along the following lines.

    @Override
    public void onSaveInstanceState(Bundle savedInstanceState) {
      savedInstanceState.putInt("position", mRecyclerView.getAdapterPosition()); // get current recycle view position here.
      //your other code
      super.onSaveInstanceState(savedInstanceState);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      //your other code
      if(savedInstanceState != null){
        // scroll to existing position which exist before rotation.

  mRecyclerView.scrollToPosition(savedInstanceState.getInt("position"));
      }
   }
Deltoid answered 16/7, 2019 at 16:10 Comment(3)
Depends, most would consider it a code smell, since activity recreation ensure that your layout resizes and positions everything properly. But if your activity is very minimal, then it's usually sufficient.Deltoid
I think this is useful when I've multiple horizontal recyclerView in an activity but in this case, I'm calculating the span count of gridLayout dynamically so it's not a good idea.Adjournment
I see, glad you found the answer you needed, and even gladder that you added the answer here. Cheers mate.Deltoid
S
1

You should check out this SO post.

Basically, you want to create a new class extending RecyclerView (remember you will have to use your new class instead of RecyclerView) and override onSaveInstanceState() and onRestoreInstanceState(). In onSaveInstanceState() you will save the index of the first visible element and scroll to that element in onRestoreInstanceState().

The way they did it in the accepted answer of the linked post is different, but used LinearLayoutManager instead so I will tailor the code to use GridLayoutManager:

public class CustomRecyclerView extends RecyclerView {
    private int mScrollPosition;
    public VacationsRecyclerView(@NonNull Context context) {
        super(context);
    }

    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        super.onRestoreInstanceState(state);
        if(state != null && state instanceof SavedState){
            mScrollPosition = ((SavedState) state).mScrollPosition;
            LayoutManager layoutManager = getLayoutManager();
            if(layoutManager != null){
                int count = layoutManager.getItemCount();
                if(mScrollPosition != RecyclerView.NO_POSITION && mScrollPosition < count){
                    layoutManager.scrollToPosition(mScrollPosition);
                }
            }
        }
    }

    @Override
    protected Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();
        LayoutManager layoutManager = getLayoutManager();
        if(layoutManager != null && layoutManager instanceof LinearLayoutManager){
            mScrollPosition = ((GridLayoutManager) layoutManager).findFirstVisibleItemPosition();
        }
        SavedState newState = new SavedState(superState);
        newState.mScrollPosition = mScrollPosition;
        return newState;
    }

    static class SavedState extends android.view.View.BaseSavedState {
        public int mScrollPosition;
        SavedState(Parcel in) {
            super(in);
            mScrollPosition = in.readInt();
        }
        SavedState(Parcelable superState) {
            super(superState);
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            super.writeToParcel(dest, flags);
            dest.writeInt(mScrollPosition);
        }
        public static final Parcelable.Creator<SavedState> CREATOR
                = new Parcelable.Creator<SavedState>() {
            @Override
            public SavedState createFromParcel(Parcel in) {
                return new SavedState(in);
            }

            @Override
            public SavedState[] newArray(int size) {
                return new SavedState[size];
            }
        };
    }
}
Scarcely answered 16/7, 2019 at 16:17 Comment(0)
A
1

Scroll position should already be automatically saved if you are using GridLayoutManager, which subclasses LinearLayoutManager. You can take a look at SavedState inside LinearLayoutManager:

public static class SavedState implements Parcelable {

    int mAnchorPosition;

    int mAnchorOffset;

    boolean mAnchorLayoutFromEnd;

That mAnchorPosition is effectively your scrolling position and it's saved during rotations. If that's not working for you, something else is likely wrong : you might be reloading / reapplying data incorrectly for example. That is probably the real question you need to be asking. You might need to cache that data somewhere so that you can automatically reapply it immediately after the configuration change.

I can also confirm for what its worth that on every project I've used LinearLayoutManager and GridLayoutManager on, scroll position is correctly maintained during rotations with no additional work on my part.

Aloysius answered 17/7, 2019 at 15:2 Comment(0)
J
0

The easiest way to achieve what you're trying to get would be to use the RecyclerView within a Fragment instead of directly within an Activity.

Unlike activities, fragments by default don't get re-created on rotation, but preserve their state. This way, the recyclerview will always remain at the same scroll position.

I am using a RecyclerView this way in one of my apps, which displays lists with > 200 elements.

Jennijennica answered 16/7, 2019 at 18:33 Comment(2)
actually, it's a project I've to submit and I've to define the recyclerView in an activity and maintain the scroll position on Screen Rotation.Adjournment
This isn't really accurate : Fragments, just like Activities, are destroyed and recreated during rotations. So this suggestion wouldn't really make a difference.Aloysius
A
0

I've tried everything suggested by everyone but finally, this gist work for me

https://gist.github.com/FrantisekGazo/a9cc4e18cee42199a287

I just import this in my project and replace the recyclerView provided by default with this "StateFulRecyclerView" and it solved my problem and handle the scroll position automatically during screen rotation or any configuration change. No need to use onSaveInstanceState to maintain scroll positon.

Adjournment answered 23/7, 2019 at 7:35 Comment(0)
B
0

very simple. save the position on pause and restore is onresume.

ON pause

lastVisitPosition = ((LinearLayoutManager)recycleView.getLayoutManager()).findFirstCompletelyVisibleItemPosition();

onResume

((LinearLayoutManager) recycleView.getLayoutManager()).scrollToPosition(lastVisitPosition);

or

((LinearLayoutManager) recycleView.getLayoutManager()).scrollToPositionWithOffset(lastVisitPosition,0)
Bosworth answered 24/3, 2021 at 20:56 Comment(0)
W
0

One solution is by saving scroll positions of recycleview

in onSaveInstanceState() method of fragment you can save the scroll position of RecycleView

@Override
  public void onSaveInstanceState(Bundle outState) {
   super.onSaveInstanceState(outState);
   LinearLayoutManager layoutManager = (LinearLayoutManager) 
   recyclerView.getLayoutManager();
   outState.putInt("scrolled_position", 
   layoutManager.findFirstCompletelyVisibleItemPosition());
}

then you can retrieve saved scroll position in onViewStateRestored() method

@Override
  public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
  super.onViewStateRestored(savedInstanceState);
  if (savedInstanceState != null) {
    int scrollPosition = savedInstanceState.getInt("scrolled_position");
    recyclerView.scrollToPosition(scrollPosition);
  }
}
Wernerwernerite answered 23/1, 2023 at 8:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.