web-dev-qa-db-fra.com

Sélection unique dans RecyclerView

Je sais qu'il n'y a pas de méthodes de sélection par défaut dans la classe recyclerview, mais j'ai essayé de suivre,

public void onBindViewHolder(ViewHolder holder, final int position) {
    holder.mTextView.setText(fonts.get(position).getName());
    holder.checkBox.setChecked(fonts.get(position).isSelected());

    holder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
            if(isChecked) {
                for (int i = 0; i < fonts.size(); i++) {
                    fonts.get(i).setSelected(false);
                }
                fonts.get(position).setSelected(isChecked);
            }
        }
    });
}

En essayant ce code, j'ai eu la sortie attendue, mais pas du tout. 

Je vais expliquer cela avec des images. 

Par défaut, le premier élément est sélectionné dans mon adaptateur.

enter image description here

Ensuite, j'essaie de sélectionner le 2e, puis le 3e, puis le 4e et enfin le 5e

enter image description here

Ici, 5ème seulement devrait être sélectionné, mais tous les cinq sont sélectionnés.

Si je fais défiler la liste vers le bas et reviens en haut,

enter image description here

J'ai eu ce que j'attendais,

Comment puis-je surmonter ce problème? Et un peu de temps Si je fais défiler la liste très rapidement, un autre élément est sélectionné. Comment surmonter ce problème aussi?

Mettre à jour

Pendant que j'essaie d'utiliser notifyDataSetChanged() après fonts.get(position).setSelected(isChecked);, je suis l'exception suivante,

Java.lang.IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling
        at Android.support.v7.widget.RecyclerView.assertNotInLayoutOrScroll(RecyclerView.Java:1462)
        at Android.support.v7.widget.RecyclerView$RecyclerViewDataObserver.onChanged(RecyclerView.Java:2982)
        at Android.support.v7.widget.RecyclerView$AdapterDataObservable.notifyChanged(RecyclerView.Java:7493)
        at Android.support.v7.widget.RecyclerView$Adapter.notifyDataSetChanged(RecyclerView.Java:4338)
        at com.app.myapp.screens.RecycleAdapter.onRowSelect(RecycleAdapter.Java:111)
53
Gunaseelan

La solution au problème:

public class yourRecyclerViewAdapter extends RecyclerView.Adapter<yourRecyclerViewAdapter.yourViewHolder> {

private static CheckBox lastChecked = null;
private static int lastCheckedPos = 0;

         ... ...

public void onBindViewHolder(ViewHolder holder, final int position) {

    holder.mTextView.setText(fonts.get(position).getName());
    holder.checkBox.setChecked(fonts.get(position).isSelected());
    holder.checkBox.setTag(new Integer(position));

    //for default check in first item
    if(position == 0 && fonts.get(0).isSelected() && holder.checkBox.isChecked())
    {
       lastChecked = holder.checkBox;
       lastCheckedPos = 0;
    }

    holder.checkBox.setOnClickListener(new View.OnClickListener() 
    {
        @Override
        public void onClick(View v) 
        {
           CheckBox cb = (CheckBox)v;
           int clickedPos = ((Integer)cb.getTag()).intValue(); 

           if(cb.isChecked)
           {
              if(lastChecked != null)
              {
                  lastChecked.setChecked(false);
                  fonts.get(lastCheckedPos).setSelected(false);
              }                       

              lastChecked = cb;
              lastCheckedPos = clickedPos;
          }
          else
             lastChecked = null;

          fonts.get(clickedPos).setSelected(cb.isChecked);
       }
   });
}
       ... ... 
}

J'espère que cette aide!

84
Xcihnegn

Il est trop tard pour le poster, cela peut aider quelqu'un d'autre Utilisez le code ci-dessous comme référence pour vérifier un élément dans RecyclerView

/**
 * Created by subrahmanyam on 28-01-2016, 04:02 PM.
 */
public class SampleAdapter extends RecyclerView.Adapter<SampleAdapter.ViewHolder> {

    private final String[] list;
    private int lastCheckedPosition = -1;

    public SampleAdapter(String[] list) {
        this.list = list;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = View.inflate(parent.getContext(), R.layout.sample_layout, null);
        ViewHolder holder = new ViewHolder(view);
        return holder;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        holder.choiceName.setText(list[position]);
        holder.radioButton.setChecked(position == lastCheckedPosition);
    }

