Ich versuche, die Elemente einer Recyclingansicht mit notifyDataSetChanged () zu aktualisieren.
Dies ist meine onBindViewHolder () -Methode im Recycleview-Adapter.
@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();
}
});
}
Ich möchte die Listenelemente aktualisieren, nachdem ich ein Kontrollkästchen aktiviert habe. Ich bekomme jedoch eine illegale Ausnahme: "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)
Ich habe auch notifyItemChanged () verwendet, dieselbe Ausnahme. Gibt es eine geheime Methode, um den Adapter zu benachrichtigen, wenn sich etwas geändert hat?
Sie sollten die Methode 'setOnCheckedChangeListener ()' in die ViewHolder-Klasse verschieben, die sich auf Ihrem Adapter befindet.
onBindViewHolder()
ist keine Methode, die ViewHolder
..__ initialisiert. Diese Methode ist ein Schritt zum Aktualisieren jedes Recycler-Elements Wenn Sie notifyDataSetChanged()
aufrufen, wird onBindViewHolder()
als Anzahl der Elemente jedes Mal aufgerufen.
Wenn Sie also notifyDataSetChanged()
in onCheckChanged()
einfügen und checkBox in onBindViewHolder()
initialisieren, erhalten Sie aufgrund eines zirkulären Methodenaufrufs IllegalStateException.
klicken Sie auf die Checkbox -> onCheckedChanged () -> notifyDataSetChanged () -> onBindViewHolder () -> Checkbox setzen -> onChecked ...
Sie können dies einfach beheben, indem Sie ein Flag in den Adapter setzen.
versuche dies,
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;
}
Die Verwendung einer Handler
zum Hinzufügen von Elementen und das Aufrufen von notify...()
aus dieser Handler
hat das Problem für mich behoben.
Sie können den vorherigen Listener einfach zurücksetzen, bevor Sie Änderungen vornehmen. Diese Ausnahme wird nicht angezeigt.
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);
}
Ich weiß es nicht gut, aber ich hatte auch das gleiche Problem. Ich habe das Problem gelöst, indem ich onClickListner
für checkbox
verwendete.
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();
}
});
Versuchen Sie dies, das kann helfen!
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);
}
}
});
}
Eine einfache Lösung gefunden -
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));
}
});
}
}
Hier erstellen wir ein lauffähiges Element, um notifyItemChanged für eine Position zu benachrichtigen, wenn das Recyclerview bereit ist, damit umzugehen.
Wenn Sie den Nachrichtenfehler haben:
Cannot call this method while RecyclerView is computing a layout or scrolling
Einfach, tun Sie einfach, was die Ausnahme verursacht in:
RecyclerView.post(new Runnable() {
@Override
public void run() {
/**
** Put Your Code here, exemple:
**/
notifyItemChanged(position);
}
});
ihr CheckBox-Element kann geändert werden, wenn Sie notifyDataSetChanged();
aufrufen. Diese Ausnahme würde auftreten Versuchen Sie, notifyDataSetChanged();
im Post Ihrer Ansicht aufzurufen. Zum Beispiel:
buttonView.post(new Runnable() {
@Override
public void run() {
notifyDataSetChanged();
}
});
Zuerst dachte ich, Moonsoos Antwort (die akzeptierte Antwort) würde für mich nicht funktionieren, da ich meine setOnCheckedChangeListener()
nicht im ViewHolder-Konstruktor initialisieren kann, da ich sie jedes Mal binden muss, damit eine aktualisierte Positionsvariable angezeigt wird. Aber ich habe lange gebraucht, um zu realisieren, was er sagte.
Hier ist ein Beispiel für den "kreisförmigen Methodenaufruf", über den er spricht:
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.
}
Das einzige Problem dabei ist, dass, wenn der Schalter ein- oder ausgeschaltet werden muss (z. B. aus dem gespeicherten Zustand), der Listener aufgerufen wird, der möglicherweise nofityItemRangeChanged
aufruft, wodurch erneut onBindViewHolder
aufgerufen wird. Sie können onBindViewHolder
nicht aufrufen, wenn Sie sich bereits in onBindViewHolder
befinden, da Sie notifyItemRangeChanged
nicht auswählen können, wenn Sie gerade benachrichtigen, dass sich der Artikelbereich geändert hat. Aber ich musste nur die Benutzeroberfläche aktualisieren, um sie ein- oder auszuschalten, und wollte eigentlich nichts auslösen.
Hier ist die Lösung, die ich aus JoniDSs Antwort gelernt habe, die die Endlosschleife verhindert. Solange wir den Listener auf "null" setzen, bevor wir Checked setzen, wird die Benutzeroberfläche aktualisiert, ohne dass der Listener ausgelöst wird. Dadurch wird die Endlosschleife vermieden. Dann können wir den Listener nach einstellen.
JoniDS-Code:
holder.checkbox.setOnCheckedChangeListener(null);
holder.checkbox.setChecked(condition);
holder.checkbox.setOnCheckedChangeListener(checkedListener);
Volle Lösung zu meinem Beispiel:
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());
}
}
});
}
Während das Element vom Layout-Manager gebunden wird, setzen Sie wahrscheinlich den aktivierten Status Ihres Kontrollkästchens, das den Rückruf auslöst.
Dies ist natürlich eine Vermutung, da Sie nicht die vollständige Stapelverfolgung veröffentlicht haben.
Sie können den Adapterinhalt nicht ändern, während RV das Layout neu berechnet. Sie können dies vermeiden, indem Sie notifyDataSetChanged nicht aufrufen, wenn der geprüfte Status des Elements dem im Callback gesendeten Wert entspricht (was der Fall ist, wenn der Aufruf von checkbox.setChecked
den Callback auslöst).
Verwenden Sie onClickListener im Kontrollkästchen anstelle von OnCheckedChangeListener. Dadurch wird das Problem gelöst
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();
}
});
Warum nicht den RecyclerView.isComputingLayout()
-Status wie folgt überprüfen?
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();
}
}
});
}
}
Bevor Sie notifyDataSetChanged()
überprüfen, überprüfen Sie dies einfach mit dieser Methode: recyclerView.IsComputingLayout()
Einfacher Gebrauch Post:
new Handler().post(new Runnable() {
@Override
public void run() {
mAdapter.notifyItemChanged(mAdapter.getItemCount() - 1);
}
}
});
Ich bin genau auf dieses Thema gestoßen! Nachdem Moonsoos Antwort mein Boot nicht wirklich getrieben hatte, machte ich ein bisschen Unfug und fand eine Lösung, die für mich funktionierte.
Zunächst ein paar von meinem 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());
}
}
});
Sie werden feststellen, dass ich den Adapter speziell für die Position, die ich ändere, benachrichtige, anstatt wie bei der gesamten Datenmenge. Obwohl ich nicht garantieren kann, dass dies für Sie funktionieren wird, habe ich das Problem gelöst, indem Sie meinen Aufruf von notifyItemChanged()
in einen try/catch-Block wickeln. Dies hat einfach die Ausnahme erfasst, aber mein Adapter konnte immer noch die Statusänderung registrieren und die Anzeige aktualisieren!
Hoffe das hilft jemandem!
EDIT: Ich gebe zu, dies ist wahrscheinlich nicht die richtige/ausgereifte Art, mit dem Problem umzugehen, aber da es offensichtlich keine Probleme bereitet, wenn die Ausnahme nicht behandelt wird, dachte ich, ich würde es teilen, falls es gut wäre genug für jemand anderen.
Bei mir trat ein Problem auf, wenn ich EditText durch Fertig, Zurück oder Eingabe außerhalb der Eingabe verlassen habe. Dies führt dazu, dass das Modell mit dem eingegebenen Text aktualisiert wird und die Ansicht des Recyclers mithilfe der Live-Datenüberwachung aktualisiert wird.
Das Problem war, dass der Cursor/Fokus in EditText bleibt.
Wenn ich den Fokus gelöscht habe mit:
editText.clearFocus()
Benachrichtigen Sie die geänderte Methode der Recycler-Ansicht. Dieser Fehler wurde nicht mehr angezeigt.
Ich denke, das ist einer der möglichen Gründe/Lösungen für dieses Problem. Es ist möglich, dass diese Ausnahme auf andere Weise behoben werden kann, da sie aus einem ganz anderen Grund verursacht werden kann.
@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 {
}
}
};
}
Für mich hörte ich die Bewertung einer Bewertungsleiste ändern, aber nach langem Drücken mehrerer Klicks auf einmal stürzte die App aufgrund des Problems ab und fand eine klare Lösung, wenn ich notifydatasetchange () benachrichtigen wollte; in bindviewholder handhabte es mit einem Handler, der gegeben wurde von:
//inside bindViewHolder
new Handler().post(new Runnable() {
@Override
public void run() {
notifyDataSetChanged();
}
});
Ich hoffe, es wird das Problem lösen.
Ich hatte das gleiche Problem mit der Checkbox und dem RadioButton. Das Ersetzen von notifyDataSetChanged()
durch notifyItemChanged(position)
hat funktioniert. Ich habe dem Datenmodell ein Boolesches Feld isChecked
hinzugefügt. Dann habe ich den Booleschen Wert aktualisiert und in onCheckedChangedListener
notifyItemChanged(adapterPosition)
aufgerufen. Dies ist vielleicht nicht der beste Weg, hat aber für mich funktioniert. Der Boolesche Wert wird verwendet, um zu überprüfen, ob das Element überprüft wurde.
verwenden Sie einfach die isPressed()
-Methode von CompoundButton
in onCheckedChanged(CompoundButton compoundButton, boolean isChecked)
z.B
public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
... //your functionality
if(compoundButton.isPressed()){
notifyDataSetChanged();
}
} });
Dies geschieht, weil Sie wahrscheinlich den 'Listener' festgelegt haben, bevor Sie den Wert für diese Zeile konfigurieren. Dadurch wird der Listener ausgelöst, wenn Sie 'den Wert' für das Kontrollkästchen konfigurieren.
Was Sie tun müssen, ist:
@Override
public void onBindViewHolder(YourAdapter.ViewHolder viewHolder, int position) {
viewHolder.mCheckBox.setOnCheckedChangeListener(null);
viewHolder.mCheckBox.setChecked(trueOrFalse);
viewHolder.setOnCheckedChangeListener(yourCheckedChangeListener);
}