Pavel Shmakov

introduce

In this series, I will share my knowledge of the inner workings of RecyclerView. Why is that?
Consider this: Nearly every modern Android app requires RecyclerView, so the way developers use it affects the experience of thousands of users. But what kind of materials do we have in RecyclerView?
You can certainly find some basic tutorials on how to use RecyclerView, but how does it work?

The “black box” approach is never good enough, especially for complex customizations or performance tuning. [creates] (https://android.jlelse.eu/anatomy-of-recyclerview-part-1-a-search-for-a-viewholder-404ba3453714#aa26) where the “deepest” material could be [Re CyclerView] (https://www.youtube.com/watch?v=LqBlYJTfLP4) and internal external I suggest to deliver a speech on the Google I/O 2016, but I will seriously, it is not even close to “the introduction”, this is just the tip of the iceberg. My goal is to go one step further. I assume that the reader has a basic knowledge of RecyclerView, such as LayoutManager, how to notify Adapter of specific changes in data, or how to use project view types.
In Part one, we’re going to use a method in RecyclerView to figure out what’s going on, right? ‘getViewByPosition()’ this is one of the most critical parts of the source code, and by exploring it we will understand many aspects of RecyclerView, such as ViewHolder recycling, hidden views, predictive animations and stable ids. You might be surprised to see the prediction animation here. Well, the truth is that, despite Google employees’ best efforts to decouplex the responsibilities of the different components of RecyclerView, there is still a lot of “knowledge” shared between them, and predictive animation is one of them. There’s no way to avoid talking about them at one point or another.

Therefore, LayoutManager will ask RecyclerView “please give me a View at position 8” when laying out the project.
Here’s RecycleView’s response:

  1. Search changed scrap
  2. Search attached scrap
  3. Search for hidden views that have not been removed
  4. Search view cache
  5. If the Adapter has a stable ID, search the Attached Scrap and View cache again by specifying the ID
  6. Search ViewCacheExtension
  7. Search RecycledViewPool
If no suitable View is found in any of these places, it creates a View by calling the Adapter’s ‘onCreateViewHolder()’ method. Then it will pass if needed
onBindViewHolder()
Binding the View
, and finally returns it.

As you can see, there’s a lot going on here, not just an expected pool of reusable ViewHolders. Our goal is to figure out what all these caches mean, how they work, and why they are needed. We’ll go through them one by one (in what I think is the best order), and our first stop is RecycledViewPool.

RecycledViewPool

We want to answer some questions about each cache: what data structures it supports, under what conditions ViewHolders are stored there and retrieved from there, and most importantly, what its purpose is.

You probably have a pretty good idea of what a pool is for: for example, when you scroll down, a view that disappears at the top is recycled back into the pool to reuse a view that appears from the bottom.
We will discuss other scenarios where ViewHolders enter the pool later.
But first let’s look at some code for RecycledViewPool.RecycledViewPool
RecyclerView.Recycler internal class) :

