5

Resumen: Intentando agregar dinámicamente filas de encabezado a un ListView a través de un envoltorio de adaptador personalizado. ListView tiene problemas para mantener sincronizada la posición de desplazamiento. Proyecto demo ejecutable proporcionado.Cómo reparar los problemas de notifyDataSetChanged/ListView en la envoltura del adaptador dinámico Android

Me gustaría agregar elementos dinámicamente a una lista basada en los valores en un CursorAdapter, varias posiciones antes de lo que el usuario está viendo actualmente. Para hacer esto, tengo un adaptador que envuelve CursorAdapter y mantiene el nuevo contenido indexado en un SparseArray. El ListView necesita ser actualizado cuando los artículos se agregan al adaptador personalizado, pero he encontrado una gran cantidad de trampas tratando de hacer que funcione y me gustaría recibir algunos consejos.

El proyecto de demostración se puede descargar aquí: DynamicSectionedList.zip

En la demo, se añaden los epígrafes dinámicamente mirando hacia adelante 10 lugares para encontrar la posición correcta, donde los elementos de la lista cambia a la siguiente letra. Cada implementación de notifyDataSetChanged tiene problemas como se describe:

Demo 1 Esta demostración muestra la importancia de notifyDataSetChanged(). Al hacer clic en cualquier cosa, la aplicación se bloqueará. Esto se debe a cierta comprobación de cordura en ListView ... mItemCount != adapter.getItemCount(). Moraleja es, tenemos que notificar a la lista que los datos han cambiado.

Demostración 2 El siguiente paso natural es notificar al ListView de los cambios cuando se producen cambios. Desafortunadamente, hacerlo mientras el ListView se desplaza firmemente interrumpe toda interacción táctil hasta que la aplicación se desconecta del modo táctil. Necesitarás "lanzar scroll" lo suficiente como para generar nuevos encabezados para darte cuenta de esto. Tocar la pantalla no hará que el desplazamiento se detenga, y una vez que se haya detenido, no se podrá hacer clic en ninguno de los elementos de la lista. Esto se debe a un código if (!mDataChanged) { /* do very important stuff */ } en AbsListView.onTouchEvent().

Demostración 3 Para solucionar este problema, Demostración 3 introduce una bandera pendingChanges y las ganancias adaptador personalizado un notifyDataSetChangedIfNeeded() que pueden ser llamadas por el ListView una vez que ha entrado en un estado "seguro" para los cambios. El primer punto donde se deben notificar los cambios es en ListView.layoutChildren(), por lo que elimino ese método para notificar primero los cambios, de ser necesario, y luego llamar. Lanza al menos un encabezado y luego haz clic en un elemento de la lista.

Esto no funciona del todo bien, aunque no estoy del todo seguro de por qué. Al tocar o seleccionar un elemento con el teclado/trackball, la lista se actualiza sin sincronizar correctamente la posición anterior. Se desplaza a la parte superior de la lista, lo que no es aceptable.

Demo 4 El problema de desplazamiento en Demo 3 se puede conquistar, al menos en modo táctil. Al agregar una llamada a notifyDataSetChangedIfNeeded() al tocar hacia abajo, el cambio de datos ocurre en un momento tal que todas las interacciones táctiles funcionan como se espera y la posición de la lista se sincroniza correctamente.

Sin embargo, no puedo encontrar un análogo para eso cuando el dispositivo no está en modo táctil, por no mencionar el hecho de que definitivamente parece un truco. La lista casi siempre se desplaza hacia la parte superior, no puedo averiguar qué causa que ocasionalmente mantenga la posición correcta.

Dado que Android lucha contra mí en cada paso del camino, creo que debería haber un mejor enfoque. Pruebe la demostración, si se pueden aplicar correcciones para que funcione, ¡sería genial!

Muchas gracias a cualquiera que pueda ver esto, con suerte si podemos hacer que funcione el código, será útil para otros que intentan lograr la misma optimización para listas con encabezados.

+1

Los bucles for inmediatamente debajo de 'LinearLayout buttons' no parecen tener una sintaxis Java válida. También parece que falta un código que completa la lista. Te recomiendo que crees un proyecto completo que contenga estas cosas, que aparezca en ZIP y que lo vincules con tu pregunta. Incluso entonces, no pondría tremendas probabilidades de que recibas mucha ayuda, ya que este es un pedazo sustancial de código difícil de descifrar, y realmente no estás haciendo ninguna pregunta en tu pregunta. – CommonsWare

+1

Puede considerar comenzar una segunda pregunta, ampliando el problema real ("la cantidad de datos en la lista puede ser bastante grande y el cálculo de los títulos puede considerarse lo suficientemente caro como para requerir una optimización"). Lo que tienes aquí es algo relacionado con tu código real que intenta abordar el problema real. Sin embargo, puede ser más simple para usted obtener ayuda con el problema real en sí, averiguar por qué "el cálculo de los títulos" es "costoso" y cómo abordarlo. Por supuesto, también está preguntando esto el jueves antes de un fin de semana festivo ... – CommonsWare

+2

Por cierto, FWIW, escribí una publicación de blog con mis pensamientos sobre agregar encabezados a un 'CursorAdapter': http://commonsware.com/ blog/2010/12/30/adding-headings-cursoradapter.html – CommonsWare

Respuesta

4

El principal problema con el enfoque presentado anteriormente era que el conjunto de datos se cambiaba directamente desde dentro del código ejecutado por las superclases. Al completar los encabezados en getView() estaba cambiando los datos en lo que podría ser el medio de una operación o un estado "inseguro" en las superclases. Esta es la razón por la que toda interacción táctil se rompió cuando se llamó a onNotifyDataSetChanged durante un desplazamiento fling. Los datos deben agregarse desde una fuente externa, no desde los métodos del adaptador.

Esto se puede hacer utilizando un controlador o AsyncTask para agregar los títulos al adaptador y llamar a notifyDataSetChanged. Estos se ejecutarán en un subproceso de UI y no interferirán con el estado de la superclase. Gracias al ejemplo EndlessAdapter de CommonsWare por introducir el concepto de AsyncTask para agregar datos a una lista.

Los ataques onTouchEvent() y layoutChildren() ya no se necesitaban después de hacer ese cambio.

Cuestiones relacionadas