WizardKnight WizardKnight - 3 months ago 18
Android Question

CheckBox states in ExpandableListView's children

I've read about 30 pages from SO as well as tutorials about tracking check states in lists but info (especially working info) is scarce on doing so in an Expandable ListView.

I've got the children populating with checkboxes but when I check a box on a child from 1 group, random children in other groups also check. I need to stop this. The best info I think I read was to set the checkbox as a separate tag but I don't know how to set multiple tags, when I tried, I got a class mismatch error comparing the checkbox to the convertview.

Has anybody come across a good way to keep track of checkbox states in an expandablelistview's children?

I've made multiple changes so I'm putting in the entire adapter code. Please check first few lines of getGroupView and entire getChildView and help me see what I'm doing wrong.

EDIT:
What's happening now is that when I check a box and then expand another group, all checked boxes uncheck:

public class MyExpandableListAdapter extends BaseExpandableListAdapter
implements OnCheckedChangeListener {

private Context mContext;

private ArrayList<ContactNameItems> mListDataHeader;
private HashMap<String, List<ContactPhoneItems>> mListDataChild;

private boolean[] mGetChecked;
private HashMap<String, boolean[]> mChildCheckStates;

private ArrayList<String> selectedNumbers;

private ChildViewHolder childViewHolder;
private GroupViewHolder groupViewHolder;

private String numberText;

public MyExpandableListAdapter(Context context,
ArrayList<ContactNameItems> listDataHeader,
HashMap<String, List<ContactPhoneItems>> listDataChild,
ArrayList<String> listOfNumbers) {

mContext = context;
mListDataHeader = listDataHeader;
mListDataChild = listDataChild;
selectedNumbers = listOfNumbers;

mChildCheckStates = new HashMap<String, boolean[]>();
}

@Override
public int getGroupCount() {
return mListDataHeader.size();
}

@Override
public ContactNameItems getGroup(int groupPosition) {
return mListDataHeader.get(groupPosition);
}

@Override
public long getGroupId(int groupPosition) {
return groupPosition;
}

@Override
public View getGroupView(int groupPosition, boolean isExpanded,
View convertView, ViewGroup parent) {

String contactName = getGroup(groupPosition).getName();
Bitmap contactImage = getGroup(groupPosition).getImage();

mGetChecked = new boolean[getChildrenCount(groupPosition)];
mChildCheckStates.put(contactName, mGetChecked);

if (convertView == null) {

LayoutInflater inflater = (LayoutInflater) mContext
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.contact_name_item, null);

groupViewHolder = new GroupViewHolder();

groupViewHolder.mContactName = (TextView) convertView
.findViewById(R.id.lblListHeader);

groupViewHolder.mContactImage = (ImageView) convertView
.findViewById(R.id.ivContactPhoto);

convertView.setTag(groupViewHolder);
} else {

groupViewHolder = (GroupViewHolder) convertView.getTag();
}

if (contactImage != null) {
groupViewHolder.mContactImage.setImageBitmap(contactImage);

} else {
groupViewHolder.mContactImage
.setImageResource(R.drawable.default_contact);
}

groupViewHolder.mContactName.setText(contactName);

return convertView;
}

@Override
public int getChildrenCount(int groupPosition) {
return mListDataChild.get(mListDataHeader.get(groupPosition).getName())
.size();
}

@Override
public ContactPhoneItems getChild(int groupPosition, int childPosition) {
return mListDataChild.get(mListDataHeader.get(groupPosition).getName())
.get(childPosition);
}

@Override
public long getChildId(int groupPosition, int childPosition) {
return childPosition;
}