    @Override
    public int getItemCount() {
        return list.length;
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        @Bind(R.id.choice_name)
        TextView choiceName;
        @Bind(R.id.choice_select)
        RadioButton radioButton;

        public ViewHolder(View itemView) {
            super(itemView);
            ButterKnife.bind(this, itemView);
            radioButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    lastCheckedPosition = getAdapterPosition();
 //because of this blinking problem occurs so 
 //i have a suggestion to add notifyDataSetChanged();
                 //   notifyItemRangeChanged(0, list.length);//blink list problem
                      notifyDataSetChanged();

                }
            });
        }
    }
}
66

On dirait qu'il y a deux choses en jeu ici: 

(1) Les vues sont réutilisées, l'ancien écouteur est donc toujours présent.

(2) Vous modifiez les données sans notifier le changement à l'adaptateur.

Je vais aborder chacun séparément.

(1) Voir réutilisation

En gros, dans onBindViewHolder, vous recevez une ViewHolder déjà initialisée, qui contient déjà une vue. Cette ViewHolder peut ou peut ne pas avoir déjà été liée à certaines données!

Notez ce morceau de code ici:

holder.checkBox.setChecked(fonts.get(position).isSelected());

Si le titulaire a déjà été lié, la case à cocher contient déjà un écouteur pour le moment où l'état coché change! Cet auditeur est en train de se déclencher à ce stade, ce qui était à l'origine de votre IllegalStateException

Une solution simple serait de supprimer l’auditeur avant d’appeler setChecked. Une solution élégante nécessiterait une meilleure connaissance de vos points de vue - je vous encourage à rechercher un moyen plus agréable de gérer cela.

(2) Avertir l'adaptateur lorsque les données changent

L'écouteur de votre code modifie l'état des données sans notifier à l'adaptateur les modifications ultérieures. Je ne sais pas comment vos points de vue fonctionnent, donc cela peut ne pas être un problème. Généralement, lorsque l'état de vos données change, vous devez en informer l'adaptateur.

RecyclerView.Adapter propose de nombreuses options, dont notifyItemChanged , qui indique qu'un élément particulier a changé d'état. Cela pourrait être bon pour votre usage 

if(isChecked) {
    for (int i = 0; i < fonts.size(); i++) {
        if (i == position) continue;
        Font f = fonts.get(i);
        if (f.isSelected()) {
            f.setSelected(false);
            notifyItemChanged(i); // Tell the adapter this item is updated
        }
    }
    fonts.get(position).setSelected(isChecked);
    notifyItemChanged(position);
}
12
William

utilisez simplement mCheckedPosition save status

@Override
        public void onBindViewHolder(ViewHolder holder, int position) {

            holder.checkBox.setChecked(position == mCheckedPostion);
            holder.checkBox.setOnClickListener(v -> {
                if (position == mCheckedPostion) {
                    holder.checkBox.setChecked(false);
                    mCheckedPostion = -1;
                } else {
                    mCheckedPostion = position;
                    notifyDataSetChanged();
                }
            });
        }
7
user5410364

Vous devez effacer la OnCheckedChangeListener avant de définir setChecked():

@Override
public void onBindViewHolder(final ViewHolder holder, int position) {

    holder.mRadioButton.setOnCheckedChangeListener(null);
    holder.mRadioButton.setChecked(position == mCheckedPosition);
    holder.mRadioButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {

            mCheckedPosition = position;
            notifyDataSetChanged();
        }
    });
}

De cette façon, l'erreur Java.lang.IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling ne sera pas déclenchée.

7
Henrique de Sousa

Cela pourrait aider ceux qui souhaitent utiliser un seul bouton radio -> Radio Button RecycleView - Gist

Si les expressions lambda ne sont pas prises en charge, utilisez plutôt ceci:

View.OnClickListener listener = new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        notifyItemChanged(mSelectedItem); // to update last selected item.
        mSelectedItem = getAdapterPosition();
    }
};

À votre santé

