1

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();
    }


}
Guanaco Devs
  • 1,822
  • 2
  • 21
  • 38

4 Answers4

5

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.

Kuffs
  • 35,581
  • 10
  • 79
  • 92
  • I added my `setData` method to the post and included the 5th line you provided in your code. I might be wrong, but in the code you provided in the `onClick`, my pattern won't change the background, I believe(I'm rookie). Now is working thanks to your comments, note that it works with or without the `notifyItemChanged(position)`. For what I understand my error was not to provide a background initially to the view, as the view's background was already white(tried with `Color.RED` and `Color.BLUE` for testing) – Guanaco Devs Jun 23 '17 at 08:38
  • 1
    Yes the code above will change the background. That is what NotifyItemChanged does. It caused the OnBindViewHolder to be called again for the relevant position which then in turn sets the background color of the container view. – Kuffs Jun 23 '17 at 09:02
  • You are totally right, thank you for your time. I had a week trying to figure this out. – Guanaco Devs Jun 23 '17 at 09:06
  • 1
    Additionally, I would move some code from `setData` to the `OnCreateViewHolder` method. For example, you are setting the typeface of your `TextView` every time you bind data. You only need to do that once when the view is created as it never changes. Not very efficient to do it repeatedly when is is not needed. You are slowing the process down unnecessarily. – Kuffs Jun 23 '17 at 09:12
  • Wow, that's a great bonus, Thank You!! – Guanaco Devs Jun 23 '17 at 09:24
2

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;
  }
Coolalien
  • 49
  • 1
  • 1
  • 7
  • This is better answer than original – Vygintas B Jun 23 '17 at 09:56
  • 1
    @VygintasB i've updated code and some fixes will help to get working multi selection in recyclerview, let me know if any other issue faced :) ps: previous i was typing on stackoverflow app due to some code and explaination was missing . – Coolalien Jun 23 '17 at 11:04
2

Try to keep the state in model but in view, and bind the model to view in onBindViewHolder.

kykomi
  • 122
  • 6
0

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);
}
Guanaco Devs
  • 1,822
  • 2
  • 21
  • 38