RecyclerView is so powerful that it is hard to imagine we were using AbsListView not long ago. Make the most out of RecyclerView’s features and utility companions!
ItemToucherHelper makes dragging-and-dropping simple. SnapHelper takes the stress out of paging. DiffUtil hides the math of animating data movement and changes. We will cover these three APIs and learn from examples that make delightful experiences in complex list UIs.
I’m Eric. I work at IFTTT. We build the data layer for the internet to transfer data in your services as a neutral party across the internet (we’re hiring!). Today, I will talk about RecyclerView’s powerful companions: DiffUtil, SnapHelper, and ItemTouchListener.
These are APIs that are companions to the RecyclerView - you attach them, and they add basic characteristics, e.g. drag listeners and touch listeners onto RecyclerView and implement complex behavior for you.
Before RecyclerView, with ListView and GridView, we had the method
notifyDataSetChanged(), which you called from your adapter. To add animations, you had to do something clever. Everything in the view would rebind, and new data may or may not show up.
Get more development news like this
RecyclerView came along, and its adapter provided these additional notify methods:
notifyItemRangeMoved. And for the ranged ones, there are utility methods for single points.
NotifyItemRangeChanged is not a structural event, instead, it’s to notify that a subset of the data has changed. For example, when you have your timestamp update on your view in your list, and you need to rebind the data.
The other notify methods are structural changes, such as movement in your UI, like scrolling, or when content has been loaded.
Though the RecyclerView has notifyDataSetChanged, you almost never want to use it, as you will not be provided animations.
Before DiffUtil, if you wanted to animate the change, you needed to calculate all the inserted, removed, and moved calls. When views are coming in from off the screen where they’re not already currently on the screen, they wouldn’t have the correct animations. It wasn’t the perfect solution, but it was the best effort solution if you had complex data that changed rather than appending to a list.
The first thing you’ll notice in DiffUtil’s API is DiffUtil.callback, but that is a misnomer, as it is not a callback. Rather, it’s a provider: it’s providing information to DiffUtil so that it knows how to calculate the diff in your dataset.
The first two methods are self-explanatory: GetNewListSize, getOldListSize.
areItemsTheSame is for structural changes; e.g. when items are removed, added, and moved.
AreContentsTheSame is for data change. Is the timestamp different, for example, or did a textfield change?
DiffUtil can be computationally expensive for large datasets and will need to be done off of the UI thread. If new data comes in, you have to be careful of a raise condition where your backing dataset has changed but are still calculating the diff - this will result in your dataset is incorrectly reporting to DiffUtil.calculateDiff.
The SnapHelper is an abstract class and is a simple API. You can snap directly onto views, but more importantly are the concrete implementations.
SnapHelper attaches to RecyclerView, and RecyclerView doesn’t know anything about this class. It adds on the correct drag listeners and scroll listeners and implements the correct behavior.
A common implementation is LinearSnapHelper. With it, the items snap to the center of view by default. You can override findSnapView, and it gives your LayoutManager the ability to change it.
PagerSnapHelper is a replacement for ViewPager. The benefit to this is that you get recycled views, although this less of a concern because people aren’t flinging a ViewPager normally.
If you’re using ViewPager for a fragment state pager adapter or restoring any view state, RecyclerView doesn’t restore view state. You won’t get that in the API.
When you’re using PagerSnapHelper, make sure that both your RecyclerView and the RecyclerView’s children that the adapter creates, both their width and their height are match parent. Otherwise, SnapHelper won’t behave correctly.
Suppose we have ViewTreeObserver and it’s setting and resetting the transient state to support swiping to dismiss or dragging and dropping. What about the empty states? And what happens when my list is removed?
I need to destroy these callbacks and remove the viewTreeObservers; this presents a lot of complexity.
ItemTouchHelper does this with RecyclerView’s touch listener callback APIs. ItemTouchHelper is OnItemTouchListener. It’s an ItemDecoration and has a field for OnItemTouchListener.
The main API you implement and interact with ItemTouchListener is called the
ItemTouchHelper.Callback. We can compose drag and swipe flags, and make use of the make movement flags. It’s a convenience method for telling the callback which flags you support, and for dragging and dropping or swiping, and swiping left to right.
Supporting dragging is easy, as there is already a built-in API for long presses. By default it returns true, so you don’t even have to override this if you don’t want. You can return false if you want to override it.
Another handy API and a common use case is
canDropOver. This is helpful when trying to deal or discern header views. Is this item view on this view holder our header view?
onMove, you have
onSwiped. If you’ve swiped and you’ve successfully dismissed something, your callback doesn’t know about your adapter. You need to tell your adapter about the change.
Support swiping is convenience API for
isSwipeEnabled. By default, it’s true, but also you have the manual API where you can declare startSwipe. If there’s a handlebar, some affordance on your view, you tell the helper, this view holder with this view can start swiping now.
ItemTouchHelper is an ItemDecoration, and you can override in the callback
onChildDrawOver. Rather than in a normal item decoration, you get the action state. You know if you’re dragging or if you’re swiping. You can provide a red state that the item is about to be deleted as it’s being swiped.
In the rare case you need to create a custom LayoutManager, you can implement ViewDropHandler.
This API has one method,
prepareForDrop. If your LayoutManager implements ViewDropHandler, your callback will do an instance sub check for this type and will call prepareForDrop automatically on you whenever onMove starts. When you’re swiping or dragging, you’ll get these calls without having to do anything but implement ViewDropHolder on your custom LayoutManager.
Q & A
Q: Do you have any recommendations if you wanted to drag multiple items into RecyclerView at once? Eric: Yes, it’s not a use case I’ve come upon.
About the content
This talk was delivered live in July 2017 at 360 AnDev. The video was recorded, produced, and transcribed by Realm, and is published here with the permission of the conference organizers.