Cómo actualizar dinámicamente un ListView en Android [cerrado]

159

En Android, ¿cómo puedo ListViewusar un filtro basado en la entrada del usuario, donde los elementos que se muestran se actualizan dinámicamente en función del TextViewvalor?

Estoy buscando algo como esto:

-------------------------
| Text View             |
-------------------------
| List item             |
| List item             |
| List item             |
| List item             |
|                       |
|                       |
|                       |
|                       |
-------------------------
Hamy
fuente
77
Nomino para la reapertura. He actualizado el texto para que esto sea más una pregunta, y es claramente un recurso valioso de la comunidad ya que las respuestas aún están llegando
Hamy
Por favor, vuelva a abrir la pregunta. Es claramente útil.
SilentNot

Respuestas:

286

Primero, debe crear un diseño XML que tenga un EditText y un ListView.

<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <!-- Pretty hint text, and maxLines -->
    <EditText android:id="@+building_list/search_box" 
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:hint="type to filter"
        android:inputType="text"
        android:maxLines="1"/>

    <!-- Set height to 0, and let the weight param expand it -->
    <!-- Note the use of the default ID! This lets us use a 
         ListActivity still! -->
    <ListView android:id="@android:id/list"
        android:layout_width="fill_parent"
        android:layout_height="0dip"
        android:layout_weight="1" 
         /> 

</LinearLayout>

Esto mostrará todo correctamente, con un agradable EditText encima de ListView. A continuación, cree una ListActivity como lo haría normalmente, pero agregue una setContentView()llamada en el onCreate()método para que usemos nuestro diseño recientemente declarado. Recuerde que identificamos el ListViewespecialmente, con android:id="@android:id/list". Esto permite ListActivitysaber qué ListViewqueremos usar en nuestro diseño declarado.

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.filterable_listview);

        setListAdapter(new ArrayAdapter<String>(this,
                       android.R.layout.simple_list_item_1, 
                       getStringArrayList());
    }

Ejecutar la aplicación ahora debería mostrar tu anterior ListView, con un bonito cuadro arriba. Para hacer que esa caja haga algo, necesitamos tomar la entrada de ella y hacer que esa entrada filtre la lista. Si bien muchas personas han intentado hacer esto manualmente, la mayoría de las ListView Adapter clases vienen con un Filterobjeto que se puede usar para realizar el filtrado de forma automática. Solo necesitamos canalizar la entrada desde EditTextel Filter. Resulta que es bastante fácil. Para ejecutar una prueba rápida, agregue esta línea a su onCreate()llamada

adapter.getFilter().filter(s);

Tenga en cuenta que tendrá que guardarlo ListAdapteren una variable para que esto funcione: he guardado mi ArrayAdapter<String>de antes en una variable llamada 'adaptador'.

El siguiente paso es obtener la entrada de EditText. Esto realmente requiere un poco de reflexión. Podrías agregar un OnKeyListener()a tu EditText. Sin embargo, este oyente solo recibe algunos eventos clave . Por ejemplo, si un usuario ingresa 'wyw', el texto predictivo probablemente recomendará 'eye'. Hasta que el usuario elija 'wyw' u 'eye', OnKeyListenerno recibirá un evento clave. Algunos pueden preferir esta solución, pero me pareció frustrante. Quería cada evento clave, así que tuve la opción de filtrar o no filtrar. La solución es a TextWatcher. Simplemente cree y agregue un TextWatchera EditText, y pase ListAdapter Filteruna solicitud de filtro cada vez que cambie el texto. Recuerde que debe retirar el TextWatcherde OnDestroy()! Aquí está la solución final:

private EditText filterText = null;
ArrayAdapter<String> adapter = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.filterable_listview);

    filterText = (EditText) findViewById(R.id.search_box);
    filterText.addTextChangedListener(filterTextWatcher);

    setListAdapter(new ArrayAdapter<String>(this,
                   android.R.layout.simple_list_item_1, 
                   getStringArrayList());
}

private TextWatcher filterTextWatcher = new TextWatcher() {

    public void afterTextChanged(Editable s) {
    }

    public void beforeTextChanged(CharSequence s, int start, int count,
            int after) {
    }

    public void onTextChanged(CharSequence s, int start, int before,
            int count) {
        adapter.getFilter().filter(s);
    }

};

