做一个单选表格
和一般的单选框不一样,这次要做一个单选的表格,有两排,这样:
因为RadioGroup是LinearLayout,所以这种两排只能选一个,需要两个RadioGroup,左边是一个RadioGroup包含几个RadioButton,右边是另一个RadioGroup。
为了两个RadioGroup每次只选中一个,于是做如下处理:
mRadioGroupLeft.setOnCheckedChangeListener((group, checkedId) -> {
if (checkedId != View.NO_ID) {
mRadioGroupRight.clearCheck();
}
});
mRadioGroupRight.setOnCheckedChangeListener((group, checkedId) -> {
if (checkedId != View.NO_ID) {
mRadioGroupLeft.clearCheck();
}
});
就是当一边的RadioGroup被点击check的时候,清除另一个的check状态。
但这样发现有一个问题:
1,先点了左边RadioButton,一切正常,如上图;
2,再点击右边任意一个RadioButton,结果,左边的RadioButton被清除了check状态,但右边被点击的RadioButton却没有被选中。就是两边的check都清空了。如下图:
3,这时,再次点击两边的任意RadioButton都又会正常check。
经过深入分析,如下办法可以达到我希望的效果:
mRadioGroupLeft.setOnCheckedChangeListener((group, checkedId) -> {
if (checkedId != View.NO_ID && !mIsClearing) {
mIsClearing = true;
mReason = ((RadioButton) group.findViewById(checkedId)).getText().toString();
mRadioGroupRight.clearCheck();
mIsClearing = false;
}
});
mRadioGroupRight.setOnCheckedChangeListener((group, checkedId) -> {
if (checkedId != View.NO_ID && !mIsClearing) {
mIsClearing = true;
mReason = ((RadioButton) findViewById(checkedId)).getText().toString();
mRadioGroupLeft.clearCheck();
mIsClearing = false;
}
});
就是自己设置一个标志,防止一次点击清除两边的check状态。
发生问题的大概流程:
1,右边被点击,为了清除掉左边的check状态会这样调用 mRadioGroupLeft.clearCheck();
2,这样会发生mRadioGroupLeft的OnCheckedChangeListener被触发,这样就会又调用mRadioGroupRight.clearCheck();
3,所以,相当于两边的check状态都被清空了;
再深入分析,为啥调用mRadioGroupLeft的clearCheck,导致OnCheckedChangeListener回调会被触发。
先把分析结果放出来:
RadioGroup.clearCheck -> RadioGroup.check -> RadioGroup.setCheckedStateForView -> RadioButton.setChecked -> RadioGroup.CheckedStateTracker.onCheckedChanged -> RadioGroup.setCheckedId -> RadioGroup.mOnCheckedChangeListener.onCheckedChanged 回调外部listener
这里进入的是mRadioGroupLeft的方法:
public void clearCheck() {
check(-1);
}
public void check(@IdRes int id) {
// don't even bother
if (id != -1 && (id == mCheckedId)) {
return;
}
if (mCheckedId != -1) {// <= 这里肯定能进来,因为左边已经有button被选中,关键就是这里
setCheckedStateForView(mCheckedId, false);
}
if (id != -1) {
setCheckedStateForView(id, true);
}
setCheckedId(id);
}
// 再看setCheckedStateForView方法:
private void setCheckedStateForView(int viewId, boolean checked) {
View checkedView = findViewById(viewId);
if (checkedView != null && checkedView instanceof RadioButton) {
((RadioButton) checkedView).setChecked(checked);
}
}
进入RadioButton的setChecked方法:
public void setChecked(boolean checked) {
if (mChecked != checked) {
......//省略一堆代码
mChecked = checked;
if (mOnCheckedChangeListener != null) {
mOnCheckedChangeListener.onCheckedChanged(this, mChecked);
}
if (mOnCheckedChangeWidgetListener != null) {// 这个callback是关键
mOnCheckedChangeWidgetListener.onCheckedChanged(this, mChecked);
}
......
}
}
可以看到,setChecked会回调onCheckedChanged并把当前的check状态带上。
这个回调是RadioGroup对每个RadioButton注册的回调,所以回到RadioGroup:
private class CheckedStateTracker implements CompoundButton.OnCheckedChangeListener {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
......//省略一堆代码
int id = buttonView.getId();
setCheckedId(id);
}
}
private void setCheckedId(@IdRes int id) {
mCheckedId = id;
if (mOnCheckedChangeListener != null) {//这个就会回到我们注册的callback
mOnCheckedChangeListener.onCheckedChanged(this, mCheckedId);
}
.......
}
所以因为RadioGroup内部的listener收到按钮状态改变isChecked=false,然后就回调到外层我们自己注册的listener了。
这里可以看出一个问题,就是RadioGroup的按钮状态isChecked不管变成true还是false,都会触发回调。
但给外层的回调又没有带上isChecked的参数,导致外层没法直接通过回调参数判断过来的状态。如果要判断,还得调用getCheckedRadioButtonId()去对比一下,这也是一个解决方案。