android - make - recyclerview dependency
Android RecyclerView: notifyDataSetChanged() IllegalStateException (20)
Estoy tratando de actualizar los elementos de una vista de reciclaje usando notifyDataSetChanged ().
Este es mi método onBindViewHolder () en el adaptador de reciclaje.
@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();
}
});
}
Lo que quiero hacer es actualizar los elementos de la lista, después de marcar una casilla de verificación.
Sin embargo, recibo una excepción ilegal:
"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)
También usé notifyItemChanged (), la misma excepción. ¿Alguna forma secreta de actualizar para notificar al adaptador que algo cambió?
¿Por qué no comprobar el estado
RecyclerView.isComputingLayout()
la siguiente manera?
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();
}
}
});
}
}
Al principio pensé que
la respuesta de Moonsoo
(la respuesta aceptada) no funcionaría para mí porque no puedo inicializar mi
setOnCheckedChangeListener()
en el constructor ViewHolder porque necesito vincularlo cada vez para obtener una variable de posición actualizada.
Pero me llevó mucho tiempo darme cuenta de lo que estaba diciendo.
Aquí hay un ejemplo de la "llamada al método circular" de la que está hablando:
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.
}
El único problema con esto es que cuando necesitamos inicializar el interruptor para que se
onBindViewHolder
o desactive (desde el estado guardado en el pasado, por ejemplo), está llamando al oyente, que podría llamar a
nofityItemRangeChanged
que
onBindViewHolder
llamar a
onBindViewHolder
.
No puede llamar a
onBindViewHolder
cuando ya está en
onBindViewHolder
], porque
no puede
notifyItemRangeChanged
si ya está en medio de la notificación de que el rango de elementos ha cambiado.
Pero solo necesitaba actualizar la interfaz de usuario para mostrarla activada o desactivada, no queriendo realmente desencadenar nada.
Aquí está la solución que aprendí de la respuesta de JoniDS que evitará el ciclo infinito. Siempre que configuremos el oyente como "nulo" antes de configurar Checked, actualizará la IU sin activar el oyente, evitando el bucle infinito. Entonces podemos establecer el oyente después.
Código de JoniDS:
holder.checkbox.setOnCheckedChangeListener(null);
holder.checkbox.setChecked(condition);
holder.checkbox.setOnCheckedChangeListener(checkedListener);
Solución completa a mi ejemplo:
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());
}
}
});
}
Antes
notifyDataSetChanged()
acaba de comprobar que con este método:
recyclerView.IsComputingLayout()
Cuando tienes el mensaje de error:
Cannot call this method while RecyclerView is computing a layout or scrolling
Simple, solo haz lo que causa la excepción en:
RecyclerView.post(new Runnable() {
@Override
public void run() {
/**
** Put Your Code here, exemple:
**/
notifyItemChanged(position);
}
});
Debe moverse método ''setOnCheckedChangeListener ()'' a ViewHolder que es clase interna en el adaptador.
onBindViewHolder()
no es un método que inicializa
ViewHolder
.
Este método es el paso de actualizar cada elemento de reciclaje.
Cuando se llama a
notifyDataSetChanged()
,
onBindViewHolder()
se llama como el número de veces cada elemento.
Entonces, si
notifyDataSetChanged()
en
onCheckChanged()
e inicializa checkBox en
onBindViewHolder()
, obtendrá IllegalStateException debido a la llamada al método circular.
haga clic en la casilla de verificación -> onCheckedChanged () -> notifyDataSetChanged () -> onBindViewHolder () -> establezca la casilla de verificación -> onChecked ...
Simplemente, puede solucionar esto colocando una bandera en Adaptador.
prueba esto,
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;
}
El uso de un
Handler
para agregar elementos y llamar a
notify...()
de este
Handler
solucionó el problema por mí.
Encontró una solución 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));
}
});
}
}
Aquí creamos un ejecutable para notificar a ItemChanged para una posición cuando la vista de reciclaje está lista para manejarla.
Esto sucede porque probablemente esté configurando el ''oyente'' antes de configurar el valor para esa fila, lo que hace que el oyente se active cuando ''configura el valor'' para la casilla de verificación.
Lo que debes hacer es:
@Override
public void onBindViewHolder(YourAdapter.ViewHolder viewHolder, int position) {
viewHolder.mCheckBox.setOnCheckedChangeListener(null);
viewHolder.mCheckBox.setChecked(trueOrFalse);
viewHolder.setOnCheckedChangeListener(yourCheckedChangeListener);
}
Me encontré con este problema exacto! Después de que la respuesta de Moonsoo realmente no hizo flotar mi bote, me equivoqué un poco y encontré una solución que funcionó para mí.
En primer lugar, aquí es parte de mi código:
@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());
}
}
});
Notarás que estoy notificando específicamente al adaptador la posición que estoy cambiando, en lugar de todo el conjunto de datos como lo estás haciendo.
Dicho esto, aunque no puedo garantizar que esto funcione para usted, resolví el problema envolviendo mi llamada
notifyItemChanged()
en un bloque try / catch.
¡Esto simplemente captó la excepción, pero aún permitió que mi adaptador registrara el cambio de estado y actualizara la pantalla!
¡Espero que esto ayude a alguien!
EDITAR: Admito que probablemente esta no sea la forma adecuada / madura de manejar el problema, pero dado que no parece estar causando ningún problema al dejar la excepción sin controlar, pensé en compartir en caso de que fuera bueno suficiente para alguien más.
Mientras el administrador de diseño vincula el elemento, es muy probable que esté configurando el estado marcado de su casilla de verificación, que está activando la devolución de llamada.
Por supuesto, esto es una suposición porque no publicó el seguimiento completo de la pila.
No puede cambiar el contenido del adaptador mientras RV está recalculando el diseño.
Puede evitarlo no llamando a notifyDataSetChanged si el estado marcado del elemento es igual al valor enviado en la devolución de llamada (que será el caso si llamar a
checkbox.setChecked
está activando la devolución de llamada).
No sé bien, pero también tenía el mismo problema.
onClickListner
esto usando
onClickListner
en la
checkbox
de
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();
}
});
¡Prueba esto, esto puede ayudar!
Para mí, el problema ocurrió cuando salí de EditText por Listo, Atrás o toque de entrada externo. Esto hace que se actualice el modelo con el texto de entrada, luego se actualiza la vista del reciclador mediante la observación de datos en vivo.
El problema era que el cursor / foco permanecía en EditText.
Cuando he eliminado enfoque mediante el uso de:
editText.clearFocus()
El método de notificación de cambio de datos de la vista del reciclador dejó de arrojar este error.
Creo que esta es una de las posibles razones / soluciones a este problema. Es posible que esta excepción se pueda solucionar de otra manera, ya que puede ser causada por una razón totalmente diferente.
Publicación de uso simple:
new Handler().post(new Runnable() {
@Override
public void run() {
mAdapter.notifyItemChanged(mAdapter.getItemCount() - 1);
}
}
});
Puede reiniciar el escucha anterior antes de realizar cambios y no obtendrá esta excepción.
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);
}
Use onClickListner en la casilla de verificación en lugar de OnCheckedChangeListener, resolverá el problema
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();
}
});
Yo tenía el mismo problema con la casilla de verificación y RadioButton.
Sustituir
notifyDataSetChanged()
con
notifyItemChanged(position)
funcionó.
isChecked
un campo booleano
isChecked
al modelo de datos.
A continuación, he actualizado el valor booleano y en
onCheckedChangedListener
, llamé
notifyItemChanged(adapterPosition)
.
Esto podría no ser la mejor manera, pero funcionó para mí.
El valor booleano se usa para verificar si el elemento está marcado.
simplemente utilizar
isPressed()
método de
CompoundButton
en
onCheckedChanged(CompoundButton compoundButton, boolean isChecked)
p.ej
public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
... //your functionality
if(compoundButton.isPressed()){
notifyDataSetChanged();
}
} });
su elemento CheckBox está en cambio dibujable cuando llama a
notifyDataSetChanged();
entonces esta excepción ocurriría.
Intente llamar a
notifyDataSetChanged();
en post de su vista.
Por ejemplo:
buttonView.post(new Runnable() {
@Override
public void run() {
notifyDataSetChanged();
}
});
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);
}
}
});
}
@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 {
}
}
};
}