You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android Kotlin中替换notifyDataSetChanged()的优雅实现方案

优化Firebase数据更新RecyclerView:替换notifyDataSetChanged()的高效方案

你的代码中每次通过ValueEventListener获取全量预订数据后,直接清空列表再重新填充,最后调用notifyDataSetChanged(),这种方式会触发RecyclerView全量重绘,效率较低。下面提供两种更优雅高效的替换方案:

方案一:用DiffUtil优化全量更新场景

如果仍需要保留全量获取数据的逻辑,DiffUtil可以帮你计算新旧数据集的差异,只更新真正变化的部分,避免全量重绘。

步骤1:创建DiffCallback类

在适配器中添加一个继承自DiffUtil.ItemCallback<Reservation>的类,用来判断两个项是否相同以及内容是否变化:

class ReservationDiffCallback : DiffUtil.ItemCallback<Reservation>() {
    override fun areItemsTheSame(oldItem: Reservation, newItem: Reservation): Boolean {
        // 用预订的唯一标识判断(比如id),确保是同一个项
        return oldItem.id == newItem.id
    }

    override fun areContentsTheSame(oldItem: Reservation, newItem: Reservation): Boolean {
        // 对比项的所有内容,判断是否需要更新视图
        return oldItem == newItem
    }
}

步骤2:修改适配器支持DiffUtil

让你的适配器继承ListAdapter<Reservation, YourViewHolder>(ReservationDiffCallback()),这样就可以直接使用DiffUtil的能力:

class ReservationsAdapter : ListAdapter<Reservation, ReservationsAdapter.ViewHolder>(ReservationDiffCallback()) {
    class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        // ... 绑定视图的代码
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        // ... 创建ViewHolder的代码
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val reservation = getItem(position)
        // ... 绑定数据到视图的代码
    }
}

步骤3:更新数据时提交新列表

在onDataChange中,不再手动操作列表,直接提交新列表给适配器:

override fun onDataChange(snapshot: DataSnapshot) {
    val newReservationList = mutableListOf<Reservation>()
    for (ds: DataSnapshot in snapshot.children){
        ds.getValue(Reservation::class.java)?.let {
            newReservationList.add(it)
        }
    }
    newReservationList.reverse()
    
    if(newReservationList.isEmpty()) {
        binding.recyclerMyReservations.hide()
        binding.textEmptyList.show()
    } else {
        binding.recyclerMyReservations.show()
        binding.textEmptyList.hide()
    }
    
    dialog?.dismiss()
    // 提交新列表,DiffUtil自动计算差异并更新
    adapterReservations.submitList(newReservationList)
}

方案二:用ChildEventListener监听单个节点变化(更高效)

ValueEventListener会每次返回全量数据,而ChildEventListener可以监听单个节点的增删改移,每次只更新变化的项,避免全量刷新,效率更高。

修改代码使用ChildEventListener

private fun configRecoverReservations(){
    val dialog: AlertDialog? = SpotsDialog.Builder()
        .setContext(requireContext())
        .setMessage(getString(R.string.recovering_reservations))
        .setCancelable(false)
        .build()
    dialog?.show()
    
    val reservationList = mutableListOf<Reservation>()
    adUserRef = FirebaseDatabase.getInstance()
        .getReference("Reservations")
        .child(auth.currentUser!!.uid)
    
    adUserRef.addChildEventListener(object : ChildEventListener {
        override fun onChildAdded(snapshot: DataSnapshot, previousChildName: String?) {
            snapshot.getValue(Reservation::class.java)?.let { reservation ->
                // 根据previousChildName确定插入位置,保持顺序
                val insertIndex = previousChildName?.let { name ->
                    reservationList.indexOfFirst { it.id == name } + 1
                } ?: 0
                reservationList.add(insertIndex, reservation)
                adapterReservations.notifyItemInserted(insertIndex)
            }
            
            checkEmptyList(reservationList)
            dialog?.dismiss()
        }

        override fun onChildChanged(snapshot: DataSnapshot, previousChildName: String?) {
            snapshot.getValue(Reservation::class.java)?.let { updatedReservation ->
                val index = reservationList.indexOfFirst { it.id == updatedReservation.id }
                if (index != -1) {
                    reservationList[index] = updatedReservation
                    adapterReservations.notifyItemChanged(index)
                }
            }
        }

        override fun onChildRemoved(snapshot: DataSnapshot) {
            snapshot.getValue(Reservation::class.java)?.let { removedReservation ->
                val index = reservationList.indexOfFirst { it.id == removedReservation.id }
                if (index != -1) {
                    reservationList.removeAt(index)
                    adapterReservations.notifyItemRemoved(index)
                }
            }
            checkEmptyList(reservationList)
        }

        override fun onChildMoved(snapshot: DataSnapshot, previousChildName: String?) {
            snapshot.getValue(Reservation::class.java)?.let { movedReservation ->
                val oldIndex = reservationList.indexOfFirst { it.id == movedReservation.id }
                if (oldIndex != -1) {
                    reservationList.removeAt(oldIndex)
                    val newIndex = previousChildName?.let { name ->
                        reservationList.indexOfFirst { it.id == name } + 1
                    } ?: 0
                    reservationList.add(newIndex, movedReservation)
                    adapterReservations.notifyItemMoved(oldIndex, newIndex)
                }
            }
        }

        override fun onCancelled(error: DatabaseError) {
            dialog?.dismiss()
        }
    })
}

// 抽做空列表检查逻辑
private fun checkEmptyList(list: List<Reservation>) {
    if(list.isEmpty()) {
        binding.recyclerMyReservations.hide()
        binding.textEmptyList.show()
    } else {
        binding.recyclerMyReservations.show()
        binding.textEmptyList.hide()
    }
}

注意事项

  • 使用ChildEventListener时,初始加载会逐个调用onChildAdded,适合实时更新场景;
  • 确保Reservation类有唯一标识(比如id字段),用来精准定位列表中的项;
  • 关于列表反转,如果后端数据是按创建时间正序存储,可以在onChildAdded时直接把新项插入到列表头部,避免后续全量反转。

内容的提问来源于stack exchange,提问作者Tods

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 16:10:40