Reputation: 37
I have a BaseExpandableListAdapter (code below) which manipulates an ExpandableListView and a model called Checklist.
The Checklist model contains a List of Categories.
A Category is a List of Checks.
A Check is a List of Lows.
And a Low contains a boolean item which is represented by a checkbox in the view.
I use a ViewHolder pattern to handle my models : CategoryViewHolder for my GroupView and CheckViewHolder for my ChildView.
The problem I have, is within the getChildView method, when I select a checkbox in a Low (within a Check) and scroll down, I find an other checkbox selected. The same thing happen when I write something in an edittext object within the Check and I scroll, an other edittext is edited.
I suspect my vue recycling is not working properly but I haven’t found any solutions.
Any ideas?
public class ChecklistAdapter extends BaseExpandableListAdapter {
private Context mContext;
private Checklist mChecklist;
private boolean isEditable;
private final int ID_LOW_ADDED = -1;
public ChecklistAdapter(Context mContext, Checklist mChecklist, boolean editable) {
this.mContext = mContext;
this.mChecklist = mChecklist;
this.isEditable = editable;
}
@Override
public Object getChild(int groupPosition, int childPosition) {
return mChecklist.getCategories().get(groupPosition).getChecks().get(childPosition);
}
@Override
public long getChildId(int groupPosition, int childPosition) {
return childPosition;
}
// ChildView is the Check item with a label and buttons actions
@Override
public View getChildView(final int groupPosition, final int childPosition, boolean isLastChild, View convertView, final ViewGroup parent) {
final CheckViewHolder holder;
final List<Category> mCategories = mChecklist.getCategories();
final Category mCategory = (Category) getGroup(groupPosition);
final List<Check> mChecks = mCategory.getChecks();
final Check mCheck = (Check) getChild(groupPosition, childPosition);
final List<Low> mLows = mCheck.getLows();
if (convertView != null){
holder = (CheckViewHolder) convertView.getTag();
}
else {
LayoutInflater inflater = (LayoutInflater) this.mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.item_check, parent, false);
holder = new CheckViewHolder(convertView);
convertView.setTag(holder);
}
Utils.logK("Holder = "+holder);
// Prepare checking lows elements
addLowsToView(mLows, convertView, holder);
// Set Checks elements
String label = mCheck.getLabel();
holder.tvCheckLabel.setText(label);
// Check the state of button
final View finalConvertView = convertView;
final ViewGroup finalParent = parent;
holder.rgActions.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup radioGroup, @IdRes int rbId) {
switch (rbId){
case R.id.rb_ko:
holder.llLow.setVisibility(View.VISIBLE);
mCheck.setState(Check.KO);
//finalParent.scrollTo(0,finalConvertView.getTop());
//((ExpandableListView) parent).smoothScrollToPosition(finalConvertView.getTop());
//((ExpandableListView) parent).setSelection(0);
//Utils.logK("Scroll to top = "+finalConvertView.getTop());
break;
case R.id.rb_nc:
holder.llLow.setVisibility(View.GONE);
mCheck.setState(Check.NC);
break;
case R.id.rb_ok:
holder.llLow.setVisibility(View.GONE);
mCheck.setState(Check.OK);
break;
default:
//TODO
}
// Prepere updated checklist object to send
mChecks.set(childPosition, mCheck);
mCategory.setChecks(mChecks);
mCategories.set(groupPosition, mCategory);
mChecklist.setCategories(mCategories);
}
});
/**
* Save the state of radioButtons (selection)
*/
if(mCheck.getState() != null) {
switch (mCheck.getState()) {
case Check.KO:
holder.rbKo.setChecked(true);
holder.rbKo.setSelected(true);
break;
case Check.NC:
holder.rbNc.setChecked(true);
holder.rbNc.setSelected(true);
break;
case Check.OK:
holder.rbOk.setChecked(true);
holder.rbOk.setSelected(true);
break;
}
}
holder.cbOther.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
int lowsSize = mLows.size();
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
if (isChecked){
holder.etOther.setEnabled(true);
}
else{
holder.etOther.setEnabled(false);
}
// Add the new low and updated checklist object to send
final Low mOtherLow = new Low();
mOtherLow.setId(ID_LOW_ADDED);
mOtherLow.setSelected(true);
// TODO save checklist when text is added
holder.etOther.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void afterTextChanged(Editable editable) {
mOtherLow.setLabel(holder.etOther.getText().toString());
}
});
Utils.logK("otherLow added = "+mOtherLow.getLabel());
//long idLastLowAdded = mLows.get(mLows.size()-1).getId();
Utils.logK("Low added = "+mOtherLow.getLabel());
mLows.add(mOtherLow);
mCheck.setLows(mLows);
mChecks.set(childPosition, mCheck);
mCategory.setChecks(mChecks);
mCategories.set(groupPosition, mCategory);
mChecklist.setCategories(mCategories);
}
});
holder.setViewsEnabled(isEditable);
return convertView;
}
@Override
public int getChildrenCount(int groupPosition) {
return mChecklist.getCategories().get(groupPosition).getChecks().size();
}
@Override
public Object getGroup(int groupPosition) {
return mChecklist.getCategories().get(groupPosition);
}
@Override
public int getGroupCount() {
return mChecklist.getCategories().size();
}
@Override
public long getGroupId(int groupPosition) {
return groupPosition;
}
@Override
public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
final CategoryViewHolder groupHolder;
final Category mCategory = (Category) getGroup(groupPosition);
if (convertView != null){
groupHolder = (CategoryViewHolder) convertView.getTag();
}
else {
LayoutInflater inflater = (LayoutInflater) this.mContext
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.item_category, parent, false);
groupHolder = new CategoryViewHolder(convertView);
convertView.setTag(groupHolder);
}
groupHolder.tvCategoryTitle.setText(mCategory.getLabel());
return convertView;
}
@Override
public boolean hasStableIds() {
return false;
}
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}
/**
* Add the lines of works to my check item (checkboxes)
* @param lows List of line of work
* @param v The view will be attached this new view
*/
private void addLowsToView(final List<Low> lows, View v, final CheckViewHolder holder) {
LinearLayout llLowPosition = v.findViewById(R.id.ll_low_position);
if(((LinearLayout) llLowPosition).getChildCount() > 0)
((LinearLayout) llLowPosition).removeAllViews();
for (int i = 0; i < lows.size(); i++) {
final int position = i;
final Low low = lows.get(i);
// Ignore the "others" lows (id of an other low start at -1 and decrement for each time the
// user click on "other" checkbox
if (low.getId()<0){
if (isEditable){
break;
}
else{
holder.cbOther.setSelected(true);
holder.etOther.setText(low.getLabel());
}
}
else {
CheckBox cbLow = new CheckBox(mContext);
cbLow.setText(lows.get(i).getLabel());
cbLow.setChecked(lows.get(i).isSelected());
//cbLow.setButtonDrawable(ContextCompat.getColor(mContext,R.color.accent));
cbLow.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
low.setSelected(isChecked);
lows.set(position, low);
}
});
llLowPosition.addView(cbLow, 0, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
// Disable if it's not editable mode
if (!isEditable){
cbLow.setEnabled(false);
}
}
}
}
/**
* Allow activity to get the updated checklist
* @return The updated checklist
*/
public Checklist getChecklist(){
return mChecklist;
}
/**
* Class that define the view holder of a child : the check model
*/
static class CheckViewHolder{
@BindView(R.id.tv_check_label)
TextView tvCheckLabel;
@BindView(R.id.rg_actions)
RadioGroup rgActions;
@BindView(R.id.ll_low)
LinearLayout llLow;
@BindView(R.id.ll_low_position)
LinearLayout llLowPosition;
@BindView(R.id.cb_other)
CheckBox cbOther;
@BindView(R.id.et_other)
EditText etOther;
@BindView(R.id.rb_ko)
RadioButton rbKo;
@BindView(R.id.rb_nc)
RadioButton rbNc;
@BindView(R.id.rb_ok)
RadioButton rbOk;
@BindView(R.id.cv_item_check)
CardView cvItemCheck;
public CheckViewHolder(View view){
ButterKnife.bind(this, view);
}
/**
* Set actions on the view. If it's not editable, disable all actions.
* @param editable : Mode of view : consulting or editing
*/
public void setViewsEnabled(boolean editable){
rgActions.setEnabled(editable);
llLowPosition.setEnabled(editable);
rbKo.setEnabled(editable);
rbNc.setEnabled(editable);
rbOk.setEnabled(editable);
cbOther.setEnabled(editable);
//etOther.setEnabled(editable);
}
}
/**
* Class that define the view holder of a parent : the category model
*/
static class CategoryViewHolder{
@BindView(R.id.tv_category_title)
TextView tvCategoryTitle;
public CategoryViewHolder(View v){
ButterKnife.bind(this, v);
}
}
}
Upvotes: -1
Views: 936
Reputation: 316
Declare ArrayList with the same size in your Activity as your mChecklist has
like :- ArrayList mChecklistBool = new ArrayList();
for (int i = 0;i<Checklist.size();i++)
{
mChecklistBool.add(false);
}
When you call adapter send this ArrayList of Boolean
new ChecklistAdapter (mContext,mChecklist,mChecklistBool,editable);
In Adapter class declare
ArrayList<Boolean> mChecklistBool;
public ChecklistAdapter(Context mContext, Checklist mChecklist, ArrayList<Boolean> mChecklistBool,boolean editable) {
this.mContext = mContext;
this.mChecklist = mChecklist;
this.isEditable = editable;
this.mChecklistBool =mChecklistBool;
}
Then where you bind your view with view holder then get the checked postion and after you can set or reset any postion.
new View.OnClickListener() {
public void onClick(View v) {
CheckBox cb = (CheckBox) v;
if (mChecklist.contains(v.getTag())) {
int ChkPos = mChecklist.indexOf(v.getTag());
mChecklistBool.set(ChkPos, cb.isChecked());
}
}
Upvotes: 0
Reputation: 300
In recycler view, views are recycled and therefore, some checkbox are already checked (which were checked before scrolling and now recycled). This is normal behaviour and you need to unset the state of checkbox and edit text.
Upvotes: 0
Reputation: 457
If I understood you correctly, you check on your checkbox, then scroll, and then some random checkboxes get checked? I had similar problem in ExpandableListView, you need to implement something like in the question:
Checking a checkbox in listview makes other random checkboxes checked too
Upvotes: 0