ListAdapter提交列表未更新,删除数据出现重复项求助
Android ListAdapter 删除数据后出现重复行的问题修复
问题描述
使用ListAdapter实现RecyclerView列表时,删除任意行数据后会出现列表重复渲染的情况:比如删除首行后,首行仍保留且第二行重复;删除其他行也会出现数据重复展示的问题。
问题根源分析
从提供的代码来看,主要问题集中在数据一致性维护和LiveData监听逻辑上:
- Fragment中
convertedArticleList在LiveData回调里未清空旧数据就追加新数据,导致多次触发时数据重复累积 - 每次
onResume重复注册LiveData Observer,加重数据重复问题 - 手动调用Adapter的
deleteItem修改内部列表,但Fragment数据源未同步,后续LiveData触发时旧数据被再次添加 Article类可能未正确实现equals方法,导致DiffUtil无法准确识别内容变化
解决方案
1. 修复Fragment的数据监听与更新逻辑
将LiveData Observer移至onViewCreated仅注册一次,每次收到新数据时先清空列表再添加,确保数据为最新完整集合:
class SavedNews : Fragment() { lateinit var mymainViewModel: MainViewModel lateinit var databaseObject: MyDataBase lateinit var myAdapter: MyListAdapter lateinit var convertedArticleList : ArrayList<Article> override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { myAdapter = MyListAdapter() convertedArticleList = ArrayList() return inflater.inflate(R.layout.fragment_saved_news, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) savedNewsRecyclerView.layoutManager = LinearLayoutManager(activity, LinearLayoutManager.VERTICAL, false) savedNewsRecyclerView.adapter = myAdapter // 初始化ViewModel与Repository,避免重复创建 val myInterfaceObject = ApiInterface.MyObject.getInstance() databaseObject = MyDataBase.MyObjectDB.getDBInstance(activity as MainActivity) val myRepository = Repository(myInterfaceObject, databaseObject) mymainViewModel = ViewModelProvider( this, MainViewModelFactory(myRepository) ).get(MainViewModel::class.java) // 仅注册一次LiveData Observer mymainViewModel.abstractSavedNews().observe(viewLifecycleOwner, Observer { likedArticles -> convertedArticleList.clear() // 清空旧数据,避免重复 likedArticles.forEach { eachLikedArticle -> val obj = toArticle(eachLikedArticle) convertedArticleList.add(obj) } myAdapter.submitList(convertedArticleList.toList()) // 提交列表副本 }) // 滑动删除逻辑 ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.RIGHT) { override fun onMove( recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder ): Boolean { return false } override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { val position = viewHolder.adapterPosition val deletedArticle = convertedArticleList[position] // 直接操作数据库,依赖LiveData自动更新列表 mymainViewModel.deleteSavedNews(deletedArticle.id!!) // 撤销功能:通过插入数据库触发LiveData更新 Snackbar.make(savedNewsRecyclerView, "已删除 ${deletedArticle.title}", Snackbar.LENGTH_LONG) .setAction("撤销") { mymainViewModel.saveNews(toLikedArticle(deletedArticle)) }.show() } }).attachToRecyclerView(savedNewsRecyclerView) // 列表项点击事件 myAdapter.setOnItemClickListener { article -> val bundle = Bundle().apply { putSerializable("article", article) } findNavController().navigate(R.id.action_savedNews_to_article, bundle) } } // Article转LikedArticle,用于撤销操作 private fun toLikedArticle(article: Article) = LikedArticle( article.id, article.author, article.content, article.description, article.publishedAt, article.source, article.title, article.url, article.urlToImage ) private fun toArticle(rawObject: LikedArticle) = Article( rawObject.author, rawObject.content, rawObject.description, rawObject.publishedAt, rawObject.source, rawObject.title, rawObject.url, rawObject.urlToImage, rawObject.id ) }
2. 简化Adapter实现,移除内部数据修改逻辑
ListAdapter设计初衷是通过外部提交新列表驱动更新,删除Adapter内的deleteItem方法:
class MyListAdapter : ListAdapter<Article, MyListAdapter.MyViewHolder>(MyDiffUtil()) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.recycler_item, parent, false) return MyViewHolder(view) } override fun onBindViewHolder(holder: MyViewHolder, position: Int) { val item = getItem(position) holder.onBind(item) holder.itemView.setOnClickListener { onItemClickListener?.invoke(item) } } class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { fun onBind(article: Article) = with(itemView) { Glide.with(this) .load(article.urlToImage) .error(R.drawable.notfound) .into(imageView) webSource.text = article.source?.name newsDate.text = article.publishedAt newsTitle.text = article.title newsDescription.text = article.description } } private var onItemClickListener: ((Article) -> Unit)? = null fun setOnItemClickListener(listener: (Article) -> Unit) { onItemClickListener = listener } class MyDiffUtil : DiffUtil.ItemCallback<Article>() { override fun areItemsTheSame(oldItem: Article, newItem: Article): Boolean { // 用唯一ID判断是否为同一条目 return oldItem.id == newItem.id } override fun areContentsTheSame(oldItem: Article, newItem: Article): Boolean { // 依赖Article类正确实现equals方法 return oldItem == newItem } } }
3. 确保Article类正确实现equals和hashCode方法
DiffUtil的areContentsTheSame依赖equals判断内容一致性,若使用普通类需手动重写,Kotlin数据类会自动生成:
// Kotlin数据类自动生成equals和hashCode data class Article( val author: String?, val content: String?, val description: String?, val publishedAt: String?, val source: Source?, val title: String?, val url: String?, val urlToImage: String?, val id: Int? ) data class Source( val name: String? )
关键说明
- 依赖LiveData自动更新:通过操作数据库触发Room的LiveData通知,Fragment收到新数据后更新Adapter,保证数据源一致性
- 提交列表副本:调用
submitList时传入列表副本,确保每次都是新实例,DiffUtil能正确对比差异并触发动画 - DiffUtil正确配置:
areItemsTheSame用唯一ID判断条目身份,areContentsTheSame依赖正确的equals方法判断内容变化,实现流畅的删除动画
内容的提问来源于stack exchange,提问作者Sijan
相关产品推荐
相关产品推荐

