LiveData驱动的RecyclerView删除条目跳转且效率低,最优更新方法是什么?
RecyclerView结合LiveData更新的最优实现方案
你当前实现的核心问题是每次LiveData数据变更时都会重新实例化适配器、重新为RecyclerView绑定适配器,相当于完全重建整个列表控件,因此会触发列表滚动回顶部,同时全量重绘所有条目,性能损耗极高。
推荐使用官方提供的ListAdapter配合DiffUtil.ItemCallback实现增量更新,该方案会自动在后台线程计算新旧列表的差异,仅更新发生变动的条目,不会触发全量重绘,也不会导致列表跳转回顶部,是RecyclerView更新的标准最优实现。
1. 定义条目差异判断规则
首先创建DiffUtil.ItemCallback实现类,用来对比新旧条目的唯一性和内容差异:
val BasketItemDiffCallback = object : DiffUtil.ItemCallback<BasketItem>() { // 判断两个条目是否是同一个对象(通常对比唯一ID) override fun areItemsTheSame(oldItem: BasketItem, newItem: BasketItem): Boolean { return oldItem.id == newItem.id } // 判断同一个条目的内容是否发生变化 override fun areContentsTheSame(oldItem: BasketItem, newItem: BasketItem): Boolean { return oldItem == newItem } }
2. 改造适配器继承ListAdapter
适配器不需要自己维护数据集,直接继承ListAdapter,内部通过currentList获取当前列表数据即可:
class BasketFragmentAdapter( val viewModel: BasketViewModel, val context: Context ) : ListAdapter<BasketItem, BasketFragmentAdapter.ViewHolder>(BasketItemDiffCallback) { // 正常实现ViewHolder创建和绑定逻辑 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val view = LayoutInflater.from(context).inflate(R.layout.item_basket, parent, false) return ViewHolder(view) } override fun onBindViewHolder(holder: ViewHolder, position: Int) { val item = currentList[position] // 绑定条目数据,删除按钮点击逻辑直接调用viewModel的删除方法即可 holder.deleteBtn.setOnClickListener { viewModel.deleteBasketItem(item.id) } } class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val deleteBtn: Button = itemView.findViewById(R.id.btn_delete) } }
3. 调整初始化和观察者逻辑
适配器只在页面初始化时实例化一次,后续LiveData更新时只需要提交新列表即可:
// 页面初始化时只初始化一次适配器 private lateinit var adapter: BasketFragmentAdapter // 在onViewCreated中完成初始化 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) adapter = BasketFragmentAdapter(viewModel, requireContext()) recycler.adapter = adapter // 观察者只需要提交新列表,不需要重建适配器 viewModel.loadBasketItems().observe(viewLifecycleOwner) { adapter.submitList(it) } }
可选补充:手动使用DiffUtil实现
如果你不想使用封装好的ListAdapter,也可以手动计算差异更新,但需要自行处理线程调度,避免大列表计算卡顿主线程:
viewModel.loadBasketItems().observe(viewLifecycleOwner) { newList -> val diffResult = DiffUtil.calculateDiff(object : DiffUtil.Callback() { override fun getOldListSize() = adapter.dataList.size override fun getNewListSize() = newList.size override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { return adapter.dataList[oldItemPosition].id == newList[newItemPosition].id } override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { return adapter.dataList[oldItemPosition] == newList[newItemPosition] } }) adapter.dataList = newList diffResult.dispatchUpdatesTo(adapter) }
内容的提问来源于stack exchange,提问作者Dragos Ciupe
相关产品推荐
相关产品推荐

