-1

I need to select some items using a checkbox in each entry of a android Recycler View. Once I checked the checkbox in position 0, every 9th position checkbox also checked automatically when scrolling.

How to overcome this?

//In my adapter class

   holder.approveCheckbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
            Logger.infoLog("Checked Position ==> "+position+" boolean ==> "+b);
        }
    });
James Z
  • 12,209
  • 10
  • 24
  • 44
Viswa Sundhar
  • 121
  • 3
  • 16

4 Answers4

3

Use an array to hold the state of the items

In the adapter use a Map or a SparseBooleanArray (which is similar to a map but is a key-value pair of int and boolean) to store the state of all the items in our list of items and then use the keys and values to compare when toggling the checked state

In the Adapter create a SparseBooleanArray

// sparse boolean array for checking the state of the items

    private SparseBooleanArray itemStateArray= new SparseBooleanArray();

then in the item click handler onClick() use the state of the items in the itemStateArray to check before toggling, here is an example

        @Override
        public void onClick(View v) {
            int adapterPosition = getAdapterPosition();
            if (!itemStateArray.get(adapterPosition, false)) {
                mCheckedTextView.setChecked(true);
                itemStateArray.put(adapterPosition, true);
            }
            else  {
                mCheckedTextView.setChecked(false);
                itemStateArray.put(adapterPosition, false);
            }
        }

also, use sparse boolean array to set the checked state when the view is bound

@Override
public void onBindViewHolder(ViewHolder holder, int position) {
    holder.bind(position);
}

@Override
public int getItemCount() {
    if (items == null) {
        return 0;
    }
    return items.size();
}

 void loadItems(List<Model> tournaments) {
    this.items = tournaments;
    notifyDataSetChanged();
}


class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

    CheckedTextView mCheckedTextView;

    ViewHolder(View itemView) {
        super(itemView);
        mCheckedTextView = (CheckedTextView) itemView.findViewById(R.id.checked_text_view);
        itemView.setOnClickListener(this);
    }

    void bind(int position) {
        // use the sparse boolean array to check
        if (!itemStateArray.get(position, false)) {
            mCheckedTextView.setChecked(false);}
        else {
            mCheckedTextView.setChecked(true);
        }
    }
Community
  • 1
  • 1
Basi
  • 3,009
  • 23
  • 28
  • the final adapter will be like [this](https://gist.github.com/e4basil/a9027ccc400486ef45879dd96e5cadad) – Basi Jan 18 '19 at 12:38
1

You are facing this issue because RecyclerView uses ViewHolder pattern, by which it reuses the child view and reduce the memory consumption.

you can follow this link

or

you can follow these simple steps.

1) Take a boolean in your Data Source i.e List of your data/model class.
(This boolean will store the current state of the checkbox)

2) In onClick event of checkbox toggle the value of that boolean for the rendered data-model and call notifyDataSetChanged.
(Don't use onCheckedChanged here)

3) While rendering put condition whether to mark the checkbox as checked or unchecked based on that boolean.

Nitesh
  • 3,868
  • 1
  • 20
  • 26
1

If you can get the position inside an annonymous implementations it means it is final. Like inside the checkbox listener there.

A final position will cause discrepancies between the data and the UI, because it wont be defined again. At this point there should be a lint in on onBindViewHolder method, that has a better explanation than mine.

For accesing the position as no final you can use:

holder.getAdapterPosition()

You dont have to worry about the holder been final because the holder is the row and it wont be re-instantiates again. If it is recycled by the adapter then there is no problem with it.

cutiko
  • 9,887
  • 3
  • 45
  • 59
1

I got problems with this when i assigned the checkedChange listener in OnBindViewHolder, sence this would be called multiple times when recycled. what worked for me was defining the callback and listener in the viewHolder create method.

public class CheckBoxHolder : RecyclerView.ViewHolder
    {
        public TextView line, destination, twd;
        public ImageView icon;
        public LinearLayout lineParent;
        public CheckBox checkBox;
        public CheckBoxHolder(View view, Action<int, CompoundButton.CheckedChangeEventArgs> listener) : base(view)
        {
            checkBox = view.FindViewById<CheckBox>(Resource.Id.check_checkBox);

            twd = view.FindViewById<TextView>(Resource.Id.check_twd);
            destination = view.FindViewById<TextView>(Resource.Id.check_dest);
            line = view.FindViewById<TextView>(Resource.Id.check_line);
            lineParent = view.FindViewById<LinearLayout>(Resource.Id.check_line_parent);
            icon = view.FindViewById<ImageView>(Resource.Id.check_icon); 
            view.Click += (sender, e) => {
                checkBox.Checked = !checkBox.Checked;
            };
            checkBox.CheckedChange += (object sender, CompoundButton.CheckedChangeEventArgs e) => listener(base.Position, e);
        }
    }

this is C# code from xamarin, but the concept should be applicable to your problem.

Joachim Haglund
  • 775
  • 5
  • 15