7
Ismail Iqbal
            public class GetStudentAdapter extends 
            RecyclerView.Adapter<GetStudentAdapter.MyViewHolder> {

            private List<GetStudentModel> getStudentList;
            Context context;
            RecyclerView recyclerView;

            public class MyViewHolder extends RecyclerView.ViewHolder {
                TextView textStudentName;
                RadioButton rbSelect;

                public MyViewHolder(View view) {
                    super(view);
                    textStudentName = (TextView) view.findViewById(R.id.textStudentName);
                    rbSelect = (RadioButton) view.findViewById(R.id.rbSelect);
                }


            }

            public GetStudentAdapter(Context context, RecyclerView recyclerView, List<GetStudentModel> getStudentList) {
                this.getStudentList = getStudentList;
                this.recyclerView = recyclerView;
                this.context = context;
            }

            @Override
            public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
                View itemView = LayoutInflater.from(parent.getContext())
                        .inflate(R.layout.select_student_list_item, parent, false);
                return new MyViewHolder(itemView);
            }

            @Override
            public void onBindViewHolder(final MyViewHolder holder, final int position) {
                holder.textStudentName.setText(getStudentList.get(position).getName());
                holder.rbSelect.setChecked(getStudentList.get(position).isSelected());
                holder.rbSelect.setTag(position); // This line is important.
                holder.rbSelect.setOnClickListener(onStateChangedListener(holder.rbSelect, position));

            }

            @Override
            public int getItemCount() {
                return getStudentList.size();
            }
            private View.OnClickListener onStateChangedListener(final RadioButton checkBox, final int position) {
                return new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        if (checkBox.isChecked()) {
                            for (int i = 0; i < getStudentList.size(); i++) {

                                getStudentList.get(i).setSelected(false);

                            }
                            getStudentList.get(position).setSelected(checkBox.isChecked());

                            notifyDataSetChanged();
                        } else {

                        }

                    }
                };
            }

        }
2

Cela se produit parce que RecyclerView, comme son nom l'indique, fait du bon travail sur recycling ses ViewHolders. Cela signifie que chaque ViewHolder, lorsqu'il disparaît (en réalité, il en faut un peu plus que disparaître, mais il est logique de le simplifier de cette façon), il est recyclé; cela implique que RecyclerView utilise ce ViewHolder déjà gonflé et remplace ses éléments par les éléments d'un autre élément de votre ensemble de données.

Maintenant, ce qui se passe ici, c'est qu'une fois que vous avez fait défiler l'écran vers le bas et que votre premier ViewHolders sélectionné est perdu de vue, il est recyclé et utilisé pour d'autres positions de votre ensemble de données. Une fois que vous remontez, les ViewHolders qui étaient liés aux 5 premiers éléments ne sont pas nécessairement les mêmes, maintenant.

C'est pourquoi vous devez conserver une variable interne dans votre adaptateur qui mémorise l'état de sélection de chaque élément. De cette façon, dans la méthode onBindViewHolder, vous pouvez savoir si l'élément dont ViewHolder est actuellement lié est sélectionné ou non, et modifier une vue en conséquence, dans ce cas l'état de votre radioButton sélection de plusieurs éléments).

Si vous souhaitez en savoir plus sur RecyclerView et son fonctionnement interne, je vous invite à consulter FancyAdapters , un projet que j'ai commencé sur GitHub. C'est un ensemble d'adaptateurs implémentant selection, drag & drop d'éléments et swipe pour ignorer les fonctionnalités. En vérifiant le code, vous pourrez peut-être bien comprendre le fonctionnement de RecyclerView.

1
MikiLoz

Voici à quoi ça ressemble

 Single selection recyclerview best way

À l'intérieur de votre adaptateur

private int selectedPosition = -1;

Et onBindViewHolder

@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {

    if (selectedPosition == position) {
        holder.itemView.setSelected(true); //using selector drawable
        holder.tvText.setTextColor(ContextCompat.getColor(holder.tvText.getContext(),R.color.white));
    } else {
        holder.itemView.setSelected(false);
        holder.tvText.setTextColor(ContextCompat.getColor(holder.tvText.getContext(),R.color.black));
    }

    holder.itemView.setOnClickListener(v -> {
        if (selectedPosition >= 0)
            notifyItemChanged(selectedPosition);
        selectedPosition = holder.getAdapterPosition();
        notifyItemChanged(selectedPosition);
    });
}

C'est tout! Comme vous pouvez le voir, je ne fais que Notification (mise à jour) élément sélectionné précédemment et élément récemment sélectionné