@Override
public View getChildView(int groupPosition, int childPosition,
boolean isLastChild, View convertView, ViewGroup parent) {

final String contactName = getGroup(groupPosition).getName();
final int mChildPosition = childPosition;

numberText = getChild(groupPosition, childPosition).getNumber();
String typeText = getChild(groupPosition, childPosition).getPhoneType();

mGetChecked = mChildCheckStates.get(contactName);

if (convertView == null) {

LayoutInflater inflater = (LayoutInflater) this.mContext
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.contact_detail_item, null);

childViewHolder = new ChildViewHolder();

childViewHolder.mPhoneNumber = (TextView) convertView
.findViewById(R.id.tv_phone_number);

childViewHolder.mPhoneType = (TextView) convertView
.findViewById(R.id.tv_phone_type);

childViewHolder.mCheckBox = (CheckBox) convertView
.findViewById(R.id.checkBox);

convertView.setTag(R.layout.contact_detail_item, childViewHolder);

} else {

childViewHolder = (ChildViewHolder) convertView
.getTag(R.layout.contact_detail_item);
}

childViewHolder.mPhoneNumber.setText(numberText);
childViewHolder.mPhoneType.setText(typeText);

childViewHolder.mCheckBox.setChecked(mGetChecked[mChildPosition]);
childViewHolder.mCheckBox.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {

boolean isChecked = childViewHolder.mCheckBox.isChecked();

Log.d("Debug", "isChecked = " + String.valueOf(isChecked));

if (isChecked) {

selectedNumbers.add(numberText);

} else {

selectedNumbers.remove(numberText);
}

childViewHolder.mCheckBox.setChecked(isChecked);

mGetChecked[mChildPosition] = isChecked;
mChildCheckStates.put(contactName, mGetChecked);
}
});

return convertView;
}

@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return false;
}

@Override
public boolean hasStableIds() {
return false;
}

public ArrayList<String> getSelectedNumbers() {

return selectedNumbers;

}

public final class GroupViewHolder {

TextView mContactName;
ImageView mContactImage;
}

public final class ChildViewHolder {

TextView mPhoneNumber;
TextView mPhoneType;
CheckBox mCheckBox;
}

@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
// TODO Auto-generated method stub
Log.d("Debug", "onCheckChangedListener : " + String.valueOf(isChecked));
}


}

Answer

I see you have a selectedNumbers object, what type of collection is that? It might make most sense to have a Map or List or some collection based on something more unique, like the group & child position. Then you can do this:

(EDIT: edited the code to make sure the checkedChangedListener doesn't trigger) (EDIT 2 based on OP's update): You probably shouldn't use a map of Strings to boolean arrays. Instead try this. It's fairly similar but uses ints which will be more foolproof to compare than Strings

...
HashMap<Integer, Integer> mCheckedStates; // assume initialized as you did
...

childViewHolder.mPhoneNumber.setText(numberText);
childViewHolder.mPhoneType.setText(typeText);
childViewHolder.mCheckBox.setOnCheckedChangeListener(null);
if (mCheckedStates.contains(groupPosition)) {
    int checkedChildren = mCheckedStates.get(groupPosition);
    if ((checkedChildren & (1 << childPosition)) ==  1) {
        childViewHolder.mCheckBox.setChecked(true);
    } else {
        //Edit: Cannot rely on checkbox being false on Recycle,
        childViewHolder.mCheckBox.setChecked(false);
    }
}
childViewHolder.mCheckBox.setOnCheckedChangeListener(this);

@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {

    // get group and child positions
    if (isChecked) {

        if (mCheckedStates.contains(groupPosition)) {
            // I'm using a bitmap to keep resources low, but it's not necessary to do it this way
            int childmap = mCheckedStates.get(groupPosition);
            childmap += (1 << childPosition);
            mCheckedStates.put(groupPosition, childmap);
        } else {
            mCheckedStates.put(groupPosition, (1 << childPosition));
        }
    } else {
        if (mCheckedStates.contains(groupPosition)) {
            int childmap = mCheckedStates.get(groupPosition);
            if (childmap == (1 << childPosition)) {
                mCheckedStates.remove(groupPosition);
            } else {
                childmap &= ~(1 << childPosition);
                mCheckedStates.put(groupPosition, childmap);
            }
        }
    }
}

}