recyclerview的item的局部刷新 payloads用法和实现原理

852 阅读4分钟

RecyclerView 中使用 payloads 可以实现 局部刷新,避免整个 item 的重新绑定,提高性能。局部刷新通过 notifyItemChanged(position, payload) 来触发,payload 参数可以携带数据,指定更新内容,从而避免不必要的视图重建。

一、局部刷新的用法

以下是使用 payloads 进行局部刷新的详细步骤:

1. 覆写 onBindViewHolder(holder, position, payloads)

RecyclerView.Adapter 中覆写 onBindViewHolder 方法(带 payloads 参数的重载版本):

kotlin
复制代码
override fun onBindViewHolder(holder: MyViewHolder, position: Int, payloads: MutableList<Any>) {
    if (payloads.isNotEmpty()) {
        // 使用 payloads 刷新局部内容
        val payload = payloads[0] as? Bundle // 或者其他类型,根据具体的需求
        if (payload?.containsKey("KEY_NAME") == true) {
            holder.nameTextView.text = payload.getString("KEY_NAME")
        }
        if (payload?.containsKey("KEY_AGE") == true) {
            holder.ageTextView.text = payload.getInt("KEY_AGE").toString()
        }
    } else {
        // 当 payloads 为空时,进行全量绑定
        super.onBindViewHolder(holder, position, payloads)
    }
}

如果 payloads 非空,则表示局部刷新,只更新指定的内容;如果 payloads 为空,意味着触发了全量刷新(会调用另一个 onBindViewHolder(holder, position) 版本)。

2. 触发局部刷新

在更新数据时,调用 notifyItemChanged(position, payload),并传入具体的 payload 数据。例如:

kotlin
复制代码
fun updateNameAt(position: Int, newName: String) {
    val payload = Bundle().apply { putString("KEY_NAME", newName) }
    notifyItemChanged(position, payload)
}

fun updateAgeAt(position: Int, newAge: Int) {
    val payload = Bundle().apply { putInt("KEY_AGE", newAge) }
    notifyItemChanged(position, payload)
}

上面的代码将 newNamenewAge 作为 payload 传递给 notifyItemChanged(),这样就能在 onBindViewHolder 中接收到具体的 payload 数据,从而实现局部刷新。

3. 覆写 onBindViewHolder(holder, position) 的全量绑定逻辑

payloads 为空时,需要在全量绑定的 onBindViewHolder(holder, position) 中实现完整的视图绑定逻辑:

kotlin
复制代码
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
    val item = itemList[position]
    holder.nameTextView.text = item.name
    holder.ageTextView.text = item.age.toString()
}

4. 使用场景

  • 数据部分变化:例如,一个 User 列表中,只更新用户的年龄或名字,而不必刷新整个 item
  • 提高性能:当 item 复杂、刷新成本高时,使用 payload 可避免整个 item 重建。
  • 局部交互:如点击后只改变局部内容(比如点赞图标),适合用 payload 局部刷新。

代码示例总结

kotlin
复制代码
class MyAdapter : RecyclerView.Adapter<MyViewHolder>() {

    // 绑定局部更新逻辑
    override fun onBindViewHolder(holder: MyViewHolder, position: Int, payloads: MutableList<Any>) {
        if (payloads.isNotEmpty()) {
            val payload = payloads[0] as? Bundle
            payload?.getString("KEY_NAME")?.let {
                holder.nameTextView.text = it
            }
            payload?.getInt("KEY_AGE")?.let {
                holder.ageTextView.text = it.toString()
            }
        } else {
            super.onBindViewHolder(holder, position, payloads)
        }
    }

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        // 完整绑定逻辑
        val item = itemList[position]
        holder.nameTextView.text = item.name
        holder.ageTextView.text = item.age.toString()
    }
}

通过 payload 局部刷新,RecyclerView 可以更有效地更新视图,避免不必要的重绘。

局部刷新的原理

RecyclerView 中实现局部刷新的原理是基于 DiffUtil 以及 payloads,它们帮助减少刷新数据的量和视图的重绘次数,从而提高性能和用户体验。

1. DiffUtil 的工作原理

DiffUtil 是 Android 提供的一个工具类,用来计算新旧数据集之间的差异。它通过比较新旧列表来得出每个 item 的变化,生成一个包含这些差异的 DiffResult,并应用到 RecyclerView 中。这种方式避免了全量刷新,可以识别出哪些 item 被增、删、移动或改变,实现局部刷新。

核心步骤:

  1. 使用 DiffUtil.Callback 实现对比新旧数据集。
  2. 调用 DiffUtil.calculateDiff 方法获取差异数据。
  3. 使用 DiffResult.dispatchUpdatesTo 将差异应用到 RecyclerView.Adapter
kotlin
复制代码
val diffResult = DiffUtil.calculateDiff(MyDiffCallback(oldList, newList))
diffResult.dispatchUpdatesTo(adapter)

这种方式的局部刷新原理是减少 RecyclerView 中不必要的 ViewHolder 重建和数据绑定,只更新变化的 item,从而优化性能。

2. Payloads 的局部刷新原理

RecyclerView.Adapter 提供的 payloads 参数让我们能够控制 ViewHolder 只更新特定的部分,而不必重绘整个 item。它通常和 notifyItemChanged(position, payload) 方法一起使用。

  • 当调用 notifyItemChanged(position, payload) 时,RecyclerView 会将 payload 数据传递给 onBindViewHolder(holder, position, payloads) 方法。
  • 在这个方法中,可以根据 payloads 内容判断需要更新的部分(例如只更新一个 TextView 的内容,而不重绘整个 item)。

示例

假设一个 item 只需要更新名字,而不需要更新其他内容:

kotlin
复制代码
override fun onBindViewHolder(holder: MyViewHolder, position: Int, payloads: MutableList<Any>) {
    if (payloads.isNotEmpty()) {
        val payload = payloads[0] as? Bundle
        payload?.getString("KEY_NAME")?.let {
            holder.nameTextView.text = it
        }
    } else {
        super.onBindViewHolder(holder, position, payloads)
    }
}

在这种实现方式下,只有名字的 TextView 被更新,而 RecyclerView 的其余部分保持不变,这样可以避免重新加载和重新布局。

3. 局部刷新带来的性能提升

局部刷新之所以有效,是因为 RecyclerView 通过 payloadsDiffUtil 的计算,能够精确地识别并只更新需要更改的视图,减少了 ViewHolder 的重建,节约了布局时间和绘制资源,进而提升性能。对于大数据量列表尤其显著。

总结

通过 DiffUtil 的数据对比和 payloads 的局部更新,RecyclerView 实现了局部刷新,从而优化了 UI 刷新的效率和用户体验。