25

I have Recycler view which lays inside of SwipeRefreshLayout. Also, have ability to open each item in another activity. After returning back to Recycler I need scroll to chosen item, or to previous Y. How to do that?

Yes, I googled, found articles in StackOverFlow about saving instance of layout manager, like this one: RecyclerView store / restore state between activities. But, it doesn't help me.

UPDATE

Right now I have this kind of resolving problem, but, of course, it also doesn't work.

private int scrollPosition;

...//onViewCreated - it is fragment
recyclerView.setHasFixedSize(true);
LinearLayoutManager llm = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(llm);
data = new ArrayList<>();
adapter.setData(getActivity(), data);
recyclerView.setAdapter(adapter);
...

@Override
public void onResume() {
    super.onResume();
    recyclerView.setScrollY(scrollPosition);
}

@Override
public void onPause() {
    super.onPause();
    scrollPosition = recyclerView.getScrollY();
}

Yes, I have tried scrollTo(int, int) - doen't work.

Now I tried just scroll, for example, to Y = 100, but it doesn't scrolling at all.

Community
  • 1
  • 1
DefaultXYZ
  • 549
  • 1
  • 5
  • 18

9 Answers9

53

Save the current state of recycle view position @onPause:

    positionIndex= llManager.findFirstVisibleItemPosition();
    View startView = rv.getChildAt(0);
    topView = (startView == null) ? 0 : (startView.getTop() - rv.getPaddingTop());

Restore the scroll position @onResume:

    if (positionIndex!= -1) {
        llManager.scrollToPositionWithOffset(positionIndex, topView);
    }

or another way can be @onPause:

long currentVisiblePosition = 0;
currentVisiblePosition = ((LinearLayoutManager)rv.getLayoutManager()).findFirstCompletelyVisibleItemPosition();

restore @onResume:

((LinearLayoutManager) rv.getLayoutManager()).scrollToPosition(currentVisiblePosition);
currentVisiblePosition = 0;
Madhukar Hebbar
  • 3,113
  • 5
  • 41
  • 69
31

A lot of these answers seem to be over complicating it.

The LayoutManager supports onRestoreInstanceState out of the box so there is no need to save scroll positions etc. The built in method already saves pixel perfect positions.

example fragment code (null checking etc removed for clarity):

private Parcelable listState;
private RecyclerView list;

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    listState=savedInstanceState.getParcelable("ListState");

}

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);

    outState.putParcelable("ListState", list.getLayoutManager().onSaveInstanceState());

}

then just call

list.getLayoutManager().onRestoreInstanceState(listState);

once your data has been reattached to your RecyclerView

Kuffs
  • 35,581
  • 10
  • 79
  • 92
  • 3
    WoW! Easy and simple solution :) – Vinay Vissh Sep 21 '17 at 09:09
  • 2
    I think this is the best solution!! – illusionJJ Feb 10 '18 at 19:20
  • 1
    I had to add `android:configChanges="orientation|screenSize" ` to AndroidManifest.xml. Than the code above worked. Thanks – Leonardo Assunção Mar 06 '18 at 12:01
  • 2
    @LeonardoAssunção Then you did it wrong. That is not required and not recommended. – Kuffs Mar 06 '18 at 13:54
  • @Kuffs I have a similar issue here: stackoverflow.com/questions/52176812/…. I would appreciate any insights you have on how to solve. – AJW Sep 05 '18 at 04:04
  • 1
    Does this work when navigating back from another activity? – tccpg288 Oct 27 '18 at 19:59
  • 1
    @tccpg288 it wont work in that case because onSaveIntanceState() wont be called. However, since `listState` is defined as a global variable, u may use onStop() of the activity to assign a 'Parcelable' to listState but only if it is null, e.g., `if (listState == null) listState = list.getLayoutManager().onSaveInstanceState()`. Hopefully, it should work. Do post ur findings! – Shahood ul Hassan Nov 11 '18 at 02:26
  • 2
    Since I'm using fragments, I have the above code line in `onDestroyView()` instead of `onStop()`. @Kuffs your tip combined with this solution really solved my problem. Thanks! – Shahood ul Hassan Nov 11 '18 at 03:00
  • So that implementation handled both screen rotation and navigating back from another fragment? @ShahoodulHassan – tccpg288 Nov 12 '18 at 23:45
  • @tccpg288 Yep...@Kuffs solution dealt with screen rotation. For navigating back from another fragment, I had to add that line in onDestroyView. So yes, this implementation handled both scenarios. – Shahood ul Hassan Nov 13 '18 at 01:46
  • 2
    hi where we can call this method list.getLayoutManager().onRestoreInstanceState(listState); – Darshan Khatri Oct 23 '19 at 13:29
  • 1
    can anyone tell where do i put this line list.getLayoutManager().onRestoreInstanceState(listState); –  May 28 '21 at 12:23
  • The answer already say this: "once your data has been reattached to your RecyclerView" – Kuffs May 29 '21 at 14:04
