MaterialButtonToggleGroup点击时addOnButtonCheckedListener触发两次且按钮状态异常
问题原因与解决方案
1. 两次回调的本质原因
点击非当前选中按钮(如btnTest2)的情况
这是设计预期的正常行为:
当singleSelection="true"时,ToggleGroup处于单选模式,切换选中项时会触发两次回调:
- 第一次:取消原选中按钮的选中状态(
isChecked=false,checkedId为原按钮ID) - 第二次:选中新按钮的状态(
isChecked=true,checkedId为新按钮ID)
目的是完整告知选中状态的切换过程。
点击当前已选中按钮(如btnTest1)的情况
这是selectionRequired="true"与singleSelection="true"组合导致的逻辑冲突:
当你点击已选中的按钮时,ToggleGroup会先尝试取消它的选中状态(触发isChecked=false的回调),但由于selectionRequired="true"强制要求必须有一个选中项,内部会立即将该按钮重新设为选中状态,从而触发第二次回调(isChecked=true)。你的日志显示回调顺序异常(先true后false),大概率是Material Components库的版本bug,建议升级到最新稳定版。
2. 解决方案
根据业务需求选择对应的处理方式:
方式一:保留单选且允许取消选中
移除布局中的app:selectionRequired="true"属性,这样点击已选中按钮时只会触发一次取消选中的回调,状态也会变为未选中。
方式二:强制保持选中状态(忽略无效取消回调)
如果必须要求始终有一个选中项,只需在监听器中处理isChecked=true的事件即可,忽略isChecked=false的回调(因为此时的取消会被内部回滚,最终状态还是选中):
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.btgTest.addOnButtonCheckedListener { group, checkedId, isChecked -> if (isChecked) { when (checkedId){ R.id.btnTest1 -> Timber.d("1 is checked") R.id.btnTest2 -> Timber.d("2 is checked") } } // 忽略isChecked=false的回调,避免无效处理 } }
方式三:精准处理切换过程(区分有效取消与临时取消)
如果需要记录切换前后的选中状态,可以通过记录上次选中ID来过滤无效的临时取消:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) var lastCheckedId = binding.btgTest.checkedButtonId binding.btgTest.addOnButtonCheckedListener { group, checkedId, isChecked -> if (isChecked) { Timber.d("选中按钮: ${if (checkedId == R.id.btnTest1) "1" else "2"}") lastCheckedId = checkedId } else { // 只有当取消的按钮不是上次选中的按钮时,才是有效切换(切换到其他按钮) if (checkedId != lastCheckedId) { Timber.d("取消按钮: ${if (checkedId == R.id.btnTest1) "1" else "2"}") } // 否则是点击当前选中按钮导致的临时取消,会被回滚,无需处理 } } }
方式四:升级Material Components库
在build.gradle中升级到最新稳定版的Material库,修复可能存在的状态回调顺序bug:
dependencies { implementation 'com.google.android.material:material:1.11.0' // 替换为最新版本 }
内容的提问来源于stack exchange,提问作者imin
相关产品推荐
相关产品推荐

