Kheiro Ok
Kheiro Ok

Reputation: 37

Select checkbox is duplicated when scrolling on an ExpandableListView

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

Answers (3)

jakir hussain
jakir hussain

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

nomag
nomag

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

Andrey Dobrikov
Andrey Dobrikov

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

Related Questions