I need some help with Multi/Single selection. Found what I was looking for here, because of its simplicity.
I'm using a GridLayoutManager
I have over 90 items in my adapter, a CardView
with a TextView
and an ImageView
, while using the procedure described in the post.
When I select one, or more than one item, as I scroll down, other items "seems" to be selected because the background replicates, but they are not selected.
Tried placing the setOnClickListener
, in onBindViewHolder
and also in MyViewHolder class and in both of them I get the same behaviour. When scrolling down other items seems to be selected.
Used notifyItemChanged(position)
and notifyDataSetChanged()
in the adapter, but the background does not change at all, although the setSelected
works properly.
Also I used setHasFixedSize(true)
in the RecyclerView setup.
In onBindViewHolder
@Override
public void onBindViewHolder(MyViewHolder myViewHolder, final int position) {
PatternImages currentPattern = patternImages.get(position);
myViewHolder.setData(currentPattern, position);
myViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
v.setSelected(!v.isSelected());
if (v.isSelected()) {
v.setBackgroundColor(ContextCompat.getColor(context, R.color.colorPrimaryHighLight));
} else {
v.setBackgroundColor(Color.WHITE);
}
notifyItemChanged(position);
}
});
}
Model
public class PatternImages {
private int imageId, imageName;
private boolean isSelected;
public PatternImages(int imageId, int imageName, boolean isSelected) {
this.imageId = imageId;
this.imageName = imageName;
this.isSelected = isSelected;
}
public int getImageId() {
return imageId;
}
public void setImageId(int imageId) {
this.imageId = imageId;
}
public int getImageName() {
return imageName;
}
public void setImageName(int imageName) {
this.imageName = imageName;
}
public boolean isSelected() {
return isSelected;
}
public void setSelected(boolean selected) {
isSelected = selected;
}
RecyclerView Setup
private void setUpPatternsRecyclerView() {
RecyclerView recyclerPatternsView = (RecyclerView) findViewById(R.id.pattern_image_recycler_view);
PatternImageAdapter adapter = new PatternImageAdapter(this, patternImages);
recyclerPatternsView.setAdapter(adapter);
ColumnQty columnQty = new ColumnQty(this, R.layout.item_image_pattern_cardview);
GridLayoutManager gridLayoutManager = new GridLayoutManager(getApplicationContext(), columnQty.calculateNoOfColumns());
recyclerPatternsView.setHasFixedSize(true);
recyclerPatternsView.setLayoutManager(gridLayoutManager);
recyclerPatternsView.setItemAnimator(new DefaultItemAnimator());
recyclerPatternsView.addItemDecoration(new GridSpacing(columnQty.calculateSpacing()));
}
setData Method
public void setData(PatternImages currentPattern, int position) {
this.position = position;
patternName.setText(context.getString(currentPattern.getImageName()));
patternName.setTypeface(Typeface.createFromAsset(context.getAssets(), "fonts/ElMessiri-SemiBold.ttf"));
patternImage.setBackgroundResource(currentPattern.getImageId());
if (position == 0 || position == 1) {
animationDrawable = (AnimationDrawable) patternImage.getBackground();
animationDrawable.start();
}
}
A RecyclerView
as its name suggests, recycles views. That means that once a view scrolls off screen, it can be reused.
Before a view is reused, it still contains all of the settings from the last time it was used. For example, if it contains a TextView
, that TextView
will still have its Text property set to whatever it was the last time it was displayed.
The reason that some items "seem" to be selected is because your selected views that have scrolled off screen are now being reused and you have not unselected them.
In your OnBindViewHolder
method, you need to "reset" all the views back to their defaults. In this case that would be to "turn off" whatever method you use to make a view appear selected.
For example:
@Override
public void onBindViewHolder(MyViewHolder myViewHolder, final int position) {
final PatternImages currentPattern = patternImages.get(position);
myViewHolder.setData(currentPattern, position);
myViewHolder.itemView.setBackgroundColor(currentPattern.isSelected() ?R.color.Red: R.color.WHITE); // choose your colors
myViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
currentPattern.setSelected(!currentPattern.isSelected())
notifyItemChanged(position);
}
});
}
Essentially, every time you bind, you set the background colour to the selected or non selected state based on the relevant property in your model.
recently, i had worked on recyclerview multi selection, so you could try this first initialize sparseboolean, boolean an one int like this :
private SparseBooleanArray storeChecked = new SparseBooleanArray();
private boolean isMultiselect;
private int itemSelected;
so on bindViewHolder, add this
holder.view.setbackground(storechecked.get(position) ? Color.White : Color.Black)
then,
implement onLongClickListener.
on longClick
add this:
if(!ismultiSelect){
storechecked.put(getAdapterPosition(), true);
notifyDataSetChanged(getAdapterPosition());
triggerOnLongClickListener(++itemSelected); // using listerner i've transfer position to fragment for actionmode selected count
}
after this, in onClick do this:
if(ismultiSelect){
boolean tof = storechecked.get(getAdapterPosition());
if (tof){
triggerOnItemClickListener(--itemSelected, v); // transfer position to update unselected
storeChecked.delete(position);// delete position of unselected position in the fragment
}else {
triggerOnItemClickListener(++itemSelected, v);
// transfer position to update selected position in the fragment
}
}
**Other methods in adapter**
//clear on actionmode close
public void exitMultiselectMode() {
isMultiselect = false;
itemSelected = 0;
storeChecked.clear();
notifyDataSetChanged();
}
// get all selected position
public List<Integer> getSelectedItems() {
List<Integer> items = new ArrayList<>(storeChecked.size());
for (int i = 0; i < storeChecked.size(); ++i) {
items.add(storeChecked.keyAt(i));
}
return items;
}
Try to keep the state in model but in view, and bind the model to view in onBindViewHolder
.
I tried @Kuffs solution and I'm posting my final code for reference.
onBindViewHolder
@Override
public void onBindViewHolder(final MyViewHolder myViewHolder, final int position) {
final PatternImages currentPattern = patternImages.get(position);
myViewHolder.setData(currentPattern, position);
myViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
currentPattern.setSelected(!currentPattern.isSelected());
notifyItemChanged(position);
}
});
}
setData()
Method
public void setData(PatternImages currentPattern, int position) {
this.position = position;
patternName.setText(context.getString(currentPattern.getImageName()));
patternName.setTypeface(Typeface.createFromAsset(context.getAssets(), "fonts/ElMessiri-SemiBold.ttf"));
patternImage.setBackgroundResource(currentPattern.getImageId());
if (position == 0 || position == 1) {
animationDrawable = (AnimationDrawable) patternImage.getBackground();
animationDrawable.start();
}
itemView.setBackgroundColor(currentPattern.isSelected() ? ContextCompat.getColor(context, R.color.colorPrimaryHighLight) : Color.WHITE);
}