My Drawable le définit comme arrière-plan pour les vues enfant recyclerview

<selector xmlns:Android="http://schemas.Android.com/apk/res/Android">
<item Android:state_focused="false" Android:state_selected="true">
    <shape Android:shape="rectangle">
        <solid Android:color="@color/blue" />
    </shape>
</item>

1
Aklesh Singh

Ce simple a fonctionné pour moi

private RadioButton lastCheckedRB = null;
...
@Override
public void onBindViewHolder(final CoachListViewHolder holder, final int position) {
  holder.priceRadioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
    @Override
    public void onCheckedChanged(RadioGroup group, int checkedId) {
        RadioButton checked_rb = (RadioButton) group.findViewById(checkedId);
        if (lastCheckedRB != null) {
            lastCheckedRB.setChecked(false);
        }
        //store the clicked radiobutton
        lastCheckedRB = checked_rb;
    }
});
1
suku

Ce qui suit pourrait être utile pour RecyclerView avec Single Choice.

Trois étapes pour le faire, 1) Déclarer une variable entière globale,

private int mSelectedItem = -1;

2) en onBindViewHolder

 mRadio.setChecked(position == mSelectedItem);

3) en onClickListener

mSelectedItem = getAdapterPosition();
notifyItemRangeChanged(0, mSingleCheckList.size());
mAdapter.onItemHolderClick(SingleCheckViewHolder.this);
1
Nanda Gopal

Voici à quoi ressemble la classe Adapter: 

public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewHolder>{

Context context;
ArrayList<RouteDetailsFromFirestore> routeDetailsFromFirestoreArrayList_;
public int  lastSelectedPosition=-1;


public MyRecyclerViewAdapter(Context context, ArrayList<RouteDetailsFromFirestore> routeDetailsFromFirestoreArrayList)
{
    this.context = context;
    this.routeDetailsFromFirestoreArrayList_ = routeDetailsFromFirestoreArrayList;
}

@NonNull
@Override
public MyRecyclerViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i)
{
    // LayoutInflater layoutInflater = LayoutInflater.from(mainActivity_.getBaseContext());
    LayoutInflater layoutInflater = LayoutInflater.from(viewGroup.getContext());
    View view = layoutInflater.inflate(R.layout.route_details, viewGroup, false);
    return new MyRecyclerViewHolder(view);
}

@Override
public void onBindViewHolder(@NonNull final MyRecyclerViewHolder myRecyclerViewHolder, final int i) {

    /* This is the part where the appropriate checking and unchecking of radio button happens appropriately */
    myRecyclerViewHolder.mRadioButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
            if(b) {
                if (lastSelectedPosition != -1) {
     /* Getting the reference to the previously checked radio button and then unchecking it.lastSelectedPosition has the index of the previously selected radioButton */  
                    //RadioButton rb = (RadioButton) ((MainActivity) context).linearLayoutManager.getChildAt(lastSelectedPosition).findViewById(R.id.rbRadioButton);
                    RadioButton rb = (RadioButton) ((MainActivity) myRecyclerViewHolder.mRadioButton.getContext()).linearLayoutManager.getChildAt(lastSelectedPosition).findViewById(R.id.rbRadioButton);
                    rb.setChecked(false);
                }
                    lastSelectedPosition = i;
                    /* Checking the currently selected radio button */
                    myRecyclerViewHolder.mRadioButton.setChecked(true);
                }
        }
    });
}

@Override
public int getItemCount() {
    return routeDetailsFromFirestoreArrayList_.size();
}
} // End of Adapter Class

Dans MainActivity.Java, nous appelons le ctor de la classe Adapter comme ceci. Le contexte passé est de MainActivity à l'adaptateur ctor: 

myRecyclerViewAdapter = new MyRecyclerViewAdapter(MainActivity.this, routeDetailsList);
1
Parag Jain

Je souhaite partager la même chose que j’ai réalisée: cela aiderait peut-être .. .. ci-dessous le code provient de l’application pour sélectionner une adresse dans une liste d’adresses affichées dans cardview(cvAddress), de sorte qu’au clic de item(cardview) la imageView à l'intérieur de l'élément doit être défini sur une ressource différente (sélectionner/désélectionner) 

    @Override
