Реализация множественного выбора в Android RecyclerView

Мне нужна помощь с выбором Multi/Single. Нашел то, что искал здесь из-за его простоты. Я использую GridLayoutManager. У меня более 90 элементов в моем адаптере, CardView с TextView и ImageView при использовании процедуры, описанной в сообщение.

Когда я выбираю один или несколько элементов при прокрутке вниз, другие элементы «кажутся» выбранными, потому что фон повторяется, но они не выбраны. Пытался поместить setOnClickListener в onBindViewHolder, а также в класс MyViewHolder, и в обоих из них я получаю одинаковое поведение. При прокрутке вниз другие элементы кажутся выбранными. Использовал notifyItemChanged(position) и notifyDataSetChanged() в переходнике, но фон вообще не меняется, хотя setSelected работает исправно. Также я использовал setHasFixedSize(true) в настройке RecyclerView.

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

Модель

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

 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

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


}

person Racu    schedule 23.06.2017    source источник
comment
поставь код первым   -  person Avinash Roy    schedule 23.06.2017
comment
@ Авинаш Рой, готово   -  person Racu    schedule 23.06.2017
comment
Для пользователей Kotlin может быть полезна следующая статья Как удалить несколько записей из Firestore с помощью множественного выбора RecyclerView?.   -  person Alex Mamo    schedule 04.01.2021


Ответы (4)


RecyclerView, как следует из названия, перерабатывает представления. Это означает, что как только вид прокручивается за пределы экрана, его можно использовать повторно.

Перед тем, как представление будет использовано повторно, оно по-прежнему содержит все настройки с момента последнего использования. Например, если он содержит TextView, для свойства Text TextView по-прежнему будет установлено значение, которое было при последнем отображении.

Причина, по которой некоторые элементы «кажутся» выбранными, заключается в том, что ваши выбранные представления, которые прокручивались за пределы экрана, теперь используются повторно, и вы не отменили их выбор.

В вашем методе OnBindViewHolder вам нужно "сбросить" все представления до значений по умолчанию. В этом случае это будет означать «отключение» любого метода, который вы используете, чтобы сделать представление выбранным.

Например:

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

По сути, каждый раз, когда вы выполняете привязку, вы устанавливаете цвет фона в выбранное или невыбранное состояние на основе соответствующего свойства в вашей модели.

person Kuffs    schedule 23.06.2017
comment
Я добавил свой метод setData в сообщение и включил 5-ю строку, которую вы указали в своем коде. Я могу ошибаться, но в коде, который вы указали в onClick, мой шаблон не изменит фон, я полагаю (я новичок). Теперь работает благодаря вашим комментариям, обратите внимание, что это работает с notifyItemChanged(position) или без него. Как я понимаю, моя ошибка заключалась в том, чтобы изначально не предоставлять фон для представления, поскольку фон представления уже был белым (пробовал с Color.RED и Color.BLUE для тестирования) - person Racu; 23.06.2017
comment
Да, приведенный выше код изменит фон. Это то, что делает NotifyItemChanged. Это вызвало повторный вызов OnBindViewHolder для соответствующей позиции, которая затем, в свою очередь, устанавливает цвет фона представления контейнера. - person Kuffs; 23.06.2017
comment
Вы совершенно правы, спасибо за ваше время. У меня была неделя, чтобы понять это. - person Racu; 23.06.2017
comment
Кроме того, я бы переместил некоторый код из setData в метод OnCreateViewHolder. Например, вы устанавливаете шрифт своего TextView каждый раз, когда связываете данные. Вам нужно сделать это только один раз, когда представление создано, так как оно никогда не меняется. Не очень эффективно делать это повторно, когда это не нужно. Вы зря замедляете процесс. - person Kuffs; 23.06.2017
comment
Вау, это отличный бонус, спасибо!! - person Racu; 23.06.2017

Недавно я работал над множественным выбором recyclerview, поэтому вы можете попробовать сначала инициализировать sparseboolean, boolean и int следующим образом:

private SparseBooleanArray storeChecked = new SparseBooleanArray();
private boolean isMultiselect;
private int itemSelected;

поэтому в bindViewHolder добавьте этот

holder.view.setbackground(storechecked.get(position) ? Color.White : Color.Black)

затем реализуйте onLongClickListener. на longClick добавьте это:

if(!ismultiSelect){
  storechecked.put(getAdapterPosition(), true);
  notifyDataSetChanged(getAdapterPosition());
  triggerOnLongClickListener(++itemSelected); // using listerner i've transfer position to fragment for actionmode selected count

}


после этого в onClick сделайте следующее:

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;
  }
person Coolalien    schedule 23.06.2017
comment
Это лучший ответ, чем оригинальный - person Vygintas B; 23.06.2017
comment
@VygintasB Я обновил код, и некоторые исправления помогут заставить работать множественный выбор в recyclerview, дайте мне знать, если возникнут какие-либо другие проблемы :) ps: ранее я печатал в приложении stackoverflow из-за отсутствия некоторого кода и объяснения. - person Coolalien; 23.06.2017

Попытайтесь сохранить состояние в модели, но в представлении, и привяжите модель к представлению в onBindViewHolder.

person kykomi    schedule 23.06.2017

Я попробовал решение @Kuffs и отправляю свой окончательный код для справки.

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() Метод

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);
}
person Racu    schedule 23.06.2017