You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)
}

关键说明

  1. 复用RecyclerView配置:避免重复设置LayoutManager和ItemDecoration,解决元素后空白问题。
  2. 局部刷新:收藏状态变化时调用notifyItemChanged,而非全量刷新,保留滚动位置。
  3. 复用Adapter:只更新数据源而非重新创建Adapter,避免RecyclerView重置。

内容的提问来源于stack exchange,提问作者Celitas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 21:20:15