← Назад к вопросам

Как реализовать DiffUtil в RecyclerView?

2.2 Middle🔥 171 комментариев
#UI и вёрстка

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как реализовать 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 упростили использование. Это критично для производительности больших списков.