You need to enable JavaScript to run this app. MVVM架构测验应用中RecyclerView嵌套setOnClickListener的选项选中状态复用异常
优惠活动
大模型
产品
解决方案
定价
更多

MVVM架构测验应用中RecyclerView嵌套setOnClickListener的选项选中状态复用异常

问题根因

这个异常是RecyclerView视图复用机制导致的典型状态残留问题:

  • RecyclerView会回收滑出屏幕的ViewHolder,直接复用给新位置的item,不会自动重置View的原有状态。
  • 你之前仅在用户点击时修改了当前ViewHolder的选项背景,没有将选中状态和题目数据做绑定,也没有在每次onBindViewHolder时重置、重算当前item的选项背景。复用的ViewHolder会带着之前位置的选中状态直接渲染新数据,因此会出现距离当前N个位置(即当前离屏复用池的缓存数量)的item同步显示选中样式的现象。
  • 你存储选中答案的quizViewModel.chosenAnswers是正确的单一数据源,仅需要补全视图绑定的逻辑即可修复,不需要调整整体MVVM架构。
  • 之前用接口回调的方式没有解决问题,本质是因为回调里仅修改了当前操作的View的背景,没有处理ViewHolder复用时的状态重置逻辑,残留的状态依然会跟着View流传到其他位置。
修复方案

核心原则:所有选项的选中样式,必须从数据源计算渲染,不要直接在点击回调里跨View修改背景,避免操作到已被复用的ViewHolder。

具体实现步骤

  1. 调整点击逻辑:只更新数据源,不直接操作View
    把点击回调的逻辑改为仅更新chosenAnswers,触发适配器重新绑定当前item即可,去掉直接修改其他选项背景的代码。

  2. 在onBindViewHolder中完整重置、渲染所有选项的状态
    每次绑定item时,先把所有选项的背景重置为默认样式,再根据当前位置的选中答案,给对应选项设置选中样式,彻底清除复用带来的状态残留。

    完整的绑定逻辑示例:

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {        
        val curQuest = myDataset[position]    
        val shuffledAnswers = curQuest.answers
    
        // 绑定选项文本
        holder.ans1.text = shuffledAnswers[0].answer_text
        holder.ans2.text = shuffledAnswers[1].answer_text
        holder.ans3.text = shuffledAnswers[2].answer_text
    
        // 【关键】先重置所有选项为默认背景,清除复用的残留状态
        val defaultDrawable = ContextCompat.getDrawable(parentFragment.requireContext(), R.drawable.default_ans_quiz)
        holder.ans1.background = defaultDrawable
        holder.ans2.background = defaultDrawable
        holder.ans3.background = defaultDrawable
    
        // 从数据源读取当前题目的选中答案,给对应选项设置选中背景
        val selectedAnswer = quizViewModel.chosenAnswers[position]
        val selectedDrawable = ContextCompat.getDrawable(parentFragment.requireContext(), R.drawable.selected_ans_quiz)
        when (selectedAnswer) {
            shuffledAnswers[0] -> holder.ans1.background = selectedDrawable
            shuffledAnswers[1] -> holder.ans2.background = selectedDrawable
            shuffledAnswers[2] -> holder.ans3.background = selectedDrawable
        }
    
        // 绑定点击事件:仅更新数据源,触发重绑
        holder.ans1.setOnClickListener {
            quizViewModel.chosenAnswers[position] = shuffledAnswers[0]
            notifyItemChanged(position)
        }
        holder.ans2.setOnClickListener {
            quizViewModel.chosenAnswers[position] = shuffledAnswers[1]
            notifyItemChanged(position)
        }
        holder.ans3.setOnClickListener {
            quizViewModel.chosenAnswers[position] = shuffledAnswers[2]
            notifyItemChanged(position)
        }
    }
    

注意事项

  • 不要把其他选项的View实例作为参数传到回调里修改,当回调执行时,对应的ViewHolder可能已经被复用到其他位置,会导致状态错乱。所有视图状态的修改都放在onBindViewHolder中统一处理,基于数据源渲染即可。
  • 如果后续需要扩展选中状态的逻辑(比如正确/错误答案的不同样式),只需要调整onBindViewHolder中的渲染规则,不需要修改点击事件的逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.28 02:24:04