1. The background

RecyclerView is an inevitable topic for every Android developer. It can not only implement common horizontal/vertical lists, but also achieve waterfall flow effect with very little code, and with a variety of tools provided by Google, but also on this basis to achieve a variety of custom animations, sliding effects, etc. When you see the list effect above, your first reaction is what RecyclerView tool to use to achieve the effect? For example, try customizing LayoutManager.

However, before customizing LayoutManager, we need to do some preparatory work to realize a LayoutManager that can meet both display and performance requirements. For example, we need to figure out what LayoutManager means to RecyclerView. LayoutManager in RecyclerView is responsible for what important work, it needs and RecyclerView in the work of other helpers to cooperate, we often say that RecyclerView cache and View reuse is responsible for? Next we go to RecyclerView source code to try to find the answer to these questions.

This paper mainly introduces some pre-work that needs to be understood before user-defined LayoutManager, including the overall design of RecyclerView and its main members and functions, and summarizes the connection between these classes and LayoutManager, providing theoretical basis for subsequent user-defined LayoutManager.

2. Overall architecture design of RecyclerView

RecyclerView is a View that can display a large amount of data in a limited area. RecyclerView may or may not have a RecyclerView.

  • The essence of RecyclerView is ViewGroup, so it needs to map a large number of data objects into View object collection, and display the View in View object collection as its own sub-view layout in its own area.
  • Because the data set to be displayed may be infinite, there may be an infinite collection of View objects. However, RecyclerView itself has limited area and cannot display all its sub-views in this area at the same time, which means that it needs to provide the ability to layout the sub-views to be displayed in its own area as required. And the ability to replace the child View being displayed by gesture operation. Correspondingly, when replacing the displayed View, the View object that is not displayed needs to be recycled in time.
  • If most of the data in the data set can be displayed using the same View form, then it means that it is wasteful to create a corresponding View for each data object. If type is used to distinguish the expected View form of the data object, RecyclerView needs to have the ability to reuse View objects according to type, that is, different data objects of the same type can reuse the same View object at different times for display.

For the above capabilities, RecyclerView arranges different classes for processing, corresponding respectively:

  • Adapter
  • LayoutManager
  • Recycler

These three classes are essential for RecyclerView, they are the basis for RecyclerView to work properly. But the Recycler’s work can be recycled inside RecyclerView, making it transparent to the Recycler. At the same time, Google provides several specific implementation classes of LayoutManagers to achieve single-column lists, waterfall flow lists and other effects. Most of the layoutManagers are enough to meet the needs. The user in daily use is more to deal with Adapter, through the RecyclerView set custom Adapter to provide the expected display of sub-view style and data binding mode.

3. An important member of RecyclerView

3.1 ViewHolder

Before introducing the three classes mentioned in Section 2, take a look at the basic unit of RecyclerView: the ViewHolder. If Item is used to represent a data object in a data set, ItemView represents the View object used to display that Item. ViewHolder is responsible for carrying an ItemView in RecyclerView. In addition to maintaining the View object itself, ViewHolder also maintains the position of Item (the position refers to the order of Item in the data set), Item type, Item Id, etc. Most of the time, the RecyclerView or its helper classes do not operate directly on the View, but on the ViewHolder.

When reading RecyclerView source code, I found that some operations need to use View as parameters, and some operations need to use ViewHolder as parameters. In fact, in RecyclerView, one can get another one through any one. Don’t worry too much about which type is maintained in the variables of RecyclerView’s internal classes.

Generally, however, the Adapter is responsible for creating the corresponding ViewHolder based on the data Item; Recycler is responsible for managing ViewHolder, creating new ViewHolder or recycling existing ViewHolder according to the situation. LayoutManager can recycle views directly through Recycler, add them to RecyclerView and recycle views that are no longer displayed.

3.2 Adapter

The Adapter is responsible for mapping data objects to View objects. The data set to be presented is maintained within the Adapter, which in addition to mapping the data to the View distributes changes to the data set.

