Android Pitfall - 扒一扒RadioGroup 和 RadioButton

今天工作时,需要以编程方式对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);
}





你可能感兴趣的:(android,RadioGroup,RadioButton)