背景
相信大家对RadioGroup+RadioButton组合的单选框布局再熟悉不过了,我自己也是这么觉得的。于是今天非常自信的写了如下一段代码:
然后直接运行,跑起来以后发现单选效果怎么都出不来,我可以同时选中这两个Button。
我一下子就懵逼了,怎么回事?我用的这么熟练的东西,怎么现在没法实现了。
然后我找了以前的代码比较了一下,发现现在写的这段代码RadioButton没有加上ID。难道就因为没有加ID导致的,于是给RadioButton加上了ID,跑了一下,发现还真的是这样,心里说了句FxxK。
为了搞清楚原因,于是乎我就开始了一次代码的深入研究。
1、首先点击事件是RadioButton检测到的,所以我先去看了下RadioButton的点击事件:
public class RadioButton extends CompoundButton {
public RadioButton(Context context) {
this(context, null);
}
public RadioButton(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.radioButtonStyle);
}
public RadioButton(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public RadioButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
/**
* {@inheritDoc}
*
* If the radio button is already checked, this method will not toggle the radio button.
*/
@Override
public void toggle() {
// we override to prevent toggle when the radio is already
// checked (as opposed to check boxes widgets)
if (!isChecked()) {
super.toggle();
}
}
@Override
public CharSequence getAccessibilityClassName() {
return RadioButton.class.getName();
}
}
并没有发现什么点击事件处理,于是去找他的父类CompoundButton:
@Override
public boolean performClick() {
toggle();
final boolean handled = super.performClick();
if (!handled) {
// View only makes a sound effect if the onClickListener was
// called, so we'll need to make one here instead.
playSoundEffect(SoundEffectConstants.CLICK);
}
return handled;
}
在点击时会触发performClick事件,至于为什么会触发这里暂时不做分析。出发performClick事件以后,会执行toggle方法,那再来看下toggle里面的处理
@Override
public void toggle() {
setChecked(!mChecked);
}
/**
* Changes the checked state of this button.
*
* @param checked true to check the button, false to uncheck it
*/
@Override
public void setChecked(boolean checked) {
if (mChecked != checked) {
mCheckedFromResource = false;
mChecked = checked;
refreshDrawableState();
notifyViewAccessibilityStateChangedIfNeeded(
AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
// Avoid infinite recursions if setChecked() is called from a listener
if (mBroadcasting) {
return;
}
mBroadcasting = true;
if (mOnCheckedChangeListener != null) {
mOnCheckedChangeListener.onCheckedChanged(this, mChecked);
}
if (mOnCheckedChangeWidgetListener != null) {
mOnCheckedChangeWidgetListener.onCheckedChanged(this, mChecked);
}
final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
if (afm != null) {
afm.notifyValueChanged(this);
}
mBroadcasting = false;
}
}
OnCheckedChangeListener监听器在RadioGroup如下代码里面进行注册
/**
* {@inheritDoc}
*/
@Override
public void onChildViewAdded(View parent, View child) {
if (parent == RadioGroup.this && child instanceof RadioButton) {
int id = child.getId();
// generates an id if it's missing
if (id == View.NO_ID) {
id = View.generateViewId();
child.setId(id);
}
((RadioButton) child).setOnCheckedChangeWidgetListener(
mChildOnCheckedChangeListener);
}
if (mOnHierarchyChangeListener != null) {
mOnHierarchyChangeListener.onChildViewAdded(parent, child);
}
}
mChildOnCheckedChangeListener这个对象呢是实现了CompoundButton.OnCheckedChangeListener接口,重写了onCheckedChanged方法,接下来要执行的方法就是这里面的onCheckChanged方法:
private class CheckedStateTracker implements CompoundButton.OnCheckedChangeListener {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
...代码省略....
//当前checkedID不为-1的时候,将当前选中的按钮置为未选中状态
if (mCheckedId != -1) {
setCheckedStateForView(mCheckedId, false);
}
...代码省略...
int id = buttonView.getId();
setCheckedId(id);
}
}
最主要的就是这几行了,之所以能实现单选的原因是调用setCheckedStateForView将选中的RadioButton设置为未选中状态,然后先获取RadioButton的id,调用setCheckedId设置当前按下的RadioButton设置为选中状态。显而易见我之前所说的单选框效果实现原因就应该是mCheckedId == -1了。
那么为什么mChecked会等于-1呢,什么时候才会有值呢?
CTRL+F整个类搜索了一下「mChecked =」发现只有两处有赋值情况
/**
* {@inheritDoc}
*/
public RadioGroup(Context context, AttributeSet attrs) {
super(context, attrs);
...代码省略...
int value = attributes.getResourceId(R.styleable.RadioGroup_checkedButton, View.NO_ID);
if (value != View.NO_ID) {
mCheckedId = value;
mInitialCheckedId = value;
}
...代码省略...
}
此处是布局初始化,给他设置属性的时候赋值的。
这里我直接讨论第二处:
private void setCheckedId(@IdRes int id) {
boolean changed = id != mCheckedId;
mCheckedId = id;
if (mOnCheckedChangeListener != null) {
mOnCheckedChangeListener.onCheckedChanged(this, mCheckedId);
}
if (changed) {
final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
if (afm != null) {
afm.notifyValueChanged(this);
}
}
}
这里面的ID就是RadioButton的ID。所以如果没有设置ID的,单选框将会失效
虽然这次碰到问题比较简单,但是也从侧面说明了源码的了解程度还是比较薄弱的。以后得多看源码,才能更好的解决问题。