Mapping data objects to View objects that can be used for display is divided into two steps in RecyclerView system:

  • Step 1: Create the ViewHolder as expected from the itemType, focusing more on the View’s structural style.
  • Step 2: Obtain the data object from the data set maintained by the Adapter according to position, bind the data object to the View in the ViewHolder, and pay more attention to the data content displayed by the View.

In code implementation, the recyclerView. Adapter base class has two callback methods to be implemented by the user. These two methods will be called by adapter. createViewHolder and Adapter.bindViewHolder respectively, corresponding to step 1 and Step 2.

public abstract VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType);

public abstract void onBindViewHolder(@NonNull VH holder, int position);
Copy the code

Now that Adapter has the ability to map data objects to View objects, when does it perform this ability? Move on to the next part: Recycler.

3.3 Recycler

Recycler is a Recycler. Literally, the Recyler manages Viewholders, recycles viewholers that are no longer displayed, and recycles them when appropriate. One of the most important capabilities of Recycler is to provide a ViewHolder/View based on position. The user does not need to care whether the ViewHolder is newly created or recycled. Recycler can help RecyclerView process the ViewHolder cache.

There are several levels of cache in RecyclerView, we have different definitions of different views, some people think it is four levels of cache, including a level of in-screen cache, two levels of off-screen cache and a level of custom cache; Some people think that RecyclerView is a three-level cache, wherein the in-screen cache and the first-level off-screen cache are combined into one level, and the other level of off-screen cache and custom cache together constitute the three-level cache; Some people think of it as a level 3 cache, but this level does not include in-screen cache, but two levels of off-screen cache and one level of custom cache. This article introduces the last point of view.

Before we understand the mechanism of Recycler caching, we will first introduce several changes of RecyclerView after it is added to View group, then introduce two different ways to recycle View, and finally introduce the RecyclerView caching mechanism.

3.3.1 View的detach vs remove

Before we look at the two ways Recycler can process views, let’s look at how Recycler can process views once they are added to a parent View. Detached A View added to a parent can be removed as well as a more lightweight detach operation. Detached means a temporary state, meaning that the View will be reattached or removed completely immediately afterwards. Detached If a View is in the detached state, it cannot be accessed by its parent’s getChildAt method as if it were removed.

3.3.2 Scrap vs. Recycle

Conversely, Recycler can deal with ViewHolder in two ways: scrap and recycle.

Scrap is usually used in association with the Detach operation. If a Recycler is used to scrap a View, it is expected that the View is in the Detach (not removed) state, and the ViewHolder holding the View is marked as scrap. Then store the recycler. mAttachedScrap temporarily in the recycler. mAttachedScrap list until further unScrap or recycle. Scrap is a temporary operation that usually means that the View was previously displayed on the screen and will most likely continue to be displayed later without being recycled by Remove. MAttachedScrap is an ArrayList that holds ViewHolder of unremoved child Views.

Recycle is usually used together with remove. If recyer is used to recyle a View, it is expected that the View has been removed from the parent and the ViewHolder holding the View is unScrap. When the ViewHolder and its View are recyclable, RecyclerView can recycle the ViewHolder into the Recycler cache pool. The Recycle operation only applies to a View that has been removed. The View is displayed on the screen before, but is no longer displayed due to sliding operations or data set changes. In this case, the View can be recycled for reuse. This is also the reason why RecyclerView is considered as level 3 cache in this paper. Only views removed can be recycled cache.

RecyclerView. In the source code of Recycler, there are some methods or variables named after scrap, but they are actually doing recycle.

3.3.3 Cache and Reclamation

The RecyclerView cache mechanism is a little more readable now that we know the difference between Detach /remove and Scrap/Recycle. The cache is actually a variable that holds collections of ViewHolder in Recycler. The priorities of Recycler variables used to represent the three-level cache are, from high to low, mCacheViews, mViewCacheExtension and mRecyclerPool. MViewCacheExtension is a custom cache, which is not expanded in this article. The contents of mCacheView and mRecyclerPool are both ViewHolder that are no longer displayed on the screen.

