Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как реализовать DiffUtil в RecyclerView
DiffUtil - это утилита для вычисления разницы между двумя списками. Это критично для производительности RecyclerView потому что позволяет обновлять только изменённые элементы.
Проблема без DiffUtil
Без DiffUtil приходится делать:
adapter.submitList(newList)
или
adapter.notifyDataSetChanged()
Это пересчитывает весь список. Медленно для больших списков.
Решение: DiffUtil.Callback
Первый шаг - создать callback:
class UserDiffCallback(
private val oldList: List<User>,
private val newList: List<User>
) : DiffUtil.Callback() {
override fun getOldListSize() = oldList.size
override fun getNewListSize() = newList.size
override fun areItemsTheSame(oldPos: Int, newPos: Int): Boolean {
return oldList[oldPos].id == newList[newPos].id
}
override fun areContentsTheSame(oldPos: Int, newPos: Int): Boolean {
return oldList[oldPos] == newList[newPos]
}
override fun getChangePayload(oldPos: Int, newPos: Int): Any? {
val old = oldList[oldPos]
val new = newList[newPos]
return if (old != new) {
Bundle().apply {
if (old.name != new.name) putString("name", new.name)
if (old.avatar != new.avatar) putString("avatar", new.avatar)
}
} else null
}
}
Использование в Adapter
class UserAdapter(
private var users: List<User> = emptyList()
) : RecyclerView.Adapter<UserViewHolder>() {
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
holder.bind(users[position])
}
override fun getItemCount() = users.size
fun updateList(newList: List<User>) {
val callback = UserDiffCallback(users, newList)
val diff = DiffUtil.calculateDiff(callback)
users = newList
diff.dispatchUpdatesTo(this)
}
}
Что дают методы
areItemsTheSame() - проверяет это ОДИН и ТОТ ЖЕ элемент
Обычно по ID:
return oldList[oldPos].id == newList[newPos].id
areContentsTheSame() - проверяет ИЗМЕНИЛОСЬ ЛИ содержимое элемента
return oldList[oldPos] == newList[newPos]
getChangePayload() - конкретно ЧТО изменилось
Позволяет обновить только изменённые поля
ListAdapter - современный подход
С Android X есть встроенный ListAdapter:
class UserAdapter : ListAdapter<User, UserViewHolder>(
UserDiffCallback()
) {
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
holder.bind(getItem(position))
}
}
class UserDiffCallback : DiffUtil.ItemCallback<User>() {
override fun areItemsTheSame(old: User, new: User) = old.id == new.id
override fun areContentsTheSame(old: User, new: User) = old == new
}
// Использование
val adapter = UserAdapter() adapter.submitList(newList) // DiffUtil работает автоматически
Оптимизация с Payloads
Можешь обновлять только часть ViewHolder:
override fun onBindViewHolder(
holder: UserViewHolder,
position: Int,
payloads: List<Any>
) {
if (payloads.isEmpty()) {
onBindViewHolder(holder, position)
} else {
val bundle = payloads[0] as Bundle
if (bundle.containsKey("name")) {
holder.updateName(bundle.getString("name"))
}
if (bundle.containsKey("avatar")) {
holder.updateAvatar(bundle.getString("avatar"))
}
}
}
Производительность
DiffUtil вычисляет разницу на фоновом потоке (если использовать AsyncListDiffer).
class UserAdapter : RecyclerView.Adapter<UserViewHolder>() {
private val differ = AsyncListDiffer(
this,
UserDiffCallback()
)
fun submitList(list: List<User>) {
differ.submitList(list)
}
override fun getItemCount() = differ.currentList.size
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
holder.bind(differ.currentList[position])
}
}
Это не блокирует UI поток.
Когда использовать
Используй DiffUtil если:
- Список больше 5-10 элементов
- Часто обновляется
- У элементов есть уникальный ID
Для маленьких списков notifyDataSetChanged() достаточно.
Резюме
DiffUtil вычисляет разницу между старым и новым списком и обновляет только изменённые элементы. ListAdapter и AsyncListDiffer упростили использование. Это критично для производительности больших списков.