RadioGroup与RadioButton配合实现一组数据的单选问题。
插播一条信息,在设置RadioButton的textColor的选中效果时,不能在drawable中创建想xml,得在res/color文件中创建xml,然后引用。
radioButton.setTextColor(getResources().getColorStateList(R.color.xxx));
这个过程中,需要注意几点。
- RadioButton 设置前面小圆点消失 radioButton.setButtonDrawable(null)
- RadioGroup 下面的RadioButton不能用其他控件包裹,否则就会是一个一个单独的RadioButton。
- 在动态创建RadioButton的时候,需要设置margin时,需要使用RadioGroup.LayoutParams 来创建布局参数,不然设置margin不起作用。
- 如何实现在RadioGroup与RadioButton配合时,现在点击两次RadioButton,取消选中
对以上问题,具体我们分析下:
-
为什么RadioGroup的直接子控件必须是RadioButt呢?
先来看下RadioGroup的init()方法private void init() { // tracks children radio buttons checked state 追踪RadioButton的选中状态 mChildOnCheckedChangeListener = new CheckedStateTracker(); // 监听层级变化 ViewGroup的子View移除和添加都会触发相对应的方法。 mPassThroughListener = new PassThroughHierarchyChangeListener(); super.setOnHierarchyChangeListener(mPassThroughListener); } /** * {@inheritDoc} 给用户提供一个方法,可以自己实现层级的监听 */ @Override public void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) { // the user listener is delegated to our pass-through listener mPassThroughListener.mOnHierarchyChangeListener = listener; }
再看下,PassThroughHierarchyChangeListener 实现的添加和移除的方法
private class PassThroughHierarchyChangeListener implements ViewGroup.OnHierarchyChangeListener { private ViewGroup.OnHierarchyChangeListener mOnHierarchyChangeListener; /** * {@inheritDoc} */ public void onChildViewAdded(View parent, View child) { // 关键点一: 如果child是RadioButton,才会执行 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选择状态的监听,设置到radiobutoon上,以实现监听 ((RadioButton) child).setOnCheckedChangeWidgetListener( mChildOnCheckedChangeListener); } // 如果用户自己定义,会继续走用户自己定义的方法 if (mOnHierarchyChangeListener != null) { mOnHierarchyChangeListener.onChildViewAdded(parent, child); } } 。。。。 }
接着,我们来看下RadioGroup的addView方法
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
if (child instanceof RadioButton) {
final RadioButton button = (RadioButton) child;
if (button.isChecked()) {
mProtectFromCheckedChange = true;
if (mCheckedId != -1) {
setCheckedStateForView(mCheckedId, false);
}
mProtectFromCheckedChange = false;
setCheckedId(button.getId());
}
}
// 父类ViewGroup的addView方法
super.addView(child, index, params);
}
ViewGroup.java
从源码里可以 addView()方法里面调用了 addViewInner()方法,在里面调用了disparchViewAdd(child)方法。
addView() —> addViewInner() —>dispatchViewAdd()
void dispatchViewAdded(View child) {
onViewAdded(child);
// 在RadioGroup init方法中,设置了该listener。在这里回调到onChildViewAdded()方法。再为radiobutton设置了OnCheckedChangeWidgetListener
if (mOnHierarchyChangeListener != null) {
mOnHierarchyChangeListener.onChildViewAdded(this, child);
}
}
理清楚他们之间的关联,现在该是点击的时候了。
之前介绍过onclick方法,之所以能执行,是整个事件分发完之后,执行了performClick()方法,然后执行clicklistener的onclick回调方法。
RadioButton继承了CompoundButton。 里面的performClick方法
CompoundButton.java
@Override
public boolean performClick() {
// 先执行radioButton的toggle方法
toggle();
// 执行 onclick方法。
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);
}
RadioButton.java
/**
* {@inheritDoc}
* <p>
* 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)
// 关键点四:如果该RadioButton已经选中了,就不执行了,这也是RadioButton一旦被选择了,就不能取消选中的根本原因。
if (!isChecked()) {
super.toggle();
}
}
那RadioButton怎么和RadioGroup联系到一起的呢?
RadioButton的父类CompoundButton的toggle
public void toggle() {
setChecked(!mChecked);
}
/**
* <p>Changes the checked state of this button.</p>
* 真正改变RadioButton状态的方法。
* @param checked true to check the button, false to uncheck it
*/
public void setChecked(boolean checked) {
if (mChecked != checked) {
mChecked = checked;
// 刷新选中态的UI
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);
}
// 由前面关键点二可知,在RadioGroup中设置这个listener,所以回调到RadioGroup中
if (mOnCheckedChangeWidgetListener != null) {
mOnCheckedChangeWidgetListener.onCheckedChanged(this, mChecked);
}
mBroadcasting = false;
}
}
再来看下CheckedChangeWidgetListener的具体实现 CheckedStateTracker
private class CheckedStateTracker implements CompoundButton.OnCheckedChangeListener {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
// prevents from infinite recursion
if (mProtectFromCheckedChange) {
return;
}
mProtectFromCheckedChange = true;
//关键点三:将其他的RadioButton置为未选中 mCheckedId是之前选择的View Id,
if (mCheckedId != -1) {
setCheckedStateForView(mCheckedId, false);
}
mProtectFromCheckedChange = false;
int id = buttonView.getId();
// 更新 mCheckedId
setCheckedId(id);
}
}
private void setCheckedStateForView(int viewId, boolean checked) {
// 找到之前选中的RadioButton。设置它的选中状态。
View checkedView = findViewById(viewId);
if (checkedView != null && checkedView instanceof RadioButton) {
((RadioButton) checkedView).setChecked(checked);
}
}
以上分析了整个RadioGroup为什么子View必须是RadioButton 才能起到一组单选的效果,以及整个流程。
2、怎么实现RadioButton点击两次,取消选中呢?
由关键点四,可以知道如果RadioButton选中了,就不会执行setChecked()方法也就无法通知RadioGroup。
但是他扔会走touch方法、click方法。
我写的思路就是,在RadioButton的点击事件里面,人为的设置radiobutton的选中状态。
但是RadioButton的click事件是发生在RadioButton自己的setChecked()之后,所以
radioButton.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v){
// 不管该控件的状态之前是选中态还是未选中态,这个时候,都已经是选中状态了
// 所以这个时候,radioButton的isChecked()得到的都是true
// 所以关键是找到之前的选中的是哪个控件。
if (checkedId == v.getId()){
// 说明是第二次点击该控件,所以要做清除选中逻辑
// 清除选中,其实纵观整个RadioGroup,只有当前的RadioButton被选中了,而此时恰恰需要清除选中,则只需要把RadioGroup全部清空状态即可。
radioGroup.clearCheck();
}
}
})
根据View的整个事件分发机制,click事件,是最后触发的。
所以我们可以在touch事件中获得到之前选中的状态。
又根据RadioGroup提供了一个方法getCheckedRadioButtonId(),返回选中态RadioButton的id
int checkedId = -1;
radioButton.setOnTouchListener(new OnTouchListener(){
@Override
public boolean onTouch(View v, MotionEvent event){
// 这时候,新的button状态还没有设置,所以返回的是之前选中的button
checkedId = radioGroup.getCheckedRadioButtonId();
// 不消耗掉该事件,接着往下传递给click事件。
return false;
}
})
这样就是整个过程了。