Public static RecycledViewPool {private SparseArray <ArrayList <ViewHolder >> mScrap = new SparseArray <> (); Private SparseIntArray mMaxScrap = New SparseIntArray (); ... Public ViewHolder getRecycledView (int viewType) {ArrayList <ViewHolder> scrapHeap = mscrape.get (viewType); ...Copy the code
First, don’t let the name
mScrap
Confuse you – with those mentioned in the list above** Changed Scrap and **** Attached Scrap **
Has nothing to do.
We see that each view type has its own ViewHolders pool.
If the RecyclerView exhausted all other possibilities during a ViewHolder search, it would require the pool to provide a ViewHolder with the correct view type;
At that point, the view type is the only thing that matters.
Now, each view type has its own capabilities.
The default is **5**, but you can change it like this:

RecyclerView. GetRecycledViewPool (.) setMaxRecycledViews (SOME_VIEW_TYPE POOL_CAPACITY);Copy the code
This is a very important flexibility.
If there are dozens of items of the same type on the screen that often change at the same time, increase the pool for that view type.
Also, if you know that items of some view types are so rare that they will never show more than one on screen, set the pool size to 1 for that view type.
Otherwise, sooner or later the pool will fill up with five of them, and four of them will just sit there, wasting memory.
The method`getRecyclerView()
.“putRecycledView()
.“clear()`
Is public, so you can manipulate the contents of the pool.
but
putRecycledView()

manual
use
Not a good idea (for example, to prepare some ViewHolders beforehand).
you
only
in
onCreateViewHolder()
Method of the adapter
Create the ViewHolder
Otherwise ViewHolders may have undesirable RecyclerView states.

[by] (https://android.jlelse.eu/anatomy-of-recyclerview-part-1-a-search-for-a-viewholder-404ba3453714#fe2a)

Another cool feature is that, along with the getter, getRecycledViewPool() has a setter setRecycledViewPool(), so you can reuse a single pool for multiple RecycleViews. Finally, I’ll notice that the pool for each view type works like a stack (last in, first out). We’ll see why this is good later.

** Pooling method **

Now let’s solve the problem of throwing ViewHolders into the pool.
There are 5 cases:

  1. In the scrolling process, the view goes beyond the RecyclerView scope.

  2. The data has changed so that the view is no longer visible.
    When the disappear animation ends, it is added to the pool.
  3. Items in **view cache** have been updated or deleted.
  4. When searching ViewHolder, we found the desired location in ** Scrap ** or **cache**, but the result was not what we wanted due to the wrong view type or ID (if the adapter had a stable ID). [

    after

    ](https://android.jlelse.eu/anatomy-of-recyclerview-part-1-a-search-for-a-viewholder-404ba3453714#8b86)

  5. LayoutManager pre – layout in the * * * *
    A view was added, but not added to ** post-Layout **.

The first two cases are obvious.
One thing to note, however, is that scenario 2 is triggered not only by deleting problematic items, but also by, for example, inserting other items, which pushes the boundaries of a given item.

We need to focus on other cases. We haven’t covered View Cache and scrap yet, but the idea behind scenarios 3 and 4 is simple. The pool is where we know the “dirty” view needs to be rebound. With the exception of the pool, ViewHolders in all caches retain some of their state (most importantly location). All of these caches are searched by location in the hope that some Viewholders can be reused as-is. Instead, when a view enters the pool, its state (all flags, positions, etc.) is cleared. The only thing left is the associated view and the view type. Pools, as we all know, search by view type, and when a ViewHolder is found within them, it gets a new lease of life.

Given the above, scenarios 3 and 4 should not be a mystery: for example, if we see that an item in the **view cache** has been deleted, it does not make sense to save it in that cache anymore, since it will not be reused. – It’s the same location.
However, it’s not good to throw it away completely, so we drop it into the pool.

The last case requires that we know what **pre-layout** and **post-layout** are.
All right, let’s get on with it!
This is definitely not the case**pre-layout**
/**post-layout**
The most critical aspect of the mechanism, but the mechanism is often very important and is reflected in every part of RecyclerView, so we need to know this anyway.

Topics: Pre-layout, Post-Layout and Predictive animations

Consider a scenario where we have projects
Type a, b,
and
C,
Among them
a
and
B happens to fill up
The screen.
We delete
b
To make
C a
In view:

! [picture released] (https://miro.medium.com/max/1710/0 * ZxzTdRCpireBVs4t. PNG)
What we want to see is
c
Smooth sliding from bottom to new position.
But how is that possible?
we
In the new layout
Know the
c
Final position of
But how do we know where it should slide from?
RecyclerView or ItemAnimator just by looking at the new layout
c
Should come from the bottom is wrong.
We might have some custom LayoutManagers, which could come from the side or somewhere else.
Therefore, we need more help from LayoutManager.
Can we use the previous layout?
No, there isn’t
с
.
In the absence of c
b
Will be deleted, therefore
LayoutManager thinks about the layout correctly
c
It’s a waste of resources.

The solution provided by Google is as follows. After the adapter changes, RecyclerView requests two layouts instead of one from LayoutManager. The first is pre-layout, which is used to layout projects that are in the previous adapter state, but it might be a good idea to use adapter changes to hint at laying out some additional views. In our example, since we now know that B has been removed, we have laid out C separately, despite the fact that it is out of scope. The second layout, the post-layout, is just the regular layout that corresponds to the changed state of the adapter.

! [picture released] (https://miro.medium.com/max/2358/0 * 8 dtqywtaehym – 7 zm.)
Now, by comparison
c
in**pre-layout**
and**post-layout**
We can animate its appearance appropriately.
This animation – when the animated view is neither in the previous layout nor in the new layout – is called
Predictive animation,
This is one of the most important concepts in RecyclerView.
We’ll discuss this in more detail in a later part of this series.
But now let’s take a quick look at another example: if
b
What about being changed instead of deleted?

! [picture released] (https://miro.medium.com/max/2596/0 * zzI5wYYr3KdkexZ4.)
This may be surprising, but LayoutManager is still *pre-layout**
Stage of
c
Layout.
Why is that?
Because maybe
b
The change of
Would make it less tall, who knows?
Also, if
b
Smaller,
c
It might pop out of the bottom, so we’d better put it in**pre-layout**
In the.
But then, in the**post-layout**
That doesn’t seem to be the case
b
Changes to some TextViews
.
As a result,
Don’t need
c
“And threw it into the pool.
This is scenario 5 where you enter the pool.
Hopefully now that’s clear, we can go back to RecycledViewPool.

RecycledViewPool, continue to

When we get to a situation where the ViewHolder should go into the pool, there are still two obstacles: it may not be recoverable, and the View may be in a **Transient state**.

** Recyclability **

Recyclability is just one flag in a ViewHolder that you can use
ViewHolder method
`setIsRecyclable()`
Perform operations.
RecycleView also uses it to make ViewHolders unrecyclable during animation.

It is usually a bad idea to operate on a single flag from a separate location.
For example,
When the animation ends
And RecyclerView will call
`setIsRecyclable(true)`
Because you don’t want to make it recyclable for some application-specific reason.
But in this case, things don’t actually break because of the call to
setIsRecyclable()
is
In pairs.
That is, if you call
setIsRecyclable(false)
Two, then
setIsRecyclable(true)
only
call
Once does not make the ViewHolder recyclable, so you need to call it twice as well.

**Transient state**

The Transient state of the view is very similar. This is a flag in the View, operated by the setHasTransientState() method, and the calls are also paired. The View class itself does not use the flag, but just keeps it. It provides hints for widgets such as ListView and RecyclerView that it is best not to reuse this view for new content at the moment. You can set this flag yourself, but ViewPropertyAnimator (that is, when you do someView.animate()… It is automatically set to true when the animation starts and false when it ends. ⁴

Note that, for example, if you animate a view using ValueAnimator, you must manage the transition state yourself.

One last thing to note about the **Transient state** is that it propagates from the child to the parent, all the way to the root view.
Therefore, if you animate some internal view of an item in the list, not only that view but also the root view of the item that ViewHolder keeps referencing will enter**Transient state**

**OnFailedToRecycleView**

If the ViewHolder to be reclaimed fails the retrievability or **Transient state** check, the
Call adapter
the
`onFailedToRecycleView()`
Methods.
Now, this is a very important point: this approach not only notifies you of the event, but also asks you how to handle the situation.
`onFailedToRecycledView()`
from
return
true 
It means “recycle anyway”.
An appropriate case is to remove all animations and other sources of this problem when binding a new project. At this time
You can be in
onFailedToRecycledView()
In the method
Get these things right
.

What you shouldn’t do is
onFailedToRecycledView()
completely
ignore
.
Here’s one situation that can hurt you.

Imagine that when you look at the images in your project, they are fading.
If the user scrolls through the list fast enough, the images will not disappear completely when they disappear, making ViewHolders ineligible for recycling.
As a result, your scrolling will be slow and, most importantly, new and new ViewHolders will be created, cluttering memory.

A successful recall of the ViewHolder results in a call
onViewRecycled()
Method, which is a good place to free up a lot of resources, such as images.
Keep in mind that some ViewHolder instances may go unused for long periods of time and sit in the pool, which can waste a lot of memory.
Now, let’s go to the next cache –**View Cache**
.