RecyclerView.Adapter变更时刷新Fragment视图的问题(基于Shrine示例)
首先咱们来拆解你遇到的问题:
你第一次尝试的代码里,调用theList.pop(1)后notifyDataSetChanged()没生效,大概率是你修改的列表和适配器持有的列表不是同一个引用,或者列表本身是不可变的(比如ProductEntry.initProductEntryList返回的是List而非MutableList)。如果是不可变列表,pop(1)根本不会修改原数据,适配器自然感知不到变化;如果是重新给theList赋值了新列表,但适配器还是拿着旧列表的引用,那通知刷新也没用。
而你编辑1里重新创建适配器的方法虽然能工作,但确实不够高效——每次都要销毁旧适配器、创建新实例、重新绑定所有数据,数据量大的时候会有明显的性能损耗。
正确的高效刷新方式
1. 基础版:复用现有适配器,更新内部列表
首先,确保适配器使用的是可变列表,并且你修改的是适配器持有的同一个列表实例。
第一步,初始化时就把列表转成可变的,并绑定给适配器:
// 把不可变列表转成可变列表,确保后续能修改 val mutableProductList = ProductEntry.initProductEntryList(resources).toMutableList() adapter = StaggeredProductCardRecyclerViewAdapter(mutableProductList) // 给RecyclerView设置适配器(这一步很重要,别漏了) view.recycler_view.adapter = adapter
然后在点击事件里直接修改这个可变列表,再调用适配器的通知方法:
val featured = view.findViewById(R.id.featured) as Button featured.setOnClickListener { if (mutableProductList.isNotEmpty()) { mutableProductList.removeAt(0) // 对应你之前的pop(1)操作,移除第一个元素 // 用细粒度的通知代替notifyDataSetChanged,性能更好 adapter.notifyItemRemoved(0) // 如果后续元素位置有变化,notifyItemRemoved会自动处理移位,不需要额外通知 } }
2. 进阶版:切换分类时高效更新数据
如果你是要切换分类(比如加载"featured"分类的商品),不要重新创建适配器,而是给适配器加一个更新数据的方法:
先修改你的适配器类,添加更新数据的函数:
class StaggeredProductCardRecyclerViewAdapter( private var productList: MutableList<ProductEntry> ) : RecyclerView.Adapter<StaggeredProductCardRecyclerViewAdapter.ViewHolder>() { // 新增:更新数据的方法 fun updateProductList(newList: List<ProductEntry>) { productList.clear() productList.addAll(newList) // 全量刷新(如果要更高效,用下面的DiffUtil方案) notifyDataSetChanged() } // 其他原有代码... }
然后在点击事件里调用这个方法:
featured.setOnClickListener { val newFeaturedList = ProductEntry.initProductEntryList(resources, "featured").toMutableList() adapter.updateProductList(newFeaturedList) }
3. 最优版:用DiffUtil实现局部刷新
如果列表数据量大,notifyDataSetChanged()会刷新整个列表,还是有点浪费。可以用Android的DiffUtil来计算新旧列表的差异,只刷新真正变化的项:
修改适配器的updateProductList方法:
fun updateProductList(newList: List<ProductEntry>) { val diffCallback = object : DiffUtil.Callback() { override fun getOldListSize() = productList.size override fun getNewListSize() = newList.size // 判断两个条目是否是同一个商品(需要ProductEntry有唯一标识,比如id) override fun areItemsTheSame(oldPos: Int, newPos: Int): Boolean { return productList[oldPos].id == newList[newPos].id } // 判断两个条目内容是否一致 override fun areContentsTheSame(oldPos: Int, newPos: Int): Boolean { return productList[oldPos] == newList[newPos] } } // 计算差异 val diffResult = DiffUtil.calculateDiff(diffCallback) // 更新内部列表 productList.clear() productList.addAll(newList) // 把差异结果分发给适配器,只刷新变化的项 diffResult.dispatchUpdatesTo(this) }
这样切换分类时,RecyclerView只会刷新新增、删除或内容变化的项,性能会好很多。
关键总结
- 永远不要每次数据变化就重新创建适配器,复用现有实例才是高效做法
- 确保你修改的是适配器持有的列表实例,或者通过适配器的方法更新内部数据
- 优先用细粒度的通知(如
notifyItemRemoved、notifyItemAdded)或DiffUtil代替全量的notifyDataSetChanged
内容的提问来源于stack exchange,提问作者Curcuma_

