Kotlin RecyclerView点击选项后无法即时更新背景的问题
问题描述
我是Kotlin新手,若发现代码存在问题或不良实践,欢迎指出。当前我开发了一款答题类小应用,用户需从多个选项中选择正确答案:选中正确答案时,选项需高亮绿色250毫秒后进入下一题;选中错误答案则直接标记错误。切换下一题的逻辑在主Activity中实现,背景更改逻辑在Adapter类中编写,相关Adapter代码如下:
class QuestionOptionAdapter( private val items: ArrayList<String>, private val correctAnswer: String, ) : RecyclerView.Adapter<QuestionOptionAdapter.ViewHolder>() { var onSelectedAnswer: (String) -> Unit = {} var onSelectedCorrectAnswer: () -> Unit = {} var onSelectedIncorrectAnswer: () -> Unit = {} override fun onBindViewHolder(holder: ViewHolder, position: Int) { val item = items[position] holder.tvOption.text = item holder.tvOption.setOnClickListener { if (item == correctAnswer) { runBlocking { it.background = ContextCompat.getDrawable(it.context, R.drawable.question_opt_correct) delay(250) } onSelectedCorrectAnswer() } else { it.background = ContextCompat.getDrawable(it.context, R.drawable.question_opt_incorrect) onSelectedIncorrectAnswer() } } } }
我发现,虽然更改背景的代码在onSelectedCorrectAnswer()之前执行,但背景要等到整个代码块执行完毕才会更新,导致用户看不到高亮效果。请问如何让背景更新在代码块结束前显示?
问题原因
你使用runBlocking执行延迟操作时,会直接阻塞主线程。Android的UI更新任务是在主线程队列中排队执行的,设置背景后立刻用runBlocking卡住主线程,UI刷新任务会被积压,直到delay(250)执行完毕、主线程恢复后才能显示,所以用户看不到高亮效果。
解决方案
不要用runBlocking,改用非阻塞的方式实现延迟,让主线程能立刻处理UI更新:
方案1:使用协程(推荐)
借助AndroidX的lifecycleScope(绑定生命周期,避免内存泄漏)启动协程,延迟操作不会阻塞主线程:
// 确保build.gradle中添加依赖 // implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.6.2" class QuestionOptionAdapter( private val items: List<String>, // 改用不可变List更规范 private val correctAnswer: String, private val lifecycleScope: LifecycleScope // 从Activity/Fragment传入绑定生命周期的Scope ) : RecyclerView.Adapter<QuestionOptionAdapter.ViewHolder>() { var onSelectedCorrectAnswer: () -> Unit = {} var onSelectedIncorrectAnswer: () -> Unit = {} override fun onBindViewHolder(holder: ViewHolder, position: Int) { val item = items[position] holder.tvOption.text = item holder.tvOption.setOnClickListener { view -> // 立刻更新背景 view.background = ContextCompat.getDrawable(view.context, R.drawable.question_opt_correct) // 禁用点击防止重复触发 view.isClickable = false lifecycleScope.launch { delay(250) onSelectedCorrectAnswer() } } else { view.background = ContextCompat.getDrawable(view.context, R.drawable.question_opt_incorrect) view.isClickable = false onSelectedIncorrectAnswer() } } // ViewHolder实现省略... }
在Activity/Fragment中初始化Adapter时传入lifecycleScope:
val adapter = QuestionOptionAdapter(items, correctAnswer, lifecycleScope)
方案2:使用Handler.postDelayed
如果不想引入协程依赖,也可以用传统的Handler实现延迟:
override fun onBindViewHolder(holder: ViewHolder, position: Int) { val item = items[position] holder.tvOption.text = item holder.tvOption.setOnClickListener { view -> if (item == correctAnswer) { view.background = ContextCompat.getDrawable(view.context, R.drawable.question_opt_correct) view.isClickable = false Handler(Looper.getMainLooper()).postDelayed({ onSelectedCorrectAnswer() }, 250) } else { view.background = ContextCompat.getDrawable(view.context, R.drawable.question_opt_incorrect) view.isClickable = false onSelectedIncorrectAnswer() } } }
额外优化建议
- 切换题目时,记得恢复所有选项的点击状态和默认背景,避免下一题选项异常。
- 提前初始化Drawable资源(比如在Adapter构造方法中加载),避免每次点击都重复调用
ContextCompat.getDrawable,提升性能。 - 用
List<String>替代ArrayList<String>作为参数,符合不可变数据的设计原则。
内容的提问来源于stack exchange,提问作者Salaah Amin
相关产品推荐
相关产品推荐