MCacheViews is a more efficient cache, requiring neither the creation nor rebinding of ViewHolder steps, meaning that only when the data objects match exactly, That is, the ViewHolder in mCacheViews will be reused only when the data Item to be displayed exactly matches the data Item maintained in the cached ViewHolder (ItemType and Item are the same).

The use conditions of ViewHolder cached in mRecyclerPool are lower than mCacheViews. ViewHolder can be reused only when ItemType matches, but ViewHolder needs to be bound again.

The differences between mCacheViews and mRecyclerPool data structures are briefly introduced. MCacheViews is an ArrayList that can store ViewHolder objects. MRecyclerPool is a RecycledViewPool object. Int denotes itemType, and ArrayList is used to store ViewHolder objects of this itemType.

Rules for Recycler to recycle ViewHolder are:

  • If McAcheviews. size does not reach the maximum size, add the ViewHolder to mCacheViews. If the size has reached the maximum value, remove the ViewHolder first added to mCacheViews and add the ViewHolder to be reclaimed to mCacheViews.
  • If McAcheviews. size has reached the maximum size, remove the ViewHolder object first added to mCacheViews from mCacheViews and try to recycle it into mRecyclerPool. Regardless of whether the ViewHolder is successfully recycled into mRecyclerPool, the ViewHolder object is removed from mCacheViews.
  • If the size of the List that can store the ViewHolder itemType in mRecyclerPool has reached its maximum size (default: 5), discard the ViewHolder. Otherwise, add the ViewHolder to the List.

3.3.4 Recycler get View

Finally, the process of Recycler getting a ViewHolder can be used directly by Recycler according to a given position. As pointed out in 3.2, Adapter can map data objects to View objects in two steps: create View objects and bind data. Recycler can retrieve ViewHolder from different parts of Recycler and the steps to call Adapter are slightly different.

  • Recycler can first try to obtain available ViewHolder from mAttachedScrap (it can be considered that the ViewHolder corresponds to the same Item data object before and after reuse and the data object remains unchanged). The ViewHolder can be used directly. Neither Adapter.createViewholder nor Adapter.bindViewholder need be executed.
  • If we don’t get a ViewHolder from mAttachedScrap, recyclcan try to recycle it into the cache. This article doesn’t talk about custom caching. Recyclcan try to recycle ViewHolder from mCacheViews. Similar to the ViewHolder obtained from mAttachedScrap, this ViewHolder can be used directly.
  • If mCacheViews still does not have a ViewHolder that meets the requirements, try to obtain a ViewHolder that meets the requirements from mRecyclerPool. The ViewHolder itemType obtained here can match, that is, the structure style of View meets the requirements. However, data binding needs to be redone, that is, adapter. createViewHolder is not required, but adapter. bindViewHolder is required.
  • If Recycler does not get a ViewHolder from the cache that satisfies the requirement, both steps of the Adapter can be completed.

3.3.5 Recycler summary

Now that we have a rough idea of how to get a ViewHolder available by position (where position still refers to the order of a data object in the data set maintained by Adapter, in other words, we can use position to refer to a particular data object), It is also clear that Recycler has the ability to recycle two View/ Viewholders: Scrap and recycle, to temporarily save or cache viewholers. So who, and when, wanted to get a ViewHolder for presentation? And who at what time can call Recycler temporarily save or recycle ViewHolder? Take a look at the last essential member of RecyclerView: LayoutManager.

3.4 LayoutManager

LayoutManager is the executor of RecyclerView that actually decides ItemView placement rules and sliding rules, and can even decide some layout parameters of ItemView. LayoutManager has several abstract methods and empty methods to be implemented, giving users full freedom to extend LayoutManager to achieve their desired list effect or scrolling effect. The specific work of LayoutManager is described in detail in section 4.

