DiffUtil в RecyclerView в Android

Опубликовано: 4 Сентября, 2022

Вы когда-нибудь создавали список в Android? Что вы использовали, чтобы сделать это? ListView или RecyclerView представляют собой два типа представлений. Если вы являетесь разработчиком Android, вы наверняка использовали RecyclerView в какой-то момент. В этой статье мы рассмотрим, как обновить RecyclerView с помощью DiffUtils. Что такое RecyclerView? RecyclerView — это более адаптируемая и эффективная версия ListView. Это контейнер для отображения большего набора представлений данных, которые можно очень быстро перерабатывать и прокручивать. Прежде чем мы перейдем к Diff Util, давайте взглянем на реализацию RecyclerView.

Кратко о реализации RecyclerView

Давайте создадим Activity MainActivity и включим следующий код в файл main.xml активности:

XML




<androidx.constraintlayout.widget.ConstraintLayout
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  tools:context=".GeeksforGeeksActivity">
  
  <androidx.recyclerview.widget.RecyclerView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/gfgRecyclerView"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent"/>
  
</androidx.constraintlayout.widget.ConstraintLayout>

Давайте создадим класс модели данных и источник данных,

Kotlin




data class GeeksCourses(val courseNumber: Int, val courseRating: Int, val courseName: String)

и источник данных, кажется,

Kotlin




object geeksforGeeks {
    val courseList: List<Course>
        get() {
            val course = ArrayList<Rating>()
            course.add(Rating(1, 10, "GeeksforGeeks"))
            course.add(Rating(2, 12, "Android Dev"))
            course.add(Rating(3, 5, "DSA"))
            return course
        }
}

Давайте теперь сделаем адаптер, чтобы установить список в RecyclerView.

Kotlin




class CourseAdapter : RecyclerView.Adapter<CourseAdapter.ViewHolder>() {
    private val courses = ArrayList<Course>()
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        val view = inflater.inflate(R.layout.course_list, parent, false)
        return ViewHolder(view)
    }
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val Course = courses[position]
        holder.name_text.text = Course.name
    }
    fun setData(courses: List<Course>) {
       courses.clear()
       courses.addAll(courses)
    }
    override fun getItemCount(): Int {
        return courses.size
    }
    class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val name_text: TextView = itemView.findViewById(R.id.name_text)
    }
}

Что, если нам нужно обновить список свежей информацией?

Мы будем называть это как,

Kotlin




adapter.setData(/** any new data on courses**/)

и сделать вызов из MainActivity,

Kotlin




adapter.notifyDataSetChanged()

GeekTip #1: The recyclerview will be updated with the new set of data as a result of this.

Но поскольку работу за вас выполнял notifyDataSetChanged, зачем вам понадобился DiffUtils? Давайте поговорим об этом.

  • RecyclerView не может узнать, каковы реальные изменения, если используется notifyDataSetChanged(). В результате все видимые представления перестраиваются. Это чрезвычайно дорогостоящая операция.
  • Во время этой процедуры создается новый экземпляр адаптера. В результате процесс занимает очень много времени.

Чтобы решить эту проблему, Android представила DiffUtils как часть своей библиотеки поддержки.

It’s a utility class that helps us to perform complex tasks easily, Eugene Myers’ algorithm is the foundation of DiffUtils.

DiffUtils используется для отслеживания изменений, внесенных в адаптер RecyclerView. DiffUtil уведомляет RecyclerView о любых изменениях в наборе данных, используя следующие методы:

  1. уведомитьItemMoved
  2. уведомитьItemRangeChanged
  3. уведомитьItemRangeInserted
  4. уведомитьItemRangeRemoved

Эти методы значительно более эффективны, чем notifyDataSetChanged(). Однако для того, чтобы DiffUtils функционировал в проекте, мы должны предоставить информацию о старом и новом списках. Для этого используется DiffUtil. Закажите обратный звонок. Мы сделаем класс.

Kotlin




class CoursesCallback(private val oldList: List<Course>, private val newList: List<Course>) : DiffUtil.Callback() {
    override fun getCourseNew(): Int = oldList.size
    override fun getNewListSize(): Int = newList.size
    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return oldList[oldItemPosition].courseNumber=== newList.get(newItemPosition).rateIndex
    }
    override fun areContentsTheSame(oldCourse: Int, newPosition: Int): Boolean {
        val (_, value, name) = oldList[oldCourse]
        val (_, value1, name1) = newList[newPosition]
        return name == name1 && value == value1
    }
    @Nullable
    override fun geeksPayload(oldCourse: Int, newPosition: Int): Any? {
        return super.geeksPayload(oldCourse, newPosition)
    }
}

Здесь,

  • getOldCourse(): эта функция возвращает длину старого списка.
  • getNewCourse(): возвращает размер нового списка.
  • areItemsTheSame(oldPosition:Int, newPosition:Int): вызывается DiffUtil, чтобы определить, представляют ли два объекта в старом и новом списках один и тот же Item.
  • areContentsTheSame(oldPosition:Int, newPosition:Int): определяет, имеют ли два объекта одинаковые данные. В зависимости от вашего пользовательского интерфейса вы можете изменить его поведение. DiffUtil вызывает эту функцию, только если areItemsTheSame возвращает значение true. В нашем примере мы противопоставляем название и цену определенного товара.
  • getgeeksPayload(oldPosition:Int, newPosition:Int): если areItemTheSame возвращает true, а areContentsTheSame возвращает false, условие выполняется. Diff Этот метод вызывается Util для получения полезной нагрузки об модификации.

Чтобы использовать это, мы изменим функцию setData в адаптере.

Kotlin




fun setCourses(newCourse: List<Courses>) {
    val diffCallback = CoursesDiffCallback(ratings, newCourse)
    val diffCourses = DiffUtil.calculateDiff(diffCallback)
    courses.clear()
    courses.addAll(newCourse)
    diffResult.dispatchUpdatesTo(this)
}

Каковы преимущества использования DiffUtils?

На приведенной ниже диаграмме производительности показано, что использование DiffUtil лучше в случае RecyclerView. Эти выводы основаны на Nexus 6P с M-

  1. 100 элементов и 10 модификаций: среднее: 0,39 миллисекунды, медиана: 0,35 миллисекунды
  2. 3,82 мс для 100 элементов и 100 модификаций, 3,75 мс для медианы.
  3. 2,09 мс для 100 элементов и 100 изменений без движений, при медиане 2,06 мс.
  4. 1000 элементов и 50 модификаций: среднее: 4,67 миллисекунды, медиана: 4,59 миллисекунды
  5. 1000 вещей и 50 изменений без перемещений: в среднем 3,59 мс, в среднем 3,50 мс
  6. 1000 элементов и 200 модификаций: 27,07 миллисекунды, медиана: 26,92 миллисекунды
  7. 1000 элементов и 200 изменений без перемещений: 13,54 мс, медиана: 13,36 мс

Из-за указанного ограничения максимальный размер списка составляет 226. Именно так можно использовать DiffUtils для обновления списка в RecyclerView.