RecyclerView
is common widget used in Android. It has build-in methods to handle insert, change, and removed.
If you uses those methods, RecyclerView
will animate it accordingly instead of refresh the whole list.
- Insert: Items after the inserted item move down.
- Update: Updated item changes content instead of view refresh.
- Remove: Items after the removed item move up.
Prerequisite
- Able to create a working RecyclerView
The old fashion way
RecyclerView.Adapter
has notifyDataSetChanged()
, notifyItemChanged(int)
, notifyItemInserted(int)
, notifyItemRemoved(int)
, notifyItemRangeChanged(int, int)
, notifyItemRangeInserted(int, int)
, notifyItemRangeRemoved(int, int)
.
notifyDataSetChanged()
is the simplest way to notify the adapter that the data has changed. However, adapter do not know what has changed, so it will refresh all the views. Many programmer will just use notifyDataSetChanged()
after updating the list.
In order to allow RecyclerView
to animate item update, changes must be notified specifically. There are two ways to do it:
First one is to extend existing list class and dispatch changes on different methods. While this is the efficient to dispatch changes, it requires all the methods to be extended, including method like sort and filter.
Second one is comparing current list and new list to calculate the different and dispatch changes accordingly. While this is less efficient, it is a more generic way to handle all the cases.
However, both ways have to implement it yourself.
The new way
DiffUtil
is added in version 25.1.0 which has implemented the second way for you.
class CustomAdapter : RecyclerView.Adapter<CustomViewHolder>() {
private val items = listOf<CustomModel>()
fun bind(items: List<CustomModel>) {
val diffCallback = DiffableListCallback(this.items, items)
val diff = DiffUtil.calculateDiff(diffCallback)
diff.dispatchUpdatesTo(this)
}
// Other method implementations
}
DiffableListCallback
is an implementation of DiffUtil.Callback
which contains logic on how to compare the items.
interface Diffable {
fun isSame(other: Any): Boolean
fun isContentSame(other: Any): Boolean
}
class DiffableListCallback<T : Diffable>(
private val oldList: List<T>,
private val newList: List<T>
) : DiffUtil.Callback() {
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
oldList[oldItemPosition].isSame(newList[newItemPosition])
override fun getOldListSize(): Int = oldList.size
override fun getNewListSize(): Int = newList.size
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
oldList[oldItemPosition].isContentSame(newList[newItemPosition])
}
Two items are the same if they are refer to the same item. For example, in terms of database, it means they have the same primary key.
Two items are content same if they are equal in value. If two item are equal in value, it means there are no needs to update the item.
Therefore, Diffable
delegates the comparison logic to the item. Combine Diffable
with data class which implements value equality, you only need to implement isSame
.
data class CustomModel(val id: Long, val text: String): Diffable {
override fun isSame(other: Any): Boolean {
val oth = other as? CustomModel ?: return false
return id == oth.id
}
override fun isContentSame(other: Any): Boolean = this == other
}
After you have update the list, you can just update the items in the adapter and DiffUtil
will calculate the changes for you.
val newItems = items.toMutableList()
newItems.add(CustomModel(99, "99"))
adapter.bind(newItems)