RecyclerView is a fundamental Android component used to build many screens. Its flexibility gives us the power to build complex UI as a vertical list containing carousels as items.
If you ever tried to create an interface similar to the above image, you probably faced losing the scroll state position of the nested RecyclerViews. Let’s understand why it happens and what is a possible solution.
Let’s consider the following example:
The straightforward implementation is: a RecyclerView using a vertical LinearLayoutManager and a ListAdapter handling 2 types, one of which is a View containing a RecyclerView using a horizontal LinearLayoutManager. This combination creates what we call nested RecyclerViews.
The example has two different state problems:
- When scrolling vertically, horizontal scroll state positions become a mess.
- When navigating forward and back, nested RecyclerViews lose their horizontal scroll position, scrolling to the initial position.
Under the hood
First, we need to understand how the component works before proposing a solution. We have two different problems caused by two different behaviours.
First scenario: Scrolling vertically
Source of the problem: View recycling
RecyclerView has a view recycling mechanism to display content in a performative way. It has a pool of views and only a minimum quantity of instances is created to be reused. When we scroll up, the view that went out from RecyclerView bounds is recycled and added to the pool. For the view that will appear on the bottom, a view is requested from the pool or a new instance is created if none is available.
Therefore, our view containing the nested RecyclerView is being reused and the scroll position is at the same state it was when recycled. This happens because the state position is not something we set
Second scenario: Navigation
Source of the problem: View restoration
But in our scenario, only the main RecyclerView has its scroll state restored. This happens because each view of the hierarchy starting from the root is responsible to call the function to store and restore state. RecyclerView implementation calls
onRestoreInstanceState() from only its LinearLayoutManager (that holds the scroll position state) and do not make the call to its nested components.
I'm presenting a possible implementation using ListAdapter inheritance but we can adapt it using composition.
For the first problem, we need to store the state for each view that is being recycled and restore it
For the second problem, we also need to save the state from the visible views. We track them when
onBindViewHolder() is called and save the state when a new
submitList() is called. This implementation also works for content refreshing.
I'll not go through the implementation details. It should be clear reading the code comments and running the sample project. Also, this solution does not cover the Fragment recreation scenario but it's viable using the same solution key points.
RecyclerView is a complex component that hides many "secrets". By understanding how it works, it's possible to fill some gaps as we did for nested RecyclerViews.