// Create the default LayoutParams for ItemView
public abstract LayoutParams generateDefaultLayoutParams(a);
// Create a RecyclerView subview
public void onLayoutChildren(Recycler recycler, State state) {
    Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, State state) ");
}
// Whether RecyclerView supports horizontal sliding
public boolean canScrollHorizontally(a) {
    return false;
}
// Whether RecyclerView supports vertical sliding
public boolean canScrollVertically(a) {
    return false;
}
// Process the horizontal sliding of RecyclerView
public int scrollHorizontallyBy(int dx, Recycler recycler, State state) {
    return 0;
}
// Process the vertical sliding of RecyclerView
public int scrollVerticallyBy(int dy, Recycler recycler, State state) {
    return 0;
}
Copy the code

3.5 ItemDecoration

In addition to the above mentioned auxiliary classes necessary for RecyclerView, RecyclerView also provides some classes that can achieve better practical effects beyond basic functions. In this paper, ItemDecoration is specially selected for introduction. This class is one of the most frequently used classes in our daily use of RecyclerView, and it has a certain relationship with LayoutManager layout processing.

ItemDecoration allows users to add special drawing and layout offsets to ItemView. Instead of expanding the drawing, I will focus on layout offsets. You can customize the spacing of the ItemView by overwriting the getItemOffsets method. The Rect used in the getItemOffsets method records four values. These four values are similar to the padding or margin of the ItemView. Left, right, top, bottom. This method is called when LayoutManager Measure ItemView is used and the corresponding value is added to the width and height result of the ItemView Measure.

Note that: The method of setting ItemDecoration for RecyclerView is add instead of set. In RecyclerView, ItemDecoration collection is maintained. When measure ItemView is used in layout process, The value of offset in the ItemDecoration collection is computed cumulatively. When individuals use ItemDecoration, they have repeatedly set the same ItemDecoration in RecyclerView, resulting in spacing performance inconsistent with expectations, mostly in page refresh scenarios.

3.6 ItemAniamtor

Finally, ItemAniamtor is used to define the animation effect that the ItemView needs to perform when the data set maintained by the Adapter changes. For example, when the Item data corresponding to an ItemView is deleted, the ItemView needs to perform the disappearing animation. And other itemViews that need to perform displacement animations because of its disappearance.

In order to complete the delay shift animation effect mentioned in the background, ItemAnimator was expected to help implement it. In fact, based on the analysis in Sections 4.2 and 4.3, it can be seen that, RecyclerView does not trigger animations defined in ItemAnimator for normal list scrolling. Only RecyclerView layout can trigger animations for ItemAnimator. Because the content of the ItemAnimator is also quite large, this article will not expand on this section.

4. The LayoutManager

Above introduced so many RecyclerView members, finally to LayoutManager. I believe everyone has heard: LayoutManager is responsible for RecyclerView layout. The onLayout method of RecyclerView is replaced by LayoutManager. No, LayoutManager’s job is to actually help RecyclerView determine the location of its child views, and it doesn’t have to be done only in recyclerView. onLayout. This section is about understanding what LayoutManager needs to do at what time.

4.1 How to realize the layout and drawing of RecyclerView

In order to know when LayoutManager starts to layout ItemView, we can go back to RecyclerView first. As a ViewGroup, RecyclerView cannot escape the three processes of measure, layout and draw.

4.1.1 measure

RecyclerView provides LayoutManager with the opportunity to customize onmMeasure methods. If LayoutManager expects RecyclerView to use custom onMeasure methods, You can disable RecyclerView’s autoMeasure policy by overriding the isAutoMeasureEnabled method to return false. In fact, this method returns false by default, but in most cases LayoutManager will return true. In particular, the onMeasure method of LayoutManager should not be overridden when isAutoMeasureEnabled returns true. Like most articles on the market, this article omits the non-Automeasure analysis and focuses on autoMeasure’s mechanics.

AutoMeasure of RecyclerView’s onMeasure is relatively simple and contains the following two branches:

  1. If RecyclerView fixed width and height, call the defaultOnMeasure method of RecyclerView to end onMeasure.
  2. If RecyclerView is an adaptive width and height, ItemView needs to be arranged in advance to determine the width and height of RecyclerView. Therefore, part of the layout process will be carried out in advance in the onMeasure method of RecyclerView.