public void onBindViewHolder(final AddressHolder holder, final int position)
{
    holderList.add(holder);
    holder.tvAddress.setText(addresses.get(position).getAddress());
    holder.cvAddress.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            selectCurrItem(position);
        }
    });
}

private void selectCurrItem(int position)
{
    int size = holderList.size();
    for(int i = 0; i<size; i++)
    {
        if(i==position)
            holderList.get(i).ivSelect.setImageResource(R.drawable.select);
        else
            holderList.get(i).ivSelect.setImageResource(R.drawable.unselect);
    }
}

Je ne sais pas si c'est la meilleure solution ou pas mais cela a fonctionné pour moi.

1
Gentle

Donc, après y avoir passé tant de jours, voici ce que j’ai trouvé qui a fonctionné pour moi et qui est une bonne pratique aussi,

  1. Créez une interface, nommez-la un écouteur: SomeSelectedListener. 
  2. Ajoutez une méthode qui prend un entier: void onSelect (int position);
  3. Initialisez l'écouteur dans le constructeur de l'adaptateur de recycleur en tant que: a) commencez par déclarer globalement comme: écouteur privé SomeSelectedListener b), puis dans le constructeur, initialisez comme suit: this.listener = écouteur;
  4. À l'intérieur de onClick () de la case à cocher à l'intérieur de onBindViewHolder (): met à jour la méthode de l'interface/écouteur en transmettant la position sous la forme: listener.onSelect (position)
  5. Dans le modèle, ajoutez une variable pour désélectionnez disons, mSelectedConstant et initialisez-la à 0. Cela représente l'état par défaut lorsque rien n'est sélectionné.
  6. Ajoutez un getter et un setter pour le mSelectedConstant dans le même modèle.
  7. Maintenant, allez dans votre fragment/activité et implémentez l'interface d'écoute. Puis substituez sa méthode: onSelect (int position). Dans cette méthode, parcourez votre liste que vous transmettez à votre adaptateur à l'aide d'une boucle for et définissezSelectedConstant sur 0 pour tous: 

code 

@Override
public void onTicketSelect(int position) {
for (ListType listName : list) {
    listName.setmSelectedConstant(0);
}

8. En dehors de cela, réglez la position sélectionnée sur 1: 

code

list.get(position).setmSelectedConstant(1);
  1. Avertissez cette modification en appelant: adapter.notifyDataSetChanged(); immédiatement après.
  2. Dernière étape: revenez sur votre adaptateur et mettez à jour à l'intérieur de onBindViewHolder () après avoir ajouté le code pour mettre à jour l'état de la case à cocher, 

code 

if (listVarInAdapter.get(position).getmSelectedConstant() == 1) {
    holder.checkIcon.setChecked(true);
    selectedTicketType = dataSetList.get(position);} 
else {
    commonHolder.checkCircularIcon.setChecked(false);
}
0
Meghna Basutkar

S'il te plaît, essaie ça… .. Ça marche pour moi….

Dans l'adaptateur, prenez un tableau booléen clairsemé.

SparseBooleanArray sparseBooleanArray;

En constructeur initialiser cela,

   sparseBooleanArray=new SparseBooleanArray();

Dans le support de reliure ajouter,

@Override
public void onBindViewHolder(DispositionViewHolder holder, final int position) {
   holder.tv_disposition.setText(dispList.get(position).getName());
    if(sparseBooleanArray.get(position,false))
    {
        holder.rd_disp.setChecked(true);
    }
    else
    {
        holder.rd_disp.setChecked(false);


    }
    setClickListner(holder,position);
}

private void setClickListner(final DispositionViewHolder holder, final int position) {
    holder.rd_disp.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            sparseBooleanArray.clear();
            sparseBooleanArray.put(position, true);
            notifyDataSetChanged();


        }
    });
}

rd_disp est un bouton radio dans un fichier XML.

Ainsi, lorsque la vue Recycler charge les éléments, dans bindView Holder, elle vérifie si sparseBooleanArray contient la valeur "true" correspondant à sa position.

Si la valeur renvoyée est true, nous définissons la sélection du bouton radio sur true.Else Nous définissons la sélection sur false . call notify datasetChangez-le à nouveau, appelez onBindViewHolder et la condition sera vérifiée à nouveau. Cela rend notre sélection pour ne sélectionner que la radio particulière.

0
Anshul Aggarwal