J'essaie de mettre à jour les éléments d'un recycleview en utilisant notifyDataSetChanged ().
C'est ma méthode onBindViewHolder () dans l'adaptateur recycleview.
@Override
public void onBindViewHolder(ViewHolder viewHolder, int position) {
//checkbox view listener
viewHolder.getCheckbox().setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
//update list items
notifyDataSetChanged();
}
});
}
Ce que je veux faire, c'est mettre à jour les éléments de la liste, après avoir coché une case à cocher. Je reçois une exception illégale cependant: "Cannot call this method while RecyclerView is computing a layout or scrolling"
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)
J'ai également utilisé notifyItemChanged (), même exception. Un moyen secret de mettre à jour pour informer l'adaptateur que quelque chose a changé?
Vous devez déplacer la méthode 'setOnCheckedChangeListener ()' vers ViewHolder, qui est la classe interne de votre adaptateur.
onBindViewHolder()
n’est pas une méthode d’initialisation ViewHolder
. Cette méthode consiste à actualiser chaque élément du recycleur . Lorsque vous appelez notifyDataSetChanged()
, onBindViewHolder()
sera appelé en tant que nombre de fois.
Donc, si vous notifyDataSetChanged()
mettez dans onCheckChanged()
et initialisez checkBox dans onBindViewHolder()
, vous obtiendrez IllegalStateException à cause de l'appel de la méthode circulaire.
cochez la case -> onCheckedChanged () -> notifyDataSetChanged () -> onBindViewHolder () -> cochez la case -> onChecked ...
Simplement, vous pouvez résoudre ce problème en mettant un indicateur dans Adapter.
essaye ça,
private boolean onBind;
public ViewHolder(View itemView) {
super(itemView);
mCheckBox = (CheckBox) itemView.findViewById(R.id.checkboxId);
mCheckBox.setOnCheckChangeListener(this);
}
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if(!onBind) {
// your process when checkBox changed
// ...
notifyDataSetChanged();
}
}
...
@Override
public void onBindViewHolder(YourAdapter.ViewHolder viewHolder, int position) {
// process other views
// ...
onBind = true;
viewHolder.mCheckBox.setChecked(trueOrFalse);
onBind = false;
}
L'utilisation d'une Handler
pour ajouter des éléments et appeler notify...()
à partir de cette Handler
a résolu le problème pour moi.
Vous pouvez simplement réinitialiser l'écouteur précédent avant d'apporter des modifications et vous n'obtiendrez pas cette exception.
private CompoundButton.OnCheckedChangeListener checkedListener = new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
//Do your stuff
});;
@Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
holder.checkbox.setOnCheckedChangeListener(null);
holder.checkbox.setChecked(condition);
holder.checkbox.setOnCheckedChangeListener(checkedListener);
}
Je ne sais pas bien, mais j'ai aussi eu le même problème. J'ai résolu ce problème en utilisant onClickListner
sur checkbox
viewHolder.mCheckBox.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
if (model.isCheckboxBoolean()) {
model.setCheckboxBoolean(false);
viewHolder.mCheckBox.setChecked(false);
} else {
model.setCheckboxBoolean(true);
viewHolder.mCheckBox.setChecked(true);
}
notifyDataSetChanged();
}
});
Essayez ceci, cela peut aider!
protected void postAndNotifyAdapter(final Handler handler, final RecyclerView recyclerView, final RecyclerView.Adapter adapter) {
handler.post(new Runnable() {
@Override
public void run() {
if (!recyclerView.isComputingLayout()) {
adapter.notifyDataSetChanged();
} else {
postAndNotifyAdapter(handler, recyclerView, adapter);
}
}
});
}
Trouvé une solution simple -
public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{
private RecyclerView mRecyclerView;
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
mRecyclerView = recyclerView;
}
private CompoundButton.OnCheckedChangeListener checkedChangeListener
= (compoundButton, b) -> {
final int position = (int) compoundButton.getTag();
// This class is used to make changes to child view
final Event event = mDataset.get(position);
// Update state of checkbox or some other computation which you require
event.state = b;
// we create a runnable and then notify item changed at position, this fix crash
mRecyclerView.post(new Runnable() {
@Override public void run() {
notifyItemChanged(position));
}
});
}
}
Ici, nous créons un runnable à notifyItemChanged pour un poste lorsque recyclerview est prêt à le gérer.
Lorsque vous avez le message d'erreur:
Cannot call this method while RecyclerView is computing a layout or scrolling
Simple, faites ce qui cause l'exception dans:
RecyclerView.post(new Runnable() {
@Override
public void run() {
/**
** Put Your Code here, exemple:
**/
notifyItemChanged(position);
}
});
votre élément CheckBox est en cours de modification lorsque vous appelez notifyDataSetChanged();
afin que cette exception se produise . Essayez d’appeler notifyDataSetChanged();
après votre commentaire. Par exemple:
buttonView.post(new Runnable() {
@Override
public void run() {
notifyDataSetChanged();
}
});
Au début, je pensais que la réponse de Moonsoo (la réponse acceptée) ne fonctionnerait pas pour moi car je ne peux pas initialiser ma setOnCheckedChangeListener()
dans le constructeur ViewHolder car je dois la lier à chaque fois pour obtenir une variable de position mise à jour. Mais il m'a fallu beaucoup de temps pour comprendre ce qu'il disait.
Voici un exemple de "l'appel de méthode circulaire" dont il parle:
public void onBindViewHolder(final ViewHolder holder, final int position) {
SwitchCompat mySwitch = (SwitchCompat) view.findViewById(R.id.switch);
mySwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
data.delete(position);
notifyItemRemoved(position);
//This will call onBindViewHolder, but we can't do that when we are already in onBindViewHolder!
notifyItemRangeChanged(position, data.size());
}
}
});
//Set the switch to how it previously was.
mySwitch.setChecked(savedSwitchState); //If the saved state was "true", then this will trigger the infinite loop.
}
Le seul problème avec cela est que, lorsque nous devons initialiser le commutateur pour qu'il soit activé ou désactivé (à partir de l'état de sauvegarde précédent, par exemple), il appelle le programme d'écoute qui peut appeler nofityItemRangeChanged
qui appelle à nouveau onBindViewHolder
. Vous ne pouvez pas appeler onBindViewHolder
lorsque vous êtes déjà dans onBindViewHolder
], car vous ne pouvez pas notifyItemRangeChanged
si vous êtes déjà en train de notifier que la plage d'éléments a été modifiée. Mais je n'avais besoin que de mettre à jour l'interface utilisateur pour l'activer ou la désactiver, ne voulant rien déclencher.
Voici la solution que j'ai tirée de la réponse de JoniDS qui empêchera la boucle infinie. Tant que nous définissons l'auditeur sur "null" avant de l'activer sur Checked, l'interface utilisateur sera mise à jour sans déclencher l'écouteur, ce qui évite la boucle infinie. Ensuite, nous pouvons définir l'auditeur après.
Code de JoniDS:
holder.checkbox.setOnCheckedChangeListener(null);
holder.checkbox.setChecked(condition);
holder.checkbox.setOnCheckedChangeListener(checkedListener);
Solution complète à mon exemple:
public void onBindViewHolder(final ViewHolder holder, final int position) {
SwitchCompat mySwitch = (SwitchCompat) view.findViewById(R.id.switch);
//Set it to null to erase an existing listener from a recycled view.
mySwitch.setOnCheckedChangeListener(null);
//Set the switch to how it previously was without triggering the listener.
mySwitch.setChecked(savedSwitchState); //If the saved state was "true", then this will trigger the infinite loop.
//Set the listener now.
mySwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
data.delete(position);
notifyItemRemoved(position);
//This will call onBindViewHolder, but we can't do that when we are already in onBindViewHolder!
notifyItemRangeChanged(position, data.size());
}
}
});
}
Tandis que l'élément est lié par le gestionnaire de disposition, il est très probable que vous définissiez l'état coché de votre case à cocher, ce qui déclenche le rappel.
Bien entendu, il s'agit d'une hypothèse, car vous n'avez pas publié la trace complète de la pile.
Vous ne pouvez pas modifier le contenu de l'adaptateur pendant que RV recalcule la mise en page. Vous pouvez l'éviter en n'appelant pas notifyDataSetChanged si l'état coché de l'élément est égal à la valeur envoyée dans le rappel (ce qui sera le cas si l'appel checkbox.setChecked
déclenche le rappel).
Utilisez onClickListener sur la case à cocher au lieu de OnCheckedChangeListener, cela résoudra le problème
viewHolder.myCheckBox.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (viewHolder.myCheckBox.isChecked()) {
// Do something when checkbox is checked
} else {
// Do something when checkbox is unchecked
}
notifyDataSetChanged();
}
});
Pourquoi ne pas vérifier l'état RecyclerView.isComputingLayout()
comme suit?
public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{
private RecyclerView mRecyclerView;
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
mRecyclerView = recyclerView;
}
@Override
public void onBindViewHolder(ViewHolder viewHolder, int position) {
viewHolder.getCheckbox().setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (mRecyclerView != null && !mRecyclerView.isComputingLayout()) {
notifyDataSetChanged();
}
}
});
}
}
Avant notifyDataSetChanged()
, vérifiez simplement avec cette méthode: recyclerView.IsComputingLayout()
Utilisation simple Post:
new Handler().post(new Runnable() {
@Override
public void run() {
mAdapter.notifyItemChanged(mAdapter.getItemCount() - 1);
}
}
});
J'ai rencontré ce problème! Après que la réponse de Moonsoo n'ait pas vraiment fait flotter mon bateau, j'ai un peu déconné et trouvé une solution qui a fonctionné pour moi.
Tout d'abord, voici une partie de mon code:
@Override
public void onBindViewHolder(ViewHolder holder, final int position) {
final Event event = mDataset.get(position);
//
// .......
//
holder.mSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
event.setActive(isChecked);
try {
notifyItemChanged(position);
} catch (Exception e) {
Log.e("onCheckChanged", e.getMessage());
}
}
});
Vous remarquerez que je notifie spécifiquement l'adaptateur pour le poste que je change, au lieu de l'ensemble de données comme vous le faites. Cela étant dit, même si je ne peux pas garantir que cela fonctionnera pour vous, j'ai résolu le problème en encapsulant mon appel notifyItemChanged()
dans un bloc try/catch. Cela a simplement attiré l'exception, mais a néanmoins permis à mon adaptateur d'enregistrer le changement d'état et de mettre à jour l'affichage!
J'espère que cela aide quelqu'un!
EDIT: Je dois admettre que ce n’est probablement pas la manière appropriée/mature de traiter le problème, mais comme cela ne semble pas poser de problèmes en laissant l’exception non gérée, j’ai pensé partager cela au cas où ce serait bien assez pour quelqu'un d'autre.
Pour moi, le problème est survenu lorsque je suis sorti de EditText par Terminé, Retour, ou une entrée extérieure tactile. Ceci a pour effet de mettre à jour le modèle avec le texte d'entrée, puis d'actualiser la vue du recycleur via l'observation en direct des données.
Le problème était que le curseur/focus reste dans EditText.
Quand j'ai supprimé le focus en utilisant:
editText.clearFocus()
Notifier que les données ont changé. La méthode d'affichage du recycleur a effectivement cessé de générer cette erreur.
Je pense que c'est l'une des raisons possibles/solutions à ce problème. Il est possible que cette exception puisse être corrigée d’une autre manière, car elle peut être provoquée par une raison totalement différente.
@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 {
}
}
};
}
Pour moi, j’écoutais le changement de classement d’une barre d’évaluation, mais après une pression longue de plusieurs clics, une application s’est effondrée à cause du problème, puis une solution claire a été trouvée si je voulais notifydatasetchange (); dans bindviewholder manipulé avec handler donné par:
//inside bindViewHolder
new Handler().post(new Runnable() {
@Override
public void run() {
notifyDataSetChanged();
}
});
J'espère que cela résoudra le problème.
J'ai eu le même problème en utilisant la case à cocher et le RadioButton. Remplacer notifyDataSetChanged()
par notifyItemChanged(position)
a fonctionné. J'ai ajouté un champ booléen isChecked
au modèle de données. Ensuite, j'ai mis à jour la valeur booléenne et dans onCheckedChangedListener
, j'ai appelé notifyItemChanged(adapterPosition)
. Ce n'est peut-être pas la meilleure façon, mais a fonctionné pour moi. La valeur booléenne est utilisée pour vérifier si l'élément est coché.
utilisez simplement la méthode isPressed()
de CompoundButton
dans onCheckedChanged(CompoundButton compoundButton, boolean isChecked)
par exemple
public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
... //your functionality
if(compoundButton.isPressed()){
notifyDataSetChanged();
}
} });
Cela se produit car vous définissez probablement le "écouteur" avant de configurer la valeur de cette ligne, ce qui oblige l'écouteur à se déclencher lorsque vous "configurez la valeur" pour la case à cocher.
Ce que vous devez faire c'est:
@Override
public void onBindViewHolder(YourAdapter.ViewHolder viewHolder, int position) {
viewHolder.mCheckBox.setOnCheckedChangeListener(null);
viewHolder.mCheckBox.setChecked(trueOrFalse);
viewHolder.setOnCheckedChangeListener(yourCheckedChangeListener);
}