[Day4] Android - Kotlin笔记:RecyclerView Adapter - ListAdapter + DiffUtil

来介绍一下DiffUtil

以往我们在使用RecyclerView时最常使用的是
RecyclerView.Adapter及其更新方式notifyDataSetChanged()
此篇会介绍新的Adapter - ListAdapter以及新的更新方法 - DiffUtil

ListAdapter+DiffUtil在动态更新上,
效能比以往大提升,撰写上也方便且省code。


我们以前使用notifyDataSetChanged()在每次更新时,
都会去重新绑定list中的每一笔item的viewHolder
以及重新绘制item的view(包含未显示在萤幕上的item)的耗能。
此外notifyDataSetChanged()也容易会造成大量萤幕更新,
显示上会使user看到萤幕闪烁、滚动时的弹跳。

而为了解决这件事,会使用RecyclerView提供的工具来取代notifyDataSetChanged()

notifyItemInserted(position)
notifyItemChanged(position)
notifyItemMoved(fromPosition, toPosition)、
notifyItemRemoved(position)

notifyItemRangeInserted(position, count)
notifyItemRangeChanged(position, count)
notifyItemRangeRemoved(position, count)
notifyItemRangeChanged(position, count, payload)

手动找出list修改的部分,以进行新增、删除、移动、更新那些变动的item。

现在,我们可以使用DiffUtil自带的演算法,
搭配ListAdapter来帮你搞定一切更新。


DiffUtil介绍

DiffUtil是为了改善RecyclerView的更新效能而生。
他能自动帮你判断新进来的资料与旧资料是否相同、移动、新增、删除⋯⋯等等。


ListAdapter介绍

ListAdapter是继承RecyclerView.Adapter的adapter:

  • 优点1: 它包含了初始化DiffUtil.ItemCallback的方式在里面
  • 优点2: 它内建了submitList(list)的function,
    可以取代每次我们在RecyclerView.Adapter中都需要撰写用来更新的dataList
  • 优点3: 不需再覆写getItemCount()ListAdapter会帮你计算好size
    //用ListAdapter就不用再每次重写他了
    var dataList = listOf<T>()
        set(value) {
            field = value
            notifyDataSetChanged()
        }

接下来我们就来下实作范例:

实作ListAdapter以及DiffUtil.ItemCallback<DataClass>

(以PersonDataClass作范例)。


ListAdapter实作

先新增一个Person作为本文范例list的DataClass

data class Person(
    val id: Long,
    val name: String
)

改写上我们将原本的RecyclerView.Adapter

class RvAdapter() :
    RecyclerView.Adapter<RecyclerView.ViewHolder>() {

替换为

class RvAdapter() : 
    ListAdapter<Person, RecyclerView.ViewHolder>(DiffCallback())

就可以了。

注:

  • DiffCallback()DiffUtil.ItemCallback的class

DiffUtil实作

新增DiffCallbackclass,extends DiffUtil.ItemCallback<DataClass>()
并implement需要覆写的方法:

    class DiffCallback : DiffUtil.ItemCallback<Person>() {
        //1
        override fun areItemsTheSame(oldItem: Person, newItem: Person): Boolean {
            TODO("Not yet implemented")
        }
        //2
        override fun areContentsTheSame(oldItem: Person, newItem: Person): Boolean {
            TODO("Not yet implemented")
        }
    }

1.areItemsTheSame用来判断item是否被编辑、移动、或删除,
在这边我们使用id来作为判断。
(注:如果id一样的话,他会被当成一样的item来判断)
此方法能使DiffUtil自动帮我们判断这笔id现在位置在哪,
来使用最佳方法更新数据及展示相对应的更新动画。

2.areContentsTheSame是为了避免在发生更改时重新设计整个列表,
只会更新两个列表之间具有不同值的项目。

    class DiffCallback : DiffUtil.ItemCallback<Person>() {
        override fun areItemsTheSame(oldItem: Person, newItem: Person): Boolean {
            return oldItem.id == newItem.id
        }

        override fun areContentsTheSame(oldItem: Person, newItem: Person): Boolean {
            return oldItem == newItem
        }
    }

areItemsTheSame用来确认新旧item是否有相同id(也可以是不同的值)
areContentsTheSame来确认新旧item内容是否完全相等。


更新ListAdapter中的data

以往取值方式为:


    var dataList = listOf<Person>()
        set(value) {
            field = value
            notifyDataSetChanged()
        }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
       val name = dataList.getOrNull(position)
    }

使用ListAdapter,直接用getItem就可取得值:

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
       val name = getItem(position)
    }

Data在ListAdapter的使用

ListAdapter中我们拔除了以前拿来更新用的dataList,
提交新list时,只要使用submitList,让DiffUtil演算法来计算就可以了。

        //activity、fragment
        viewModel.personListResult.observe(viewLifecycleOwner, { resultList ->
                rvAdapter.submitList(resultList)
        })

全程序码范例展示:
(有时间来补上github连结给各位)

data class Person(
    val id: Long?,
    val name: String?
)

class PersonAdapter : ListAdapter<Person, RecyclerView.ViewHolder>(DiffCallback()) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return ItemViewHolder.from(parent)
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (holder) {
            is ItemViewHolder -> {
                holder.bind(getItem(position))
            }
        }
    }

    class ItemViewHolder private constructor(val view: View) : RecyclerView.ViewHolder(view) {
        fun bind(item: Person) {
            itemView.tv_play_name.text = item.name
        }

        companion object {
            fun from(parent: ViewGroup): RecyclerView.ViewHolder {
                val binding = LayoutInflater.from(parent.context)
                    .inflate(R.layout.itemview_person, parent, false)
                return ItemViewHolder(binding)
            }
        }
    }

    class DiffCallback: DiffUtil.ItemCallback<Person>() {
        override fun areItemsTheSame(oldItem: Person, newItem: Person): Boolean {
            return oldItem == newItem
        }

        override fun areContentsTheSame(oldItem: Person, newItem: Person): Boolean {
            return oldItem == newItem
        }

    }

}

参考资源:


<<:  [13th-铁人赛]Day 2:Modern CSS 超详细新手攻略 - 入门

>>:  Day 7 jinja (2)

D28 / Compose 可以用来做 Desktop App? - Compose JB

写到最後三天了,想要聊聊和 Android 不完全相关的东西。感谢JetBrains 的开发,和 K...

Day-3: Rails的Route + MVC架构

MVC模式(Model–view–controller) 是软件工程中的一种软件架构模式, 把软件...

终於 30 天了,可以偷懒了ㄅ

终於最後一天,照惯例大家都在这里写後记吧~ 三年前第一次知道铁人赛,就是以社群身份去参加了颁奖典礼X...

Kotlin Android 第15天,从 0 到 ML - Android Jetpack

前言: 前两遍的基础activity 和 fragment 就可以作出不错的app了,但功能愈来愈多...

ASP.NET MVC 从入门到放弃(Day20) -MVC模型(Model) Entity Framework

接下来讲讲Entity Framework 如何建立... 首先先开启visual studio.....