Reputation: 153
I am using a custom recyclerview adapter with checkbox so that user can select multiple checked items.
In the beginning I faced duplicate checkbox selection while scrolling down so I added a position array to keep checkbox selection true or false.Now duplicate checkbox selection problem is gone but while scrolling down selected checkbox are deselected.My recyclerview adpater is given below,
class IngredientsAdapter(var context: Context?,var activity: Activity, var ingredientList:ArrayList<String>, var imageID:Int):
RecyclerView.Adapter<IngredientsAdapter.IngredientViewHolder>() {
private var positionArray:ArrayList<Boolean> = ArrayList(ingredientList.size)
private val selectedList=ArrayList<String>()
init {
for (i in ingredientList.indices) {
positionArray.add(false)
}
}
override fun onCreateViewHolder(p0: ViewGroup, p1: Int): IngredientViewHolder {
val layoutInflater = LayoutInflater.from(p0.context)
return (IngredientsAdapter.IngredientViewHolder(layoutInflater.inflate(R.layout.ingredient_list_row,p0,false)))
}
override fun getItemCount(): Int {
return ingredientList.size
}
override fun onBindViewHolder(p0: IngredientViewHolder, p1: Int) {
p0.imageView.setImageResource(imageID)
p0.textView.text = ingredientList[p1]
p0.checkBox.isChecked = positionArray[p1]
val sharedPreferences= SharedPreferenceHelper(context, SystemConstants.SHARED_PREFS_CHECKDATA)
val checked=sharedPreferences.getPrefsBooleanValue(ingredientList[p1])
p0.checkBox.isChecked = checked
p0.checkBox.setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
if(isChecked){
positionArray[p1] = true
selectedList.add(ingredientList[p1])
ingredientList.get(p1)
sharedPreferences.addPrefsBooleanVal(ingredientList[p1],true)
Log.d("debug","selecteditem==>$selectedList")
}else{
positionArray[p1] = false
selectedList.remove(ingredientList[p1])
sharedPreferences.addPrefsBooleanVal(ingredientList[p1],false)
Log.d("debug","selecteditem==>$selectedList")
}
})
}
class IngredientViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var textView= itemView.txt_row!!
var imageView= itemView.image_view!!
var checkBox= itemView.chk_row!!
}
}
Any suggestion is appreciated.
Upvotes: 3
Views: 6252
Reputation: 1299
OnClickListener
won't work when sliding the Switch
.
Since RecyclerView
is recycling views, a previously attached OnCheckedChangeListener
can be triggered when setting checked value for the Switch
of the new item.
When binding new data to an item:
switch.setOnCheckedChangeListener(null) // remove any existing listener from recycled view
switch.isChecked = [true/false] // will no longer trigger any callback to listener
switch.setOnCheckedChangeListener { btnView, isChecked ->
// do exiting stuff
}
Upvotes: 8
Reputation: 33
What @Bek suggested is the correct solution. You will need to use OnClickListener. Also what you commented is not wrong either, just what you used to use(ListView) is or was!
Let me break it down for you:
ListView: The older version of RecyclerView.
Wonder why the developer created another redundant component? Basically Memory Issue! ListView creates as many ItemViews as it requires whereas RecyclerView just recycles the itemView.
For example if for a list of 100 items if we use ListView it will initially create as many ItemViews as the phone can display. Then as we scroll down it keeps creating more Views, till the 100th item(100 ItemViews in memory). But RecyclerView does this more efficiently it creates as many views it can show + 1 (Next view in list) then it keeps recycling them so we always have only As many views in screen +1 and not 100 when we reach the bottom of the list.
For more details read this and this.
OnCheckChangeListener: The Other problem maker!
This listener is called whenever check changes for the checkbox, Whenever! So if i where to refresh a checkbox this listener(theoretically) will be called! Getting where am going with this? Yup when used along with RecyclerView its gonna cause an havoc in ones code. The moment recyclerView destroys or reuses an ItemView the checkbox is reset which fires the listener and causing your SharedPref to rewrite the check! I tested this by adding logs inside the listener and saw it get triggered for outer most views when it got recycled.
This is just my findings, there maybe some way or fix for this but i too would suggest using OnClickListener and write a listener to change the model in main class rather than sharedPref in adapter.
Oh! You can use ViewHolder.SetIsRecyclable(false), this would stop RecyclerView from Recycling the views and create as many views as there are items in list. But i wouldn't suggest this as the UI as well as UX will be compromised as u might find a bit lag while scrolling(Memory Issue)!
Long Story Short use OnClickListener with RecyclerView!
Upvotes: 3
Reputation: 69
When you call setOnCheckedChangeListener
, checkbox has a listener, then recyclerview will multiplex view when you scroll.
and you apply p0.checkBox.isChecked = positionArray[p1]
, actually you change last poisiton.
For example , you check the first checkbox, arrray[0]
is true, then you scroll the view , the next item may the fourth or fifth will have the same checkbox refrence.
Then you call p0.checkBox.isChecked = positionArray[p1]
again, but the listener belong the first item . then the listener will change array[0]
.
So you should call p0.checkBox.setOnCheckedChangeListener
before p0.checkBox.isChecked = positionArray[p1]
Upvotes: 1
Reputation: 337
You can use Model class to keep track of each recyclerView item's checkbox. Use setTag and getTag is used to keep track of checkbox status.
Make Model
public class Model {
private boolean isSelected;
private String animal;
public String getAnimal() {
return animal;
}
public void setAnimal(String animal) {
this.animal = animal;
}
public boolean getSelected() {
return isSelected;
}
public void setSelected(boolean selected) {
isSelected = selected;
}
}
create integer.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="btnplusview">1</integer>
<integer name="btnpluspos">2</integer>
</resources>
Finally adapter looks like this:
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
public class CustomAdapter extends
RecyclerView.Adapter<CustomAdapter.MyViewHolder> {
private LayoutInflater inflater;
public static ArrayList<Model> imageModelArrayList;
private Context ctx;
public CustomAdapter(Context ctx, ArrayList<Model> imageModelArrayList) {
inflater = LayoutInflater.from(ctx);
this.imageModelArrayList = imageModelArrayList;
this.ctx = ctx;
}
@Override
public CustomAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int
viewType) {
View view = inflater.inflate(R.layout.rv_item, parent, false);
MyViewHolder holder = new MyViewHolder(view);
return holder;
}
@Override
public void onBindViewHolder(final CustomAdapter.MyViewHolder holder, int
position) {
holder.checkBox.setText("Checkbox " + position);
holder.checkBox.setChecked(imageModelArrayList.get(position).getSelected());
holder.tvAnimal.setText(imageModelArrayList.get(position).getAnimal());
// holder.checkBox.setTag(R.integer.btnplusview, convertView);
holder.checkBox.setTag(position);
holder.checkBox.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Integer pos = (Integer) holder.checkBox.getTag();
Toast.makeText(ctx, imageModelArrayList.get(pos).getAnimal() + "
clicked!", Toast.LENGTH_SHORT).show();
if (imageModelArrayList.get(pos).getSelected()) {
imageModelArrayList.get(pos).setSelected(false);
} else {
imageModelArrayList.get(pos).setSelected(true);
}
}
});
}
@Override
public int getItemCount() {
return imageModelArrayList.size();
}
class MyViewHolder extends RecyclerView.ViewHolder {
protected CheckBox checkBox;
private TextView tvAnimal;
public MyViewHolder(View itemView) {
super(itemView);
checkBox = (CheckBox) itemView.findViewById(R.id.cb);
tvAnimal = (TextView) itemView.findViewById(R.id.animal);
}
}
}
Upvotes: 0