6

Beginning from version 1.2.0-alpha02 of androidx recyclerView library, it is now automatically managed. Just add it with:

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

And use:

adapter.stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY

The StateRestorationPolicy enum has 3 options:

  • ALLOW — the default state, that restores the RecyclerView state immediately, in the next layout pass
  • PREVENT_WHEN_EMPTY — restores the RecyclerView state only when the adapter is not empty (adapter.getItemCount() > 0). If your data is loaded async, the RecyclerView waits until data is loaded and only then the state is restored. If you have default items, like headers or load progress indicators as part of your Adapter, then you should use the PREVENT option, unless the default items are added using MergeAdapter. MergeAdapter waits for all of its adapters to be ready and only then it restores the state.
  • PREVENT — all state restoration is deferred until you set ALLOW or PREVENT_WHEN_EMPTY.

Note that at the time of this answer, recyclerView library is still in alpha03, but alpha phase is not suitable for production purposes.

Rubén Viguera
  • 3,277
  • 1
  • 17
  • 31
  • This just made restoring state 100 times easier. Thanks for pointing this out! Worked perfectly and I'm using `1.2.0-alpha05` in production. – gMale Aug 27 '20 at 06:34
2

User your recycler view linearlayoutmanager for getting scroll position

int position = 0;
if (linearLayoutManager != null) {
   scrollPosition = inearLayoutManager.findFirstVisibleItemPosition();
}

and when restoring use following code

if (linearLayoutManager != null) {
  cardRecyclerView.scrollToPosition(mScrollPosition);
}

Hope this helps you

Nikhil
  • 3,711
  • 8
  • 32
  • 43
0

to save position to Preferences, add this to your onStop()

 int currentVisiblePosition = ((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstCompletelyVisibleItemPosition();
 getPreferences(MODE_PRIVATE).edit().putInt("listPosition", currentVisiblePosition).apply();

then restore position like this

 if (getItemCount() == 0) {
     int savedListPosition = getPreferences(MODE_PRIVATE).getInt("listPosition", 0);
     recyclerView.getLayoutManager().scrollToPosition(savedListPosition); }

this last code should be added inside an event of the Adapter (not sure witch event but in my case was onEvent() - com.google.firebase.firestore.EventListener)

Dan Alboteanu
  • 9,404
  • 1
  • 52
  • 40
0

For some reason there are a lot of quite misleading tips/suggestions on how to save and restore scroll position in your_scrolling_container upon orientation changes.

Taking current scroll position and saving it in Activity’s onSaveInstanceState Extending a certain scrollable View to do same there Preventing Activity from being destroyed on rotation And yeah, they are working fine, but…

But in fact, everything is much simpler, because Android is already doing it for you!

If you take a closer look at RecyclerView/ListView/ScrollView/NestedScrollView sources, you’ll see that each of them is saving its scroll position in onSaveInstanceState. And during the first layout pass they are trying to scroll to this position in onLayout method.

There are only 2 things you need to do, to make sure it’s gonna work fine:

  1. Set an id for your scrollable view, which is probably already done. Otherwise Android won’t be able to save View state automatically.

  2. Provide a data before the first layout pass, to have the same scroll boundaries you had before rotation. That’s the step where developers usually have some issues.

0

The easiest and transition compatible way I found is:

   @Override
public void onPause() {
    super.onPause();
    recyclerView.setLayoutFrozen(true);
}

@Override
public void onResume() {
    super.onResume();
    recyclerView.setLayoutFrozen(false);
}
Shayki Abramczyk
  • 36,824
  • 16
  • 89
  • 114
Mikias
  • 131
  • 2
  • 3
0

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);
  }
}
-1

You can use scrollToPosition or smoothScrollToPosition to scroll to any item position in RecyclerView.

If you want to scroll to item position in adapter, then you would have to use adapter's scrollToPosition or smoothScrollToPosition.

Rohit Arya
  • 6,751
  • 1
  • 26
  • 40