4.1.2 layout

RecyclerView layout process is divided into 3 steps, and the corresponding method of the three steps is also very simple and rough:

  • dispatchLayoutStep1
  • dispatchLayoutStep2
  • dispatchLayoutStep3

Corresponding to it is the three possible values of the mLayoutStep variable in RecyclerVIew’s State class (the State class records all kinds of information that may be used) (the mLayoutStep variable is actually of int type, and the constant names of its possible values are listed here for ease of reading) :

  • STEP_START
  • STEP_LAYOUT
  • STEP_ANIMATIONS

A complete layout process in RecyclerView needs to call dispatchLayoutStep1, dispatchLayoutStep2, dispatchLayoutStep3 at least once. DispatchLayoutStep2 may be called multiple times. As mentioned in 3.1.1, onMeasure may carry out some layout processes in advance, namely dispatchLayoutStep1 and dispatchLayoutStep2. If onMeasure has completed the first two steps of layout, In most cases, you only need to execute dispatchLayoutStep3 in onLayout. If the first two steps of layout are not performed in onMeasure, you need to perform a complete layout in onLayout.

Although it is often said that RecyclerView outsources the ability of layout to LayoutManager, in fact RecyclerView only assigns the ability of layout sub-views to LayoutManager. RecyclerView in the layout process will also be pre-layout pre-layout and other operations.

In the layout process, the second step of dispatchLayoutStep2 calls the onLayoutChildren method of LayoutManager. This step is usually considered as the actual layout process post-layout. In this step, add the ItemView that needs to be displayed on the screen to RecyclerView, and measure and layout of ItemView; The first step and the third step in the layout process is mainly to serve RecyclerView animation (ItemAnimator). Pre-layout is carried out in the first step, and then the difference between pre-layout and post-layout is compared in the third step. This triggers the animation execution of the ItemAnimator.

4.1.3 the draw

There are relatively few special treatments in the drawing process of RecyclerView. This paper only briefly introduces the process related to ItemDecoration. Before drawing ItemView, RecyclerView will first traverse its maintained ItemDecoration list, execute the onDraw method of ItemDecoration, draw the content in the lower layer of ItemView; After the ItemView is drawn, execute the onDrawOver method of ItemDecoration. The drawn content is in the upper layer of the ItemView.

4.2 Scroll Processing

The main goal of this paper is to make necessary preparation for the realization of custom LayoutManager, so do not expand RecyclerView complex nested rolling and inertial rolling logic, only consider the role of LayoutManager in ordinary rolling processing. List of scroll is usually the user’s gestures, to look into the first RecyclerView. OnTouchEvent method.

We know that the fingers of the user in the list up or down, can lead to a scrolling list, so we have to RecyclerView. The onTouchEvent ACTION_MOVE branch, look at it have any processing related to scroll, It was found that after receiving the ACTION_MOVE message, RecyclerView could get dx and DY of horizontal and vertical displacement of the list caused by gesture sliding after a series of calculation and judgment. Then call scrollByInternal method of RecyclerView to process the displacement value of scrolling DX and dy, and finally enter the scrollStep method. According to the dx and dy calling LayoutManager scrollHorizontallyBy/scrollVerticallyBy method respectively, and the rolling lead to child View mobile and outsource the LayoutManager processing layout, LayoutManager also needs to use Recycler to handle views that are not displayed on the screen when scrolling.

Note about RecyclerView scrolling: Take the LinearLayoutManager provided by Google as an example. The LinearLayoutManager handles scrolling by calling the View’s offsetTopAndBottom method to move the ItemView displayed on the screen. And the fill method is used to scroll the empty area to add the View and handle are not in the screen shows the View, in the process, and LayoutManager. OnLayoutChildren method. A normal scrolling process does not result in repeated RecyclerView layouts, so a normal list scrolling will not trigger any animation of the ItemAnimator.

4.3 Data update processing