@Override
protected void onDestroy() {
    super.onDestroy();
    filterText.removeTextChangedListener(filterTextWatcher);
}
Hamy
fuente
77
¿Hay alguna manera directa de filtrar ListView en "contiene" en lugar de "comienza con" la moda como lo hace esta solución?
Viktor Brešan
12
Viktor: si las palabras que le interesan están separadas por espacios, lo hará automáticamente. De lo contrario, no realmente. Probablemente la forma más fácil sería subclasificar el Adaptador extendiéndolo y anular el método getFilter para devolver un objeto Filter que defina. Consulte github.com/android/platform_frameworks_base/blob/master/core/… para comprender cómo funciona el ArrayFilter predeterminado: sería sencillo copiar el 95% de este código y cambiar las líneas 479 y 486
Hamy
2
Hamy, excelente redacción! Sin embargo, tengo una pregunta: he implementado esto y después de cada letra que escribo, el ListView desaparece por un par de segundos y luego vuelve, filtrado. ¿Has experimentado esto? Mi intuición es que es porque tengo más de 600 elementos en la lista, con funciones no triviales toString ().
lowellk
2
En modo horizontal en una pantalla más pequeña, el teclado EditText + ocupa toda la pantalla y ListView no está visible. ¿Alguna solución?
Martin Konicek
44
¿Es esto realmente necesario? > Recuerde eliminar TextWatcher en OnDestroy ()
Jojo
10

ejecutar el programa provocará un cierre forzado.

Cambié la línea:

android: id = "@ + building_list / search_box"

con

android: id = "@ + id / search_box"

¿Podría ser el problema? ¿Para qué sirve la '@ + building_list'?

j7nn7k
fuente
Johe, cuando dices R.id. algo, el algo existe en id porque dijiste android: id = "@ + id / search_box". Si dice android: id = "@ + building_list / search_box", en el código puede llamar a findViewById (R.building_list.search_box); ¿Cuál es la excepción de nivel superior que está recibiendo? Este código se copia sin una compilación de prueba, por lo que es probable que haya dejado al menos un error allí en alguna parte
Hamy
Hola Hamy, hiciste referencia a "@ + building_list / search_box" en el código con "filterText = (EditText) findViewById (R.id.search_box);" Por eso me preguntaba.
j7nn7k
1
La fuerza se cierra al comenzar a escribir. Parte del error: ERROR / AndroidRuntime (188): java.lang.NullPointerException 02-11 07: 30: 29.828: ERROR / AndroidRuntime (188): en xxx.com.ListFilter $ 1. onTextChanged (ListFilter.java:46) 02-11 07: 30: 29.828: ERROR / AndroidRuntime (188): en android.widget.TextView.sendOnTextChanged (TextView.java:6102) 02-11 07: 30: 29.828: ERROR / AndroidRuntime (188): en android.widget.TextView.handleTextChanged (TextView.java:6143) 02-11 07: 30: 29.828: ERROR / AndroidRuntime (188): en android.widget.TextView $ ChangeWatcher.onTextChanged (TextView.java : 6286)
j7nn7k
Johe, ¡Vaya! Parece que estaba tratando de modificar el código para deshacerme de la @ + building_list (porque es un punto de confusión), pero no lo entendí en todas partes. ¡Gracias por el consejo! Solo cambiarlo a @ + id debería solucionarlo
Hamy
4

Tuve un problema con el filtrado, los resultados se han filtrado, ¡pero no se han restaurado !

así que antes de filtrar (inicio de actividad) creé una copia de seguridad de la lista ... (solo otra lista, que contiene los mismos datos)

Al filtrar, el filtro y el adaptador de lista se conectan a la lista primaria.

pero el filtro en sí utilizó los datos de la lista de respaldo.

esto aseguró en mi caso, que la lista se actualizó de inmediato e incluso al eliminar los caracteres de término de búsqueda, la lista se restaura con éxito en todos los casos :)

Gracias por esta solución de todos modos.

cV2
fuente