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
相关产品推荐
相关产品推荐