RecyclerView Creates a RecyclerViewDataObserver to register an Observable in the Adapter. RecyclerViewDataObserver what RecyclerViewDataObserver does is actually in the Adapter data set send change or one of the data changes, in the appropriate circumstances requestLayout, to complete a RecyclerView layout process, This is the time to trigger the corresponding animation of the ItemAnimator.

The observer mode is not described here, and RecyclerView can receive adapter.notifyXXX message after clearly registering listener, and then pay attention to RecyclerViewDataObserver and pay attention to its specific processing of notify message.

First, clarify the types of data updates:

  • Data set full update
  • Data sets are locally updated
    • Local Item changes (ItemChanged/ItemRangeChanged)
    • New Item inserted
    • Removed existing Item
    • Item moved (ItemMoved)

Observed RecyclerViewDataObserver used to process data update methods, found that these methods are used in the same help class: AdapterHelper. In AdapterHelper, the data update behavior is abstracted into UpdateOp class. Each UpdateOp object represents a data update operation. AdapterHelper maintains a list of mPendingUpdates (ArrayList) to be processed.

If Adapter triggers a full update, then RecyclerViewDataObserver processes requestLayout when mPendingUpdates are empty, and RecyclerView relayouts; If Adapter triggered local update (including ItemChange/ItemInsert/ItemRemove/ItemMove, etc.), The RecyclerViewDataObserver process will trigger the RecyclerView layout when the size of the mPendingUpdates list is 1.

4. Summary

So far, you have seen several important members of RecyclerView and their basic responsibilities, as well as their relationship to LayoutManager:

  1. The Adapter provides the View based on the type of the data object and provides the binding relationship between the View and the data. LayoutManager does not need to deal with the Adapter.
  2. Recyler can provide a View that can be directly used for display according to position. It is also responsible for managing the View that is not displayed. LayoutManager needs to work directly with Recycler when onLayoutChildren to ask Recycler for views that can be displayed or to recycle views that can no longer be displayed when they slide.
  3. ItemDecoration can handle layout offsets of ItemView, which LayoutManager will count when measuring ItemView.
  4. The LayoutManager has no direct connection to the ItemAnimator, which defines the animation that the ItemView needs to perform when the dataset changes. The execution timing of the animation defined by ItemAnimator is triggered by the RecyclerView layout process. Normal list sliding does not trigger the RecyclerView’s repeated layout, so the list sliding does not trigger the execution of ItemAnimator.

In addition, you can see from the above description that LayoutManager needs to do two important things:

  1. Handle the layout of the ItemView in the onLayoutChildren method.
  2. The scrollHorizontallyBy and scrollVerticallyBy methods handle the paning of the ItemView as the list scrolls, and the replenishing and recycling of the ItemView.

At this point, we know that LayoutManager can handle the measure and layout process of the child View. It can measure the child View according to its own needs and put the child View in the desired position (or even stack all the child View in the same position). LayoutManager can also take over the scrolling process (we can even rearrange the child View in the Scroll method without triggering the RecyclerView layout process if we want to).

Looking back at the demo in the first section, you need to make sure that the first ItemView that is fully visible is a large card and the other ItemViews are small cards when you’re laying out or sliding. Layout and sliding happen to be handled in LayoutManager. Therefore, the requirement becomes to make the first ItemView fully visible by LayoutManager measure be treated as large card state and the other ItemView measures as small card state and LayoutManager arrange these ItemViews in the correct position. In future articles, we will take you through a 0-1 analysis of how to implement a LayoutManager that meets our scrolling animation needs.

Hi, I am the fish pond of Kuaishou E-commerce

Kuaishou e-commerce wireless technology team is recruiting talents 🎉🎉🎉! We are the core business line of the company, which is full of talents, opportunities and challenges. With the rapid development of the business, the team is also expanding rapidly. Welcome to join us and create world-class e-commerce products together

Hot job: Android/iOS Senior Developer, Android/iOS expert, Java Architect, Product Manager (e-commerce background), Test development… Plenty of HC waiting for you

Internal recommendation please send resume to >>> our email: [email protected] <<<, note my roster success rate is higher oh ~ 😘