ListView no se actualiza después de filtrar

Tengo un ListView (con setTextFilterEnabled (true)) y un adaptador personalizado (extends ArrayAdapter) que actualizo desde el subproceso de interfaz de usuario principal cada vez que un nuevo elemento se agrega / insertó. Todo funciona bien al principio – los nuevos elementos aparecen en la lista inmediatamente. Sin embargo, esto se detiene en el momento en que intento filtrar la lista.

El filtrado funciona, pero lo hago una vez y todos mis intentos sucesivos de modificar el contenido de la lista (agregar, eliminar) no se muestran más. He utilizado el registro para ver si los datos de la lista del adaptador se actualiza correctamente, y lo hace, pero ya no está en sincronía con el ListView que se muestra.

Cualquier idea de lo que está causando esto y la mejor manera de abordar el problema?

Fui a través del código fuente real de ArrayAdapter y parece que realmente se escribió para comportarse de esa manera.

ArrayAdapter tiene dos listas para empezar: mObjects y mOriginalValues. MObjects es el conjunto de datos primario con el que funcionará el adaptador. Tomando la función add (), por ejemplo:

public void add(T object) { if (mOriginalValues != null) { synchronized (mLock) { mOriginalValues.add(object); if (mNotifyOnChange) notifyDataSetChanged(); } } else { mObjects.add(object); if (mNotifyOnChange) notifyDataSetChanged(); } } 

MOriginalValues ​​es inicialmente nulo, por lo que todas las operaciones (añadir, insertar, eliminar, borrar) se orientan de forma predeterminada a mObjects. Todo está bien hasta el momento en que decida activar el filtrado en la lista y realice uno. El filtrado por primera vez inicializa mOriginalValues ​​con lo que mObjects tiene:

 private class ArrayFilter extends Filter { @Override protected FilterResults performFiltering(CharSequence prefix) { FilterResults results = new FilterResults(); if (mOriginalValues == null) { synchronized (mLock) { mOriginalValues = new ArrayList<T>(mObjects); //mOriginalValues is no longer null } } if (prefix == null || prefix.length() == 0) { synchronized (mLock) { ArrayList<T> list = new ArrayList<T>(mOriginalValues); results.values = list; results.count = list.size(); } } else { //filtering work happens here and a new filtered set is stored in newValues results.values = newValues; results.count = newValues.size(); } return results; } @Override protected void publishResults(CharSequence constraint, FilterResults results) { //noinspection unchecked mObjects = (List<T>) results.values; if (results.count > 0) { notifyDataSetChanged(); } else { notifyDataSetInvalidated(); } } } 

MOriginalValues ​​ahora tiene una copia de, así, los valores / elementos originales, por lo que el adaptador podría hacer su trabajo y mostrar una lista filtrada a través de mObjects sin perder los datos pre-filtrados.

Ahora perdóname (y por favor dile y explícame) si mi pensamiento es incorrecto pero encuentro esto extraño porque ahora que mOriginalValues ​​ya no es nulo, todas las llamadas posteriores a cualquiera de las operaciones del adaptador solo modificarán mOriginalValues. Sin embargo, desde que el adaptador fue configurado para mirar a mObjects como su conjunto de datos primarios, aparecería en la pantalla que nada está sucediendo. Eso es hasta que realice otra ronda de filtrado. La eliminación del filtro desencadena esto:

 if (prefix == null || prefix.length() == 0) { synchronized (mLock) { ArrayList<T> list = new ArrayList<T>(mOriginalValues); results.values = list; results.count = list.size(); } } 

MOriginalValues, que hemos estado modificando desde nuestro primer filtro (aunque no pudimos verlo sucediendo en pantalla) se almacena en otra lista y se copia en mObjects, para finalmente mostrar los cambios realizados. Sin embargo, será así a partir de este punto: todas las operaciones se realizarán en mOriginalValues, y los cambios sólo aparecerán después del filtrado.

En cuanto a la solución, lo que he venido por el momento es (1) para poner una bandera booleana que dice las operaciones del adaptador si hay un filtrado en curso o no – si el filtrado está completo, a continuación, copie sobre el contenido De mOriginalValues ​​a mObjects, o (2) simplemente llamar al objeto Filter del adaptador y pasar una cadena vacía * .getFilter (). Filter ("") para forzar un filtro después de cada operación [como también sugirió BennySkogberg].

Sería muy apreciado si alguien puede arrojar algo más de luz sobre este tema o confirmar lo que acabo de hacer. ¡Gracias!

Si entiendo bien su problema, ¿llama al método notifyDataSetChanged ()? Obliga a que el listview se vuelva a dibujar.

 listAdapter.notifyDataSetChanged(); 

Sólo tienes que usar esto cada vez que haces algo (por ejemplo: lazy-carga de imágenes) para actualizar el listview.

El problema se informa como un defecto desde julio de 2010. Echa un vistazo a mi comentario # 5

En primer lugar me gustaría mencionar, que @ jhie respuesta es muy buena. Sin embargo esos hechos realmente no solucionaron mi problema. Pero con la ayuda del conocimiento de cómo funcionó el mecanismo utilizado internamente, podría escribir mi propia función de filtrado:

 public ArrayList<String> listArray = new ArrayList<>(); public ArrayList<String> tempListArray = new ArrayList<>(); public ArrayAdapter<String> tempAdapter; public String currentFilter; 

En onCreate:

 // Fill my ORIGINAL listArray; listArray.add("Cookies are delicious."); // After adding everything to listArray, I add everything to tempListArray for (String element : listArray){ tempListArray.add(element); } // Setting up the Adapter... tempAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, tempListArray); myListView.setAdapter(tempAdapter); 

Filtración:

 // Defining filter functions public void customFilterList(String filter){ tempListArray.clear(); for (String item : listArray){ if (item.toLowerCase().contains(filter.toLowerCase())){ tempListArray.add(item); } } currentFilter = filter; tempAdapter.notifyDataSetChanged(); } public void updateList(){ customFilterList(currentFilter); } 

Lo usé para implementar una función de búsqueda en mi lista.

La gran ventaja: tiene mucho más poder de filtrado: en customFilterList () puede implementar fácilmente el uso de expresiones regulares o utilizarlo como un filtro sensible a mayúsculas.

En lugar de notifyDataSetChanged llamaría a updateList () .

En lugar de myListView.getFilter (). Filter ('filter text') se llama a customFilterList ('filter text')

Por el momento esto sólo funciona con un ListView. Tal vez – con un poco de trabajo – que podría implementar esto como un Adaptador de Arraigo Personalizado.


Espero poder ayudarle proporcionando estos pedazos pequeños de código.


Codificación feliz

  • Extender el ejemplo de AbsListView
  • Comportamiento personalizado de AutoCompleteTextView
  • Pasar Contexto a ArrayAdapter dentro de Fragmento con setRetainInstance (true) causará la fuga?
  • Utilizar ArrayAdapter con AlertDialog y .setAdapter
  • ListView con un adaptador personalizado, agregando elementos uno por uno
  • Android: smoothScrollToPosition () no funciona correctamente
  • Cómo cambiar mediante programación la altura de un elemento de lista después de que se haya completado la lista
  • Conjunto de datos grande con ArrayAdapter y ListView en Android
  • (Unidad) Prueba de ArrayAdapter
  • Utilizar un adaptador de lista personalizado con AutoCompleteTextView y mantener la funcionalidad del auto completo trabajando igual
  • Android: Cómo utilizar la lista de arrays de cadenas dentro de la fila de lista
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.