Implementing multi selection in Android RecyclerVi

2019-05-29 10:34发布

问题:

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


}

回答1:

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.



回答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;
  }


回答3:

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



回答4:

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