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 la manière suivante,

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 complètement pas.

Je vais vous expliquer avec des images.

Par défaut, le premier élément est sélectionné à partir de mon adaptateur

entrer la description de l'image ici

alors j’essaie de sélectionner 2ème et 3ème, puis 4ème et enfin 5ème,

entrer la description de l'image ici

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

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

entrer la description de l'image ici

J’ai ce que j’attendais,

Comment puis-je surmonter ce problème? Et quelque 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); J’ai eu 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) 

Toute solution sera très appréciée.

Merci.

La solution au problème:

 public class yourRecyclerViewAdapter extends RecyclerView.Adapter { 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!

Il est encore trop tard pour poster, cela peut aider quelqu’un d’autre

 /** * Created by subrahmanyam on 28-01-2016, 04:02 PM. */ public class SampleAdapter extends RecyclerView.Adapter { private final Ssortingng[] list; private int lastCheckedPosition = -1; public SampleAdapter(Ssortingng[] 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(); } }); } } } 

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

(1) Les vues sont réutilisées, l’ancien auditeur est toujours présent.

(2) Vous modifiez les données sans avertir l’adaptateur de la modification.

Je vais m’adresser à chacun séparément.

(1) Voir réutiliser

Fondamentalement, dans onBindViewHolder vous onBindViewHolder un ViewHolder déjà initialisé, qui contient déjà une vue. Ce ViewHolder peut ou non avoir été précédemment lié à certaines données!

Notez ce bit de code ici:

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

Si le détenteur a déjà été lié, la case à cocher a déjà un auditeur pour la modification de l’état vérifié! Cet écouteur est déclenché à ce stade, ce qui a provoqué 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 à chercher une meilleure façon de gérer cela.

(2) Avertissez l’adaptateur lorsque les données changent

L’écouteur de votre code modifie l’état des données sans avertir l’adaptateur des 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 a de nombreuses options à choisir, y compris notifyItemChanged , qui lui 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); } 

Cela pourrait aider ceux qui veulent un seul bouton radio pour fonctionner -> Radio Button RecycleView – Gist

Si les expressions lambda ne sont pas sockets 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é

Vous devez effacer le 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, il ne déclenchera pas java.lang.IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling erreur de java.lang.IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling .

il suffit d’utiliser le statut d’enregistrement de mCheckedPosition

 @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(); } }); } 
  public class GetStudentAdapter extends RecyclerView.Adapter { private List 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 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 { } } }; } } 

Cela se produit parce que RecyclerView, comme son nom l’indique, fait du bon travail pour recycler ses ViewHolders. Cela signifie que chaque ViewHolder, quand il est hors de vue (en fait, cela prend un peu plus que de perdre de vue, mais il est logique de le simplifier de cette façon), il est recyclé; Cela implique que RecyclerView prend ce ViewHolder déjà gonflé et remplace ses éléments par les éléments d’un autre élément de votre dataset.

Maintenant, ce qui se passe ici est qu’une fois que vous faites défiler vers le bas et votre premier, sélectionné, ViewHolders disparaît, ils sont recyclés et utilisés pour d’autres positions de votre jeu de données. Une fois que vous montez à nouveau, les ViewHolders 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 le ViewHolder est actuellement lié a été sélectionné ou non, et modifier une vue en conséquence, en l’occurrence l’état de votre RadioButton (même si je vous conseille d’utiliser sélectionner plusieurs éléments).

Si vous voulez en savoir plus sur RecyclerView et son fonctionnement interne, je vous invite à vérifier FancyAdapters , un projet que j’ai lancé sur GitHub. Il s’agit d’une collection d’adaptateurs qui implémentent la sélection , le glisser-déposer d’éléments et les fonctions de balayage . En vérifiant le code, vous pouvez peut-être bien comprendre comment RecyclerView fonctionne.

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

Les éléments suivants pourraient être utiles pour RecyclerView avec Single Choice .

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

 private int mSelectedItem = -1; 

2) dans onBindViewHolder

  mRadio.setChecked(position == mSelectedItem); 

3) dans onClickListener

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

S’il vous plaît essayez ceci … Cela fonctionne pour moi ..

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

 SparseBooleanArray sparseBooleanArray; 

En constructeur initialiser ceci,

  sparseBooleanArray=new SparseBooleanArray(); 

Dans le support de liaison append,

 @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 le bouton radio du fichier xml.

Ainsi, lorsque la vue du recycleur charge les éléments, dans bindView Holder, il vérifie si le sparseBooleanArray contient la valeur “true” correspondant à sa position.

Si la valeur renvoyée est vraie, nous définissons la sélection du bouton radio true.Else, nous définissons la sélection sur false. Dans onclickHolder, j’ai effacé le sparseArray et défini la valeur true correspondant à cette position. Lorsque j’appelle avertir datasetChange, il appelle à nouveau onBindViewHolder et la condition est vérifiée à nouveau. Cela nous permet de sélectionner uniquement une radio particulière.

Je veux partager la même chose que j’ai réalisée, peut-être que cela aidera quelqu’un. Le code ci-dessous provient de l’application pour sélectionner une adresse dans une liste d’adresses affichées dans cardview(cvAddress) , de sorte que cardview(cvAddress) cliquez sur un item(cardview) particulier item(cardview) l’ imageView à l’intérieur de l’élément doit avoir une ressource différente (select / unselect)

  @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 

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

C’est comme ça que ça ressemble

Sélection unique recyclerview meilleur moyen

À 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 signaler (mettre à jour) l’élément sélectionné précédemment et l’élément nouvellement sélectionné

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

       

Voici à quoi ressemble la classe de l’adaptateur:

 public class MyRecyclerViewAdapter extends RecyclerView.Adapter{ Context context; ArrayList routeDetailsFromFirestoreArrayList_; public int lastSelectedPosition=-1; public MyRecyclerViewAdapter(Context context, ArrayList 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 transmis est de MainActivity à l’adaptateur ctor:

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