android - limpiar - recyclerview adapter notifydatasetchanged
RecyclerView y actualizaciones de datos del adaptador (4)
Esta es una pregunta sobre el comportamiento interno de RecyclerView para alguien que conoce su mecánica o está dispuesto a profundizar en el código fuente. Me gustaría una respuesta respaldada por referencias a la fuente.
Pregunta original
(desplácese hacia abajo hasta ''En otras palabras'' para una pregunta más enfocada)
Necesito comprender cómo se notifyItemInserted()
acciones de notify*
(por ejemplo, notifyItemInserted()
). Imagina que tengo un adaptador respaldado por esta lista:
ArrayList<String> list = Arrays.asList("one", "three", "four");
Quiero agregar los valores zero
y two
, que faltan.
Ejemplo 1
list.add(1, "two");
// notify the view
adapter.notifyItemInserted(1);
// Seconds later, I go on with zero
list.add(0, "zero");
// notify the view
adapter.notifyItemInserted(0);
Esto es bastante sencillo y claro, nada que decir.
Ejemplo 2
Pero, ¿qué sucede si las dos acciones están muy cerca unas de otras y no hay un paso de diseño intermedio?
list.add(1, "two");
list.add(0, "zero”);
¿Qué debería hacer ahora?
adapter.notifyItemInserted(1);
adapter.notifyItemInserted(0);
O tal vez
adapter.notifyItemInserted(2);
adapter.notifyItemInserted(0);
? Desde la perspectiva del adaptador, la lista cambió inmediatamente de one, three, four
a zero, one, two, three, four
por lo que la segunda opción parece más razonable.
Ejemplo 3
list.add(0, “zero”);
adapter.notifyItemInserted(0);
list.add(2, “two”);
adapter.notifyItemInserted(...)
¿Qué pasa ahora? 1
o 2
? La lista se actualizó inmediatamente después, pero estoy seguro de que no hubo un pase de diseño intermedio.
Pregunta
Tiene el problema principal y quiero saber cómo debo comportarme en estas situaciones. El caso real es que tengo varias tareas asíncronas que terminan en un método insert()
. Puedo encolar sus operaciones, pero:
- No quiero hacer eso si ya hay una cola interna, y seguramente hay
- No sé qué sucede si ocurren dos acciones sin un pase de diseño intermedio, consulte el Ejemplo 3.
En otras palabras
Para actualizar el reciclador, deben ocurrir 4 acciones:
- Realmente modifico el modelo de datos (por ejemplo, inserte algo en la matriz de respaldo)
- Llamo a
adapter.notify*()
- Recicladora recibe la llamada.
- El reciclador realiza la acción (por ejemplo, llama a
getItem*()
yonBind()
en el adaptador) y establece el cambio.
Es fácil entender esto cuando no hay concurrencia, y suceden en secuencia:
1. => 2. => 3. => 4. => (new update) 1. => 2. => 3. => 4. ...
Vamos a ver qué pasa entre los pasos.
- Entre 1. y 2 .: Yo diría que es responsabilidad del desarrollador llamar a notificar () inmediatamente después de haber modificado los datos. Está bien.
- Entre 2. y 3 .: Esto sucede de inmediato, no hay problema aquí.
- Entre 3. y 4 .: ¡Esto no sucede de inmediato! HASTA DONDE SE. Por lo tanto, es perfectamente posible que una nueva actualización (pasos 1 y 2 ) se encuentre entre los pasos 3 y 4 de la actualización anterior .
Quiero entender lo que pasa en este caso. ¿Cómo debemos comportarnos? ¿Debo asegurarme de que el paso 4 de la actualización anterior tuvo lugar antes de insertar nuevas cosas? ¿Si es así, cómo?
Así que comencemos desde una pequeña introducción a RecyclerView que funciona con elementos de notificación. Y funciona bastante simple con otra lista de elementos de ViewGroup guardados (ListView para ej.)
RecyclerView tiene Cola de elementos de vista que ya están dibujados. Y no conoce ninguna de sus actualizaciones, sin llamar notify(...)
métodos de notify(...)
. Cuando agrega elementos nuevos y notifica a RecyclerView, comienza el ciclo para verificar todas las vistas una por una.
RecyclerView contains and drawn next objects
View view-0 (position 0), view-1 (position 1), View-2 (position 2)
// Here is changes after updating
You added Item View view-new into (position 1) and Notify
RecyclerView starts loop to check changes
RecyclerView received unmodified view-0(position-0) and left them;
RecyclerView found new item view-new(position 1)
RecyclerView removing old item view-1(position 1)
RecyclerView drawing new item view-new(position 1)
// In RecyclerView queue in position-2 was item view-2,
// But now we replacing previous item to this position
RecyclerView found new item view-1 (new position-2)
RecyclerView removing old item view-2(position 2)
RecyclerView drawing new item view-1(position 2)
// And again same behavior
RecyclerView found new item view-3 (new position-3)
RecyclerView drawing new item view-1(position 2)
// And after all changes new RecyclerView would be
RecyclerView contains and drawn next objects
View view-0 (position 0), view-new (position 1) view-1 (position 2), View-2 (position 3)
Es solo el flujo principal de las funciones de notificación de trabajo, pero lo que debería saber todas estas acciones ocurre en el UI Thread, Main Thread, incluso si puede llamar a la actualización desde Async Tasks. Y respondiendo a su pregunta 2: puede llamar a Notify to the RecyclerView todo lo que desee, y asegúrese de que la acción esté en la cola correcta.
RecyclerView funciona correctamente en cualquier uso, las preguntas más complicadas serían para su trabajo Adaptador. En primer lugar, debe sincronizar la acción del adaptador, como agregar elementos eliminados y rechazar totalmente el uso del índice. Por ejemplo, sería mejor para tu ejemplo 3
Item firstItem = new Item(0, “zero”);
list.add(firstItem);
adapter.notifyItemInserted(list.indexOf(firstItem));
//Other action...
Item nextItem = new Item(2, “two”);
list.add(nextItem);
adapter.notifyItemInserted(list.indexOf(nextItem))
//Other actions
Actualización |
Relacionado con RecyclerView.Adapter Doc , donde puede ver las mismas funciones con notifyDataSetChanged()
. Y donde este RecyclerView.Adapter
invoca elementos secundarios con las extensiones android.database.Observable
, consulte más acerca de Observable . El acceso a este titular observable está sincronizado, hasta el uso del elemento View en RecyclerView.
Vea también RecyclerView de la biblioteca de soporte, versión 25.0, líneas 9934 - 9988;
No debería ser un problema si realiza varias actualizaciones entre los pases de diseño. RecyclerView
está diseñado para manejar (y optimizar) este caso:
RecyclerView introduce un nivel adicional de abstracción entre RecyclerView.Adapter y RecyclerView.LayoutManager para poder detectar cambios de conjuntos de datos en lotes durante un cálculo de diseño. [...] Hay dos tipos de métodos relacionados con la posición en RecyclerView:
- Posición de diseño: posición de un elemento en el último cálculo de diseño. Esta es la posición desde la perspectiva del LayoutManager.
- Posición del adaptador: Posición de un elemento en el adaptador. Esta es la posición desde la perspectiva del Adaptador.
Estas dos posiciones son las mismas, excepto el tiempo entre el envío de adapter.notify * events y el cálculo del diseño actualizado .
En tu caso los pasos son:
Actualizas la capa de datos.
adapter.notify*()
aadapter.notify*()
El recyclerview registra el cambio (en
AdapterHelper.mPendingUpdates
si entiendo el código correctamente). Este cambio se reflejará en ViewHolder.getAdapterPosition() , pero aún no en ViewHolder.getLayoutPosition() .- En algún punto, el recyclerView aplica los cambios registrados, básicamente concilia el punto de vista del diseño con el punto de vista del adaptador. Parece que esto puede suceder antes del pase de diseño.
La secuencia 1. , 2. , 3. puede suceder cualquier número de veces, siempre que 2. siga inmediatamente 1. (y ambas suceden en el hilo principal).
(1. => 2. => 3.) ... (1. => 2. => 3.) ... 4.
Pensé en preguntas similares antes, y decidí:
Si quiero insertar más de 1 elemento directamente al final de la lista y quiero obtener una animación para todos, debería:
list.add("0"); list.add("1"); adapter.notifyItemRangeInserted(5, 2); // Suppose there were 5 items before so "0" has index of 5 and we want to insert 2 items.
Si quiero insertar más de 1 elemento directamente al final de la lista, pero quiero obtener una animación separada para cada elemento insertado, debería:
list.add("0"); list.add("1"); adapter.notifyItemInserted(0); mRecyclerView.postDelayed(new Runnable() { @Override public void run() { // before this happens, Be careful to call other notify* methods. Never call notifyDataSetChanged. adapter.notifyItemInserted(1); } }, mRecyclerView.getItemAnimator().getAddDuration());
- Si quiero insertar más de 1 elemento en una posición diferente de la lista, similar a 2.
Espero que esto pueda ayudar.
Item firstItem = new Item(0, “zero”);
list.add(firstItem);
adapter.notifyItemInserted(list.indexOf(firstItem));
//Other action...
Item nextItem = new Item(2, “two”);
list.add(nextItem);
adapter.notifyItemInserted(list.indexOf(nextItem))