Kotlin中Room增删收藏后RecyclerView重置异常求助
解决方案
问题根源
你的代码核心问题是每次数据更新(包括收藏变化、搜索)时,都会重新创建RecyclerView的Adapter和LayoutManager,这会导致RecyclerView完全重置(滚动位置回到顶部),且重复添加DividerItemDecoration会引发布局异常(元素后空白)。同时,收藏操作后没有局部刷新数据,而是依赖LiveData全量刷新,加剧了问题。
步骤1:重构CoasterFragment,复用RecyclerView配置
将RecyclerView的初始化(LayoutManager、ItemDecoration)移到onCreateView中只执行一次,避免重复设置;同时复用Adapter,只更新数据而非重新创建。
修改后的CoasterFragment代码:
class CoasterFragment : Fragment() { lateinit var coasterListener: CoasterListener private lateinit var searchView: SearchView private lateinit var cAdapter: CoasterRecyclerViewAdapter private var _binding: FragmentCoasterBinding? = null private val binding get() = _binding!! private val viewModel: CoastersViewModel by viewModels() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { _binding = FragmentCoasterBinding.inflate(inflater, container, false) val root: View = binding.root // 1. 只初始化一次RecyclerView的核心配置 initRecyclerView() // 2. 观察全量数据,更新Adapter内容 viewModel.coasters().observe(viewLifecycleOwner) { coasters -> cAdapter.updateData(coasters) } coasterListener = CoasterListenerImpl(requireContext(), viewModel) searchView = binding.search searchView.clearFocus() searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { override fun onQueryTextSubmit(query: String?): Boolean { query?.let { searchDatabase(it) } return true } override fun onQueryTextChange(query: String?): Boolean { query?.let { searchDatabase(it) } return true } }) return root } private fun initRecyclerView() { // 初始化空Adapter cAdapter = CoasterRecyclerViewAdapter(mutableListOf(), coasterListener, requireContext()) binding.recyclerCoaster.apply { layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false) adapter = cAdapter // 只添加一次分割线 addItemDecoration(DividerItemDecoration(context, RecyclerView.VERTICAL)) } } fun searchDatabase(query: String) { val searchQuery = "%$query%" viewModel.searchDatabase(searchQuery).observe(viewLifecycleOwner) { coasters -> cAdapter.updateData(coasters) } } override fun onDestroyView() { super.onDestroyView() _binding = null } }
步骤2:修改Adapter,支持数据更新和局部刷新
给Adapter添加updateData方法,用于替换数据源;同时在收藏状态变化时,直接更新对应位置的数据并局部刷新,避免全量重置。
修改后的CoasterRecyclerViewAdapter代码:
class CoasterRecyclerViewAdapter( // 改为var,允许修改数据源 private var coasters: MutableList<CoasterFavorito>, val listener: CoasterListener, val context: Context ) : RecyclerView.Adapter<CoasterRecyclerViewAdapter.ViewHolder>() { // 添加更新数据的方法 fun updateData(newData: List<CoasterFavorito>) { coasters.clear() coasters.addAll(newData) notifyDataSetChanged() // 如果需要更精确的刷新,可以用DiffUtil,这里先简化 } // 提供修改单个item收藏状态的方法 fun updateFavoriteStatus(position: Int, isFavorite: Boolean) { coasters[position].favorito = isFavorite notifyItemChanged(position) // 局部刷新,不会影响滚动位置 } class ViewHolder private constructor( val binding: CoasterItemBinding, private val listener: CoasterListener, private val adapter: CoasterRecyclerViewAdapter, private val context: Context ) : RecyclerView.ViewHolder(binding.root) { fun relleno(data: CoasterFavorito) { binding.nombre.text = data.coaster.nombre binding.parque.text = data.coaster.parque binding.ciudad.text = data.coaster.ciudad binding.provincia.text = data.coaster.provincia binding.comunidad.text = data.coaster.comunidadAutonoma Glide.with(context) .load(data.coaster.imagen) .centerCrop() .into(binding.imagen) binding.check.isChecked = data.favorito binding.check.setOnClickListener { val newStatus = !data.favorito if (newStatus) { listener.addFavorito(data.coaster.id) } else { listener.delFavorito(data.coaster.id) } // 局部刷新当前item的收藏状态 adapter.updateFavoriteStatus(adapterPosition, newStatus) } } companion object { fun crearViewHolder( parent: ViewGroup, listener: CoasterListener, adapter: CoasterRecyclerViewAdapter, context: Context ): ViewHolder { val layoutInflater = LayoutInflater.from(parent.context) val binding = CoasterItemBinding.inflate(layoutInflater, parent, false) return ViewHolder(binding, listener, adapter, context) } } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder.crearViewHolder(parent, listener, this, context) override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.relleno(coasters[position]) override fun getItemCount() = coasters.size } interface CoasterListener { fun addFavorito(id: Long) fun delFavorito(id: Long) }
步骤3:优化LiveData订阅(可选)
如果搜索频繁触发,建议使用Transformations.switchMap来避免重复创建LiveData订阅,减少资源消耗:
在CoastersViewModel中添加搜索的转换方法:
class CoastersViewModel : ViewModel() { private val searchQuery = MutableLiveData<String>() // 用switchMap处理搜索,自动切换数据源 val searchResults = Transformations.switchMap(searchQuery) { query -> searchDatabase(query) } fun setSearchQuery(query: String) { searchQuery.value = "%$query%" } }
然后在Fragment的searchDatabase方法中改为:
fun searchDatabase(query: String) { viewModel.setSearchQuery(query) }
同时在onCreateView中观察searchResults:
viewModel.searchResults.observe(viewLifecycleOwner) { coasters -> cAdapter.updateData(coasters) }
关键说明
- 复用RecyclerView配置:避免重复设置LayoutManager和ItemDecoration,解决元素后空白问题。
- 局部刷新:收藏状态变化时调用
notifyItemChanged,而非全量刷新,保留滚动位置。 - 复用Adapter:只更新数据源而非重新创建Adapter,避免RecyclerView重置。
内容的提问来源于stack exchange,提问作者Celitas
相关产品推荐
相关产品推荐

