在做一个购物车页面的时候,经常遇到这种情况,购物车中的商品按店铺分开,每个店铺和商品都有一个选择框,还有一个全部选择的选择框。其中全部选择选择框的下级(子级)为所有店铺选择框,店铺选择框的下级为店铺下的商品选择框。这就是一个典型的多级(级联)选择框案例。必须满足以下所有条件才能使用本文的思路。
- 每个级别只能有一个上级或者没有上级。
- 每个级别有任意个下级。
- 当前级别的选择框的选择状态由所有下级的选择状态(true或者false)做
And
运算。 - 当前级别的选择框的选择状态由上级的选择状态决定,即上级为true时为true,上级为false时为false。
首先分析一下选择框改变状态的原因都包括哪些:
- 点击
- 初始化
这个原因比较特殊,只有在listview或者gridview中包含checkbox时才会用到,一般这种情况下,checkbox的状态都由数据(即模型里的checked属性)决定,在getView
函数的时候一般会掉用checkbox的setChecked
方法,这个时候的原因就是初始化
。 - 下级改变了状态
- 上级改变了状态
下级改变状态后需要检测所有的下级的选择状态,并做And
运算。
每种原因所需要进行的操作都不一样,以下为详细操作:
原因 | 操作 |
---|---|
点击 | 通知上级检测它的状态,通知所有下级改变状态 |
初始化 | 不需要做任何操作 |
下级改变了状态 | 下级改变状态后需要检测所有的下级的选择状态,并做And 运算。 |
上级改变了状态 | 直接将自己的状态改为父级的状态 |
需要注意的是对上下级操作时,需要传递原因
下面是CheckBox控件的详细代码
/**
* Created by zongren on 16/5/19.
*/
public class TreeCheckbox extends AppCompatCheckBox {
/* 为什么要改变自己的checked状态 */
public enum WHY_CHANGE_STATUS {
SELF_CLICKED,//onCheckedChanged事件来自于自己(即被点击,默认是这个原因)
SELF_INIT,//onCheckedChanged事件来自于初始化(即代码调用setChecked函数)
CHILD_CHANGED,//onCheckedChanged事件来自于子级改变了状态,导致需要检测自己的状态
PARENT_CHANGED//onCheckedChanged事件来自于父级改变了状态,导致自己跟随改变(即跟父级保持一致)
}
private WHY_CHANGE_STATUS mWhyChangeStatus = WHY_CHANGE_STATUS.SELF_CLICKED;
private int mParentCheckboxId;
private TreeCheckbox mParentCheckbox;
private List<TreeCheckbox> mChildrenCheckbox;
public TreeCheckbox(Context context) {
this(context, null);
}
public TreeCheckbox(Context context, AttributeSet attrs) {
this(context, attrs, android.support.v7.appcompat.R.attr.checkboxStyle);
}
public TreeCheckbox(Context context, AttributeSet attrs, int defStyleAttr) {
super(TintContextWrapper.wrap(context), attrs, defStyleAttr);
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.TreeCheckbox);
mParentCheckboxId = array.getInt(R.styleable.TreeCheckbox_parentCheckbox,0);
array.recycle();
mChildrenCheckbox = new ArrayList<>();
setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
switch (mWhyChangeStatus){
case SELF_CLICKED:
//自己被点击需要改变所有直接子级的状态,同时需要父级检测自己的状态
if(mParentCheckbox != null){
if(!Utils.isNull(mParentCheckbox.getChildrenCheckbox())){
boolean isChildrenAllChecked = true;
for (TreeCheckbox checkBox : mParentCheckbox.getChildrenCheckbox()) {
isChildrenAllChecked = isChildrenAllChecked && checkBox.isChecked();
}
//父级改变状态的原因是子级改变了状态,所以是CHILD_CHANGED
mParentCheckbox.setChecked(isChildrenAllChecked,WHY_CHANGE_STATUS.CHILD_CHANGED);
}
}
if (!Utils.isNull(mChildrenCheckbox)) {
for (TreeCheckbox checkBox : mChildrenCheckbox) {
//所以子级更改状态的原因都是PARENT_CHANGED
checkBox.setChecked(isChecked, WHY_CHANGE_STATUS.PARENT_CHANGED);
}
}
break;
case SELF_INIT:
//通过代码初始化状态,不需要做任何处理
break;
case CHILD_CHANGED:
//因为子级改变了状态所以要继续向父级传递这个事件
if(mParentCheckbox != null){
if(!Utils.isNull(mParentCheckbox.getChildrenCheckbox())){
boolean isChildrenAllChecked = true;
for (TreeCheckbox checkBox : mParentCheckbox.getChildrenCheckbox()) {
isChildrenAllChecked = isChildrenAllChecked && checkBox.isChecked();
}
//父级改变状态的原因是子级改变了状态,所以是CHILD_CHANGED
mParentCheckbox.setChecked(isChildrenAllChecked,WHY_CHANGE_STATUS.CHILD_CHANGED);
}
}
break;
case PARENT_CHANGED:
//因为父级改变了状态所以要继续向子级传递这个事件
if (!Utils.isNull(mChildrenCheckbox)) {
for (TreeCheckbox checkBox : mChildrenCheckbox) {
//所以子级更改状态的原因都是PARENT_CHANGED
checkBox.setChecked(isChecked, WHY_CHANGE_STATUS.PARENT_CHANGED);
}
}
break;
}
//无论什么原因导致自己的状态改变,最后都设置为默认原因,也就是SELF_CLICKED
//mWhyChangeStatus = WHY_CHANGE_STATUS.SELF_CLICKED;
}
});
}
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
View parentCheckbox = getRootView().findViewById(mParentCheckboxId);
if (parentCheckbox instanceof TreeCheckbox) {
((TreeCheckbox) parentCheckbox).addChildCheckbox(this);
setParentCheckbox((TreeCheckbox) parentCheckbox);
}
}
public void setChecked(boolean isChecked, WHY_CHANGE_STATUS reason) {
mWhyChangeStatus = reason;
setChecked(isChecked);
//所以重置mWhyChangeStatus的操作放在这里,因为有可能没有监听器
mWhyChangeStatus = WHY_CHANGE_STATUS.SELF_CLICKED;
}
public TreeCheckbox getParentCheckbox() {
return mParentCheckbox;
}
public void setParentCheckbox(TreeCheckbox parentCheckbox) {
mParentCheckbox = parentCheckbox;
}
public List<TreeCheckbox> getChildrenCheckbox() {
return mChildrenCheckbox;
}
public void setChildrenCheckbox(List<TreeCheckbox> childrenCheckbox) {
mChildrenCheckbox = childrenCheckbox;
}
public void addChildCheckbox(TreeCheckbox childCheckbox) {
if (Utils.isNull(mChildrenCheckbox)) {
mChildrenCheckbox = new ArrayList<>();
}
mChildrenCheckbox.add(childCheckbox);
}
public WHY_CHANGE_STATUS getWhyChangeStatus() {
return mWhyChangeStatus;
}
public void setWhyChangeStatus(WHY_CHANGE_STATUS whyChangeStatus) {
mWhyChangeStatus = whyChangeStatus;
}
}
对于在ListView使用CheckBox的这种情况就需要做针对性的处理了,因为这个时候mParentCheckBox和mChildrenCheckBox都是不存在的。