Skip to content

Instantly share code, notes, and snippets.

@Psest328
Last active April 14, 2023 15:15
Show Gist options
  • Save Psest328/8762232 to your computer and use it in GitHub Desktop.
Save Psest328/8762232 to your computer and use it in GitHub Desktop.
ExpandableListView adapter with Checkboxes - BaseExpandableListAdapter that accurately tracks child checkbox states (no random checks/unchecks). This is setup for checkboxes but can be used for anything that uses true/false values
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import android.annotation.SuppressLint;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.TextView;
// Eclipse wanted me to use a sparse array instead of my hashmaps, I just suppressed that suggestion
@SuppressLint("UseSparseArrays")
public class ExpListViewAdapterWithCheckbox extends BaseExpandableListAdapter {
// Define activity context
private Context mContext;
/*
* Here we have a Hashmap containing a String key
* (can be Integer or other type but I was testing
* with contacts so I used contact name as the key)
*/
private HashMap<String, List<ExpListChildItems>> mListDataChild;
// ArrayList that is what each key in the above
// hashmap points to
private ArrayList<ExpListGroupItems> mListDataGroup;
// Hashmap for keeping track of our checkbox check states
private HashMap<Integer, boolean[]> mChildCheckStates;
// Our getChildView & getGroupView use the viewholder patter
// Here are the viewholders defined, the inner classes are
// at the bottom
private ChildViewHolder childViewHolder;
private GroupViewHolder groupViewHolder;
/*
* For the purpose of this document, I'm only using a single
* textview in the group (parent) and child, but you're limited only
* by your XML view for each group item :)
*/
private String groupText;
private String childText
/* Here's the constructor we'll use to pass in our calling
* activity's context, group items, and child items
*/
public ExpListViewAdapterWithCheckbox(Context context,
ArrayList<ExpListGroupItems> listDataGroup, HashMap<String, List<ExpListChildItems>> listDataChild) {
mContext = context;
mListDataGroup = listDataGroup;
mListDataChild = listDataChild;
// Initialize our hashmap containing our check states here
mChildCheckStates = new HashMap<Integer, boolean[]>();
}
@Override
public int getGroupCount() {
return mListDataGroup.size();
}
/*
* This defaults to "public object getGroup" if you auto import the methods
* I've always make a point to change it from "object" to whatever item
* I passed through the constructor
*/
@Override
public ExpListGroupItems getGroup(int groupPosition) {
return mListDataGroup.get(groupPosition);
}
@Override
public long getGroupId(int groupPosition) {
return groupPosition;
}
@Override
public View getGroupView(int groupPosition, boolean isExpanded,
View convertView, ViewGroup parent) {
// I passed a text string into an activity holding a getter/setter
// which I passed in through "ExpListGroupItems".
// Here is where I call the getter to get that text
groupText = getGroup(groupPosition).getMyText();
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) mContext
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.group_item, null);
// Initialize the GroupViewHolder defined at the bottom of this document
groupViewHolder = new GroupViewHolder();
groupViewHolder.mGroupText = (TextView) convertView.findViewById(R.id.groupTextView);
convertView.setTag(groupViewHolder);
} else {
groupViewHolder = (GroupViewHolder) convertView.getTag();
}
groupViewHolder.mGroupText.setText(groupText);
return convertView;
}
@Override
public int getChildrenCount(int groupPosition) {
return mListDataChild.get(mListDataGroup.get(groupPosition).getMyText()).size();
}
/*
* This defaults to "public object getChild" if you auto import the methods
* I've always make a point to change it from "object" to whatever item
* I passed through the constructor
*/
@Override
public ExpListChildItems getChild(int groupPosition, int childPosition) {
return mListDataChild.get(mListDataGroup.get(groupPosition).getMyText()).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 int mGroupPosition = groupPosition;
final int mChildPosition = childPosition;
// I passed a text string into an activity holding a getter/setter
// which I passed in through "ExpListChildItems".
// Here is where I call the getter to get that text
childText = getChild(mGroupPosition, mChildPosition).getChildText();
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) this.mContext
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.child_item, null);
childViewHolder = new ChildViewHolder();
childViewHolder.mChildText = (TextView) convertView
.findViewById(R.id.childTextView);
childViewHolder.mCheckBox = (CheckBox) convertView
.findViewById(R.id.checkBox);
convertView.setTag(R.layout.child_item, childViewHolder);
} else {
childViewHolder = (ChildViewHolder) convertView
.getTag(R.layout.child_item);
}
childViewHolder.mChildText.setText(childText);
/*
* You have to set the onCheckChangedListener to null
* before restoring check states because each call to
* "setChecked" is accompanied by a call to the
* onCheckChangedListener
*/
childViewHolder.mCheckBox.setOnCheckedChangeListener(null);
if (mChildCheckStates.containsKey(mGroupPosition)) {
/*
* if the hashmap mChildCheckStates<Integer, Boolean[]> contains
* the value of the parent view (group) of this child (aka, the key),
* then retrive the boolean array getChecked[]
*/
boolean getChecked[] = mChildCheckStates.get(mGroupPosition);
// set the check state of this position's checkbox based on the
// boolean value of getChecked[position]
childViewHolder.mCheckBox.setChecked(getChecked[mChildPosition]);
} else {
/*
* if the hashmap mChildCheckStates<Integer, Boolean[]> does not
* contain the value of the parent view (group) of this child (aka, the key),
* (aka, the key), then initialize getChecked[] as a new boolean array
* and set it's size to the total number of children associated with
* the parent group
*/
boolean getChecked[] = new boolean[getChildrenCount(mGroupPosition)];
// add getChecked[] to the mChildCheckStates hashmap using mGroupPosition as the key
mChildCheckStates.put(mGroupPosition, getChecked);
// set the check state of this position's checkbox based on the
// boolean value of getChecked[position]
childViewHolder.mCheckBox.setChecked(false);
}
childViewHolder.mCheckBox.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
boolean getChecked[] = mChildCheckStates.get(mGroupPosition);
getChecked[mChildPosition] = isChecked;
mChildCheckStates.put(mGroupPosition, getChecked);
} else {
boolean getChecked[] = mChildCheckStates.get(mGroupPosition);
getChecked[mChildPosition] = isChecked;
mChildCheckStates.put(mGroupPosition, getChecked);
}
}
});
return convertView;
}
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return false;
}
@Override
public boolean hasStableIds() {
return false;
}
public final class GroupViewHolder {
TextView mGroupText;
}
public final class ChildViewHolder {
TextView mChildText;
CheckBox mCheckBox;
}
}
@ashokbg
Copy link

ashokbg commented Apr 6, 2017

on-expand , collapse group then checked checkbox getting unchecked...

@amritesh14
Copy link

Great solution , it is very useful. Thanks!!!

@amritesh14
Copy link

Hi ,Is there any way to store "HashMap<Integer, boolean[]> mChildCheckStates=new HashMap<Integer, boolean[]>();" into shared preferences or any other storage.

@shubhrajps
Copy link

Thanks a lot for the example. I just want to know how am I supposed the retrieve the items selected.

@jay2011patel
Copy link

thanks man you do useful work

Copy link

ghost commented Mar 9, 2018

Hey is there a special reason why the same code will be executed on if and else? ^^

`
childViewHolder.mCheckBox.setOnCheckedChangeListener(new OnCheckedChangeListener() {

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

				boolean getChecked[] = mChildCheckStates.get(mGroupPosition);
				getChecked[mChildPosition] = isChecked;
				mChildCheckStates.put(mGroupPosition, getChecked);
							
			} else {

				boolean getChecked[] = mChildCheckStates.get(mGroupPosition);
				getChecked[mChildPosition] = isChecked;
				mChildCheckStates.put(mGroupPosition, getChecked);
			}
		}
	});

`

@chnouman
Copy link

working example would be to helpful if anybody have.

@NomiRizwan
Copy link

HI i am new in development, can anybody share the xml to use. thanks in advance to all.

@20244
Copy link

20244 commented Jun 18, 2018

Hi, I want to getText of selected items, Can anyone please help with this?

@samuellim18
Copy link

image

Hi, I seem to get this recurring problem when I copied and pasted the code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment