Introduction:

RecyclerView is a new UI control proposed in Android 5.0, which can be used to replace the traditional ListView.


Bugly’s previous post explained the differences between RecyclerView and ListView:


Android ListView and RecyclerView comparison analysis – cache mechanism


Today spirit brother to give you a detailed introduction about RecyclerView, you need to understand all aspects.


This article is from Tencent Tiantian Ptu team — DamonXia (Xia Zhengdong), an Android engineer

preface

Source code address for Demo below:

https://github.com/xiazdong/RecyclerViewDemo

(Click on the bottom to read the article and visit the project directly.)

  • RecyclerView add HeaderView and FooterView, ItemDecoration example.

  • Demo2: ListView implements partial refresh.

  • Demo3: RecyclerView to achieve drag, sideslip delete.

  • Demo4: RecyclerView flash screen problem.

  • Demo5: RecyclerView implementation setEmptyView().

  • Demo6: RecyclerView universal adapter, waterfall flow layout, nested sliding mechanism.

The basic concept

RecyclerView is a new UI control proposed by Android 5.0 and is located in support-V7 package. Can be in the build. Add the compile gradle ‘com. Android. Support: recyclerview – v7:24.2.1’ import.

RecyclerView’s official definition is as follows:

A flexible view for providing a limited window into a large data set.

As can be seen from the definition, flexible is the characteristic of RecyclerView. It’s a bit like a ListView, and we’ll talk about the difference between RecyclerView and ListView later in this article.

Why RecyclerView?

RecyclerView does not completely replace ListView (as you can see from the fact that ListView is not marked as @deprecated). The usage scenarios are different. But the emergence of RecyclerView will make a lot of open source projects abandoned, such as horizontal scrolling ListView, horizontal scrolling GridView, waterfall flow control, because RecyclerView can achieve all these functions.

For example, there is a requirement that the display form when the screen is vertical is ListView, and the display form when the screen is horizontal is a two-column GridView. At this time, if RecyclerView is used, a line of code is set to replace LayoutManager.

ListView vs RecyclerView

ListView has some advantages over RecyclerView:

  • AddHeaderView (), addFooterView() adds a header view and a tail view.

  • Use the “Android: Divider” to set custom dividing lines.

  • SetOnItemClickListener () and setOnItemLongClickListener () sets the click event and long press event.

These functions in RecyclerView have no direct interface, to achieve their own (although it is very simple to achieve), so if only simple display function, ListView is undoubtedly simpler.

RecyclerView has some obvious advantages compared with ListView:

  • View reuse has been implemented by default, there is no need for if(convertView == NULL) implementation, and the recycling mechanism is more complete.

  • Partial refresh is supported by default.

  • Easy to add item, delete item animation effect.

  • Easy to achieve drag, side – slide delete and other functions.

RecyclerView is a plug-in implementation, decoupling of various functions, so that the scalability is better.

ListView implements a partial refresh

. We all know the ListView by adapter notifyDataSetChanged () implementation ListView update, the update method shortcoming is a global update, namely to redraw each Item View. But in fact, a lot of times, we just update the data of one Item, and the other items don’t need to be redrawn.

Here’s how ListView implements local updates:

As you can see, we get the View to update by getChildAt() of the ListView, and then get the ViewHolder by getTag().

Standard usage

The steps of RecyclerView standard are as follows:

  • Create Adapter: Create an Adapter class that inherits RecyclerView.Adapter

    (VH is the ViewHolder class name) and call it NormalAdapter.

  • Create ViewHolder: Create a static internal class in NormalAdapter that inherits RecyclerView.ViewHolder, called VH. The ViewHolder implementation is almost identical to the ListView ViewHolder implementation.

  • Implemented in NormalAdapter:

    • VH onCreateViewHolder(ViewGroup parent, int viewType): Map Item Layout Id, create VH and return.

    • Void onBindViewHolder(VH holder, int position): Sets the specified data for the holder.

    • Int getItemCount(): Number of returned items.

As you can see, RecyclerView splits the function of getView() into onCreateViewHolder() and onBindViewHolder().

The basic Adapter implementation is as follows:

After creating Adapter, then set RecyclerView. Generally speaking, four Settings need to be set for RecyclerView, which is the four components of the following text: Adapter(mandatory),Layout Manager(mandatory),Item Decoration(optional, default blank),Item Animator(optional, default DefaultItemAnimator)

Note that in onCreateViewHolder(), the mapping Layout must be

View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_1, parent, false);Copy the code

Not:

View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_1, null);Copy the code

To implement the ListView effect, you only need to set up Adapter and Layout Manager, as follows:

List<String> data = initData();
RecyclerView rv = (RecyclerView) findViewById(R.id.rv);
rv.setLayoutManager(new LinearLayoutManager(this));
rv.setAdapter(new NormalAdapter(data));Copy the code

ListView only provides notifyDataSetChanged() to update the entire view, which is quite unreasonable. RecyclerView provides apis such as notifyItemInserted(),notifyItemRemoved(), and notifyItemChanged() to update a single or a range of Item views.

Of the four

The four major components of RecyclerView are:

  • Adapter: Provides data for Item.

  • Layout Manager: Layout of an Item.

  • Item Animator: Add and remove Item animations.

  • Item Decoration: Divider between items.

Adapter

The use of Adapter has been introduced in front, the function is to provide data for RecyclerView, here mainly introduces the realization of universal Adapter. In fact, the concept of a universal adapter already exists in ListView, namely base-Adapter-Helper.

Here we are only for RecyclerView, talk about the emergence of universal adapters. In order to create a RecyclerView Adapter, we need to do repeated work every time, including rewriting onCreateViewHolder(), getItemCount(), creating ViewHolder, and the implementation process is very similar, so the universal Adapter came into being. He can quickly create an Adapter by:

Is it convenient? Of course, complications can be easily solved.

Here’s how to implement the universal adapter.

Public Abstract class QuickAdapter

extends recyclerView. Adapter< quickAdapter. VH> T is the type of each element in the list data, and QuickAdapter.VH is the ViewHolder implementation of QuickAdapter, called the universal ViewHolder.

The quickAdapter.vh implementation is introduced:

SparseArray

store item View control, getView(int ID) function is to obtain the corresponding View by id. Put findViewById() in mViews to avoid findViewById() next time.

The QuickAdapter implementation is as follows:

Among them:

  • GetLayoutId (int viewType) returns the layout ID based on the viewType.

  • Convert () does the specific bind operation.

With that, the universal adapter implementation is complete.

Item Decoration

RecyclerView adds separators between items by using the addItemDecoration() method. Android doesn’t provide a well-implemented Divider, so any Divider styles need to be implemented yourself.

The method is: create a class and inherit RecyclerView. ItemDecoration, rewrite the following two methods:

  • OnDraw (): Draws the dividing line.

  • GetItemOffsets (): Sets the width and height of the splitter line.

Google provides a reference implementation class in sample: DividerItemDecoration. Here we analyze this example to see how to customize ItemDecoration.

First look at the constructor, which retrieves the system attribute Android :listDivider, which is a Drawable object.

So if you want to set it, you need to set it in value/styles.xml:

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <item name="android:listDivider">@drawable/item_divider</item></style>Copy the code

GetItemOffsets ()

Here just see mOrientation = = VERTICAL_LIST, outRect is the spacing around the current item, similar margin properties, set spacing for mDivider under this item now. GetIntrinsicHeight ().

So how does getItemOffsets() get called?

RecyclerView inherits the ViewGroup and rewrites measureChild(), which is called in onMeasure() to calculate the size of each child, To calculate the size of each child, add the outer spacing set by getItemOffsets() :

Here we only consider the case where the mOrientation == VERTICAL_LIST, and the onDraw() of the DividerItemDecoration actually calls drawVertical() :

So how does onDraw() get called? ItemDecoration also has a method onDrawOver(), which can also be overridden, so what’s the relationship between onDraw() and onDrawOver()?

Let’s look at the following code:

Draw(); draw(); draw(); draw(); draw(); Call dispatchDraw() to draw children. So: onDraw() of ItemDecoration is called before the Item is drawn, and onDrawOver() of ItemDecoration is called after the Item is drawn.

Of course, if you only need to achieve a certain distance between items, you only need to set margins for the layout of items, and there is no need to implement Item decoration.

Layout Manager

LayoutManager is responsible for the layout of RecyclerView, which includes the acquisition and recycling of Item View. Here we briefly analyze the implementation of the LinearLayoutManager.

For LinearLayoutManager, the important methods are:

  • OnLayoutChildren (): Entry method for RecyclerView layout.

  • Fill (): responsible for filling RecyclerView.

  • ScrollVerticallyBy (): Slide a certain distance based on the movement of your finger and call fill() to fill.

  • CanScrollVertically () or canScrollHorizontally(): Checks whether vertical or horizontal sliding is supported.

The core implementation of onLayoutChildren() is as follows:

An important concept of RecyclerView is to divide the Recycle station into Scrap Heap and Recycle Pool, where Scrap Heap elements can be reused directly without calling onBindViewHolder(). DetachAndScrapAttachedViews () according to the situation, the original Item View into the Scrap Heap or Recycle Pool, so as to promote efficiency in the reuse.

Fill () calls layoutChunk() over and over again until the remaining space is filled. The core implementation of layoutChunk() is as follows:

In which, next() calls getViewForPosition(currentPosition), which can obtain appropriate View from Recycler by RecyclerView. The recycling mechanism of this method will be introduced in the following.

To customize LayoutManager, see:

Create a RecyclerView LayoutManager – Part 1

https://github.com/hehonghui/android-tech-frontier/blob/master/issue-9/%E5%88%9B%E5%BB%BA-RecyclerView-LayoutManager-Par t-1.md

Create a RecyclerView LayoutManager – Part 2

https://github.com/hehonghui/android-tech-frontier/blob/master/issue-13/%E5%88%9B%E5%BB%BA-RecyclerView-LayoutManager-Pa rt-2.md

Create a RecyclerView LayoutManager – Part 3

https://github.com/hehonghui/android-tech-frontier/blob/master/issue-13/%E5%88%9B%E5%BB%BA-RecyclerView-LayoutManager-Pa rt-3.md

    Item Animator

    RecyclerView can pass mRecyclerView. SetItemAnimator (ItemAnimator animator) set to add, delete, move, change of animation effects. RecyclerView provides the DefaultItemAnimator implementation class: DefaultItemAnimator. Here we show how to customize an ItemAnimator by analyzing the source code for DefaultItemAnimator.

    DefaultItemAnimator inherits from SimpleItemAnimator and SimpleItemAnimator inherits from ItemAnimator.

    We’ll start with a few important methods for the ItemAnimator class:

    • AnimateAppearance (): Called when a ViewHolder appears on the screen (possibly add or move).

    • Animatepattern (): called when the ViewHolder disappears on the screen (possibly remove or move)

    • AnimatePersistence (): Called when the layout changes without calling notifyItemChanged() and notifyDataSetChanged().

    • AnimateChange (): Called when notifyItemChanged() or notifyDataSetChanged() is explicitly called.

    • RunPendingAnimations (): RecyclerView animations are executed once per frame rather than immediately. For example, if multiple items are added between frames, the animations will be Pending and stored in member variables until the next frame is executed together. This method is executed if the previous animateXxx() returns true.

    • IsRunning (): Whether there are animations to be executed or being executed.

    • DispatchAnimationsFinished () : invoked when all the animation is performed.

    The italicized method above is a little confusing, but that’s okay because Android provides the SimpleItemAnimator class (inherited from ItemAnimator), which provides a more straightforward set of apis, To customize an ItemAnimator, simply inherit from SimpleItemAnimator:

    • AnimateAdd (ViewHolder holder): Called when an Item is added.

    • AnimateMove (ViewHolder holder, int fromX, int fromY, int toX, int toY): Called when the Item is moved.

    • AnimateRemove (ViewHolder holder): Called when an Item is deleted.

    • animateChange(ViewHolder oldHolder, ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop): Called when notifyItemChanged() or notifyDataSetChanged() is explicitly called.

    For the above four methods, two points should be noted:

    • DispatchXxxStarting (Holder) will be called before the Xxx animation starts (in runPendingAnimations()), and dispatchXxxFinished(holder) after.

    • Inside these methods are not actually writing the code that performs animations; instead, they store all the items that need to be animated into member variables, return true, and execute them together in runPendingAnimations().

    The DefaultItemAnimator class is the default animation class provided by RecyclerView. We learned how to customize the Item Animator by reading the source code of this class. Let’s first look at the member variable of DefaultItemAnimator:

    DefaultItemAnimator implements the animateAdd() method of SimpleItemAnimator, which simply adds the item to mPendingAdditions and waits until executed in runPendingAnimations().

    Moving on to the implementation of runPendingAnimations(), this method performs remove,move,change, and Add animations in the following order: Remove animations are performed first,move and change are performed in parallel, and add animations are performed last. For simplicity, we omit the animation execution process of remove,move and change, and only look at the animation execution process of add, as follows:

    In order to prevent confusion in the execution of Add animations when a new Add animation was added to mPendingAdditions, we moved the contents of mPendingAdditions to the local variable additions and then performed the animation by walking through the additions.

    AnimateAddImpl () in runPendingAnimations(), animateAddImpl() is the specific way to perform an add animation, which changes the opacity of an itemView from 0 to 1 (the view has been changed to 0 in animateAdd()), as follows:

    DefaultItemAnimator is a cumbersome ItemAnimator that needs to inherit from the SimpleItemAnimator class and implement a bunch of methods. Don’t worry, RecyclerView-Animators come to the rescue. Here’s why:

    First of all, provide a series of Animator recyclerview – animators, such as FadeInAnimator ScaleInAnimator. The BaseItemAnimator class extends from SimpleItemAnimator. This class further encapsulates the code for customizing the ItemAnimator, making it easier to customize the ItemAnimator. You just need to focus on the animation itself. If you want to implement DefaultItemAnimator code, you only need the following implementation:

    More convenient than inheriting SimpleItemAnimator.

    A common pitfall for RecyclerView’s Item Animator is the “flash screen problem”. The problem is described as follows: When the Item view has an image and text, when updating the text and calling notifyItemChanged(), the image flashes while the text changes. The reason for this problem is that when notifyItemChanged() is called, the animateChangeImpl() of DefaultItemAnimator is called to perform the change animation, which causes the opacity of the Item to change from 0 to 1, causing a splash screen.

    Solution is very simple, in the rv. SetAdapter () before calling ((SimpleItemAnimator) the rv. GetItemAnimator ()). SetSupportsChangeAnimations disabled (false) change the animation.

    Expand RecyclerView

    Add the setOnItemClickListener interface

    RecyclerView does not provide the setOnItemClickListener() interface as ListView does by default. Is not able to add RecyclerView onItemClickListener best efficient solutions (http://blog.csdn.net/liaoinstan/article/details/51200600) is given by this article recyclerView.addOnItemTouchListener(…) Add methods for click events, but I don’t think there’s any need to bother exposing the interface because we could just write the implementation of the click event inside the Adapter’s onBindViewHolder() without exposing it. The specific method is through:

    public void onBindViewHolder(VH holder, int position) { holder.itemView.setOnClickListener(...) ; }Copy the code

    Add HeaderView and FooterView

    RecyclerView doesn’t provide apis like addHeaderView() and addFooterView() by default, so here’s how to implement them elegantly.

    If you have implemented a Adapter and want to add addHeaderView() and addFooterView() interfaces to the Adapter, you need to add several Item types to the Adapter. Then modify the getItemViewType(), onCreateViewHolder(), onBindViewHolder(), getItemCount() and other methods, and add the switch statement to determine. So how do you do this without breaking the original Adapter implementation?

    This introduces the Decorator design pattern, which extends the functionality of an existing class by composition without breaking the code of the original class.

    This is exactly what we need. We just need to add addHeaderView() and addFooterView() interfaces to the existing Adapter (named NormalAdapter here) as follows:

    Doesn’t it look elegant? Create a class that inherits recyclerView. Adapter< recyclerview. ViewHolder>, rewrite common methods, and then implement it by introducing ITEM TYPE:

    Add setEmptyView

    ListView provides a View for setEmptyView() to set Adapter data to null. RecyclerView does not provide a direct API, but it can be easily implemented.

    • Create a class that inherits RecyclerView and call it EmptyRecyclerView.

    • Add the View displayed when empty data to the current View hierarchy with getRootView().addView(emptyView).

    • Use AdapterDataObserver to monitor the change of RecyclerView data. If adapter is empty, hide RecyclerView and display EmptyView.

    The concrete implementation is as follows:

    Drag and slide to delete

    Android provides an ItemTouchHelper class that makes it easy to swipe and drag RecyclerView. Here we need to drag up and down and swipe away. First create a class that inherits from ItemTouchHelper.callback and override the following methods:

    • GetMovementFlags (): Sets the supported drag and slide directions. Here we support drag directions up and down, slide directions left to right and right to left, internally via makeMovementFlags().

    • OnMove (): callback on drag.

    • OnSwiped (): Callback while sliding.

    • OnSelectedChanged (): Callback when the status changes. There are three states: ACTION_STATE_IDLE, ACTION_STATE_SWIPE, and ACTION_STATE_DRAG. In this method, you can do some state changes, such as changing the background color while dragging.

    • ClearView (): callback at the end of user interaction. This method does some state clearing, such as restoring the background color after the drag.

    • IsLongPressDragEnabled (): whether long drag is supported. The default value is true. Override and return false if you do not want to support drag and drop.

    The concrete implementation is as follows:

    Then set up the slide and drag function for RecyclerView by using the following code:

    ItemTouchHelper helper = new ItemTouchHelper(new SimpleItemTouchCallback(adapter, data));
    helper.attachToRecyclerView(recyclerview);Copy the code

    If you want to support dragging by touching a View within an Item, the core method is helper.startDrag(holder). First define the interface:

    interface OnStartDragListener{
        void startDrag(RecyclerView.ViewHolder holder);
    }
    Copy the code

    Then let the Activity implement this interface:

    public MainActivity extends Activity implements OnStartDragListener{ ... public void startDrag(RecyclerView.ViewHolder holder) { mHelper.startDrag(holder); }}Copy the code

    To support touch drag for ViewHolder text objects, add onBindViewHolder() to the Adapter:

    MListener is an Activity object that implements the OnStartDragListener interface when the Adapter is created.

    Recycling mechanism

    ListView collection mechanism

    In order to ensure the reuse of Item View, ListView implements a set of recycling mechanism. The recycle mechanism is RecycleBin, which realizes two levels of cache:

    • View[] mActiveViews: Cache a View on the screen. Views in this cache do not need to call getView().

    • ArrayList

      [] mScrapViews; : Each Item Type corresponds to a list that is used as a recycle bin to cache views that have disappeared due to scrolling. Views here, if reused, are passed to getView() as arguments.

    Next, we analyze how ListView interacts with RecycleBin through source code. In fact, ListView and RecyclerView layout process is much the same, ListView layout function is layoutChildren(), the implementation is as follows:

    FillXxx () implements the filling of the Item View. This method internally calls makeAndAddView(), which is implemented as follows:

    GetActiveView () gets the appropriate View from mActiveViews. If it gets the View, it returns it directly without calling obtainView(). You do not need to call getView().

    ObtainView () gets the appropriate View from mScrapViews and passes it to getView() as a parameter.

    Getposition (Position) getPosition (position) getPosition (Position) getPosition (Position) GetPosition (Position) GetPosition (Position) GetPosition (Position) getPosition (Position)

    RecyclerView recycling mechanism

    RecyclerView and ListView recycle mechanism is very similar, but ListView recycles by View as unit, and Recycles by ViewHolder as unit. Recycler is a RecyclerView implementation that recycles four levels of caching:

    • MAttachedScrap: ViewHolder cached on the screen.

    • MCachedViews: Cache off-screen ViewHolder. The default value is 2. ListView calls getView() for all off-screen caches.

    • MViewCacheExtensions: Requires user customization and is not implemented by default.

    • MRecyclerPool: Cache pool shared by multiple RecyclerViews.

    The Layout process of RecyclerView has been introduced in the Layout Manager above, but the getViewForPosition() is ignored, so the implementation of this method is introduced here.

    From the above implementation, we can see that the ViewHolder can be reused from mAttachedScrap, mCachedViews, mViewCacheExtension, and mRecyclerPool. OnBindViewHolder () is not called if the ViewHolder is obtained from mAttachedScrap or mCachedViews, which are also known as Scrap Heap; OnBindViewHolder () is called if a ViewHolder is retrieved from mViewCacheExtension or mRecyclerPool.

    The realization principle of RecyclerView local refresh is also based on RecyclerView recycling mechanism, that is, ViewHolder that can be reused directly does not call onBindViewHolder().

    Nested sliding mechanism

    Android 5.0 introduced nested sliding mechanism. Previously, once the child View handles the touch event, the parent View has no chance to process the touch event. The nested sliding mechanism solves this problem and can achieve the following effects:

    To support nested sliding, the child View must implement the NestedScrollingChild interface, the parent View must implement the NestedScrollingParent interface, and RecyclerView implements the NestedScrollingChild interface. CoordinatorLayout implements the NestedScrollingParent interface. This is how it nested RecyclerView.

    To achieve the effect shown above, the components needed are:

    • CoordinatorLayout: Lays out the root element.

    • AppBarLayout: Wraps the content as the application Bar.

    • CollapsingToolbarLayout: Implements a collapsible ToolBar.

    • ToolBar: Replaces ActionBar.

    The points to note in the implementation are:

    • We set the ToolBar app:layout_collapseMode to PIN, which means it is fixed at the top, and the ImageView app:layout_collapseMode to parallax, which means the effect of the gradient.

    • In order for RecyclerView to support nested sliding, you also need to set app layout_behavior=”@string/appbar_scrolling_view_behavior”.

    • Set the app for CollapsingToolbarLayout: layout_scrollFlags = “scroll | exitUntilCollapsed”, said the scroll scroll out of the screen, exitUntilCollapsed said after folding.

    For details, see Demo6.

    review

    Review the whole article and see that we have implemented many of the RecyclerView extensions, including: create universal adapters, add Item events, add header and tail views, set up empty layout, side slide and drag. BaseRecyclerViewAdapterHelper RecyclerView extension library is a fire, a carefully look, found that there 80% of the functions in this article we all realized.

    If you think our content is good, please forward it to moments and share it with your friends


    This article is the exclusive content of Tencent Bugly. Please mark the author and source prominently at the beginning of the article “Tencent Bugly(http://bugly.qq.com)”.