今天工作时,需要以编程方式对RadioGroup进行操作(包括清空、选中),结果却遇到了一个非常意外的结果—— radioButton.setChecked(true); 后,该按钮竟不会变为选中状态!在Android源码中一阵翻腾后(开源万岁啊),于是便有了这篇博文。
首先,RadioButton 继承自 CompoundButton,大部分的功能都是由CompoundButton完成的。其中,也包括我们熟悉的 setChecked(booleanchecked)
public void setChecked(boolean checked) { if (mChecked != checked) { 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); } mBroadcasting = false; } }
点击按钮或调用setChecked()方法后,CompoundButton更新选中状态后,会依次调用
mOnCheckedChangeListener.onCheckedChanged() 和mOnCheckedChangeWidgetListener.onCheckedChanged(),前者是用户程序调用 button.setOnclickListener() 方法所设置的回调函数,而后者,则得从 RadioGroup 说起。
RadioGroup 构造器中,会调用如下这么一个函数:private void init() { mChildOnCheckedChangeListener = new CheckedStateTracker(); mPassThroughListener = new PassThroughHierarchyChangeListener(); super.setOnHierarchyChangeListener(mPassThroughListener); }在这里,RadioGroup 设置了 OnHierarchyChangeListener 这么一个Callback,而在这个Callback ——PassThroughHierarchyChangeListener 中,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); } }对于每个 instanceof RadioButton 的 child view,都会调用 setOnCheckedChangeWidgetListener(),这就是我们调用radioButton.setChecked()的第二个回调了,也是RadioGroup能够保持只有一个按钮选中的秘密所在。RadioGroup 的回调函数就长这样:public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { // prevents from infinite recursion if (mProtectFromCheckedChange) { return; } mProtectFromCheckedChange = true; if (mCheckedId != -1) { setCheckedStateForView(mCheckedId, false); } mProtectFromCheckedChange = false; int id = buttonView.getId(); setCheckedId(id); }首先,将先前选中的按钮设置为“未选择”(setCheckedStateForView(mCheckedId, false)),然后更新内部状态(setCheckedId(id))。在我的代码中,有这么一段:for (RadioButton button : radioButtons) { button.setChecked(false); }把RadioGroup中所有的按钮都设置为false(记这个操作为“清空”),确实,如我所愿,所有按钮均显示“未选中”。但是,但我再次点击“清空”操作前那个被选中的按钮时,任凭我怎么点击,他始终都无动于衷。至于原因,上面已经说明了:在setChecked()里,由于未选中的按钮状态没有改变,所有,setChecked() 什么都不做,而对于那个已经选中的按钮,他的状态将变为未选中,而后更新drawable (虽然我们看不到变化,但他确实调用了refreshDrawableState()更新其状态 )。至于问题,就出在后面的 mOnCheckedChangeWedgitListener.onCheckedChanged(this, mChecked)。
RadioGroup中,他会将先前已选中的按钮变为“未选中”,而后更新内部的mChekedId ,于是,便把前面 refreshDrawableState() 所更新的drawable状态又变回了 unchecked,也就是我们所看到的,“无法再次选中”。
解决方案:直接调用 radioGroup.check(-1) 或 clearCheck();
/** * <p>Sets the selection to the radio button whose identifier is passed in * parameter. Using -1 as the selection identifier clears the selection; * such an operation is equivalent to invoking {@link #clearCheck()}.</p> * * @param id the unique id of the radio button to select in this group * * @see #getCheckedRadioButtonId() * @see #clearCheck() */ public void check(int id) { // don't even bother if (id != -1 && (id == mCheckedId)) { return; } if (mCheckedId != -1) { setCheckedStateForView(mCheckedId, false); } if (id != -1) { setCheckedStateForView(id, true); } setCheckedId(id); }
这里的第二个if语句会将之前选择的按钮设置为“未选中”。至此,问题圆满解决。
PS:点击后切换StateListDrawable是在 performClick() 里实现的
@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; }
public void toggle() { setChecked(!mChecked); }