Last active
April 14, 2023 15:15
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
Hi ,Is there any way to store "HashMap<Integer, boolean[]> mChildCheckStates=new HashMap<Integer, boolean[]>();" into shared preferences or any other storage.
Thanks a lot for the example. I just want to know how am I supposed the retrieve the items selected.
thanks man you do useful work
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);
}
}
});
`
working example would be to helpful if anybody have.
HI i am new in development, can anybody share the xml to use. thanks in advance to all.
Hi, I want to getText of selected items, Can anyone please help with this?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Great solution , it is very useful. Thanks!!!