Cómo escribir un filtro personalizado para ListView con ArrayAdapter

Tengo un ListView que está conectado a un ArrayAdapter donde Artist es una clase simple mía, que solo tiene una identificación y un nombre.

Ahora quiero filtrar el ListView para llamar:

artistAdapter.getFilter().filter("bla", new Filter.FilterListener() { public void onFilterComplete(int count) { Log.d(Config.LOG_TAG, "filter complete! count: " + count); // returns 8 Log.d(Config.LOG_TAG, "adapter count: " + artistAdapter.getCount()); // return 1150 } }); 

La primera statement de depuración imprime un conteo de 8. Esa es la cuenta de corrent para los elementos de lista que comienzan con “bla” pero el adaptador no lo obtiene. La segunda instrucción de depuración imprime un recuento de 1150 elementos. Esa es la cantidad completa de elementos en la lista.

De alguna manera, el filtro no le dice al adaptador que ha filtrado los datos subyacentes.

Quiero saber ahora: ¿tengo que codificar algo en mi adaptador para que reciba las actualizaciones del filtro? ¿Debo escribir un filtro personalizado? ¿Que tengo que hacer?

Actualmente

Noté que debería haber estado usando la lista ‘originalItems’ para comstackr la nueva filtrada en performFiltering.

Esto solucionará cualquier problema que vea con respecto a cambiar el texto en el filtro. Por ejemplo, busca ‘Pan’ y luego retrocede a solo ‘B’ y debería ver todas las ‘B’. En mi publicación original no lo tendrías.

  private class GlycaemicIndexItemAdapter extends ArrayAdapter { private ArrayList items; private ArrayList originalItems = new ArrayList(); private GlycaemicIndexItemFilter filter; private final Object mLock = new Object(); public GlycaemicIndexItemAdapter(Context context, int textViewResourceId, ArrayList newItems) { super(context, textViewResourceId, newItems); this.items = newItems; cloneItems(newItems); } protected void cloneItems(ArrayList items) { for (Iterator iterator = items.iterator(); iterator .hasNext();) { GlycaemicIndexItem gi = (GlycaemicIndexItem) iterator.next(); originalItems.add(gi); } } @Override public int getCount() { synchronized(mLock) { return items!=null ? items.size() : 0; } @Override public GlycaemicIndexItem getItem(int item) { GlycaemicIndexItem gi = null; synchronized(mLock) { gi = items!=null ? items.get(item) : null; } return gi; } @Override public View getView(int position, View convertView, ViewGroup parent) { View v = convertView; if (v == null) { LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE); v = vi.inflate(R.layout.row, null); } GlycaemicIndexItem i = null; synchronized(mLock) { i = items.get(position); } if (i != null) { TextView tt = (TextView) v.findViewById(R.id.rowText); TextView bt = (TextView) v.findViewById(R.id.rowText2); if (tt != null) { tt.setText("Name: "+i.getName()); } if(bt != null){ bt.setText("GI Value: " + i.getGlycaemicIndex()); } } return v; } /** * Implementing the Filterable interface. */ public Filter getFilter() { if (filter == null) { filter = new GlycaemicIndexItemFilter(); } return filter; } /** * Custom Filter implementation for the items adapter. * */ private class GlycaemicIndexItemFilter extends Filter { protected FilterResults performFiltering(CharSequence prefix) { // Initiate our results object FilterResults results = new FilterResults(); // No prefix is sent to filter by so we're going to send back the original array if (prefix == null || prefix.length() == 0) { synchronized (mLock) { results.values = originalItems; results.count = originalItems.size(); } } else { synchronized(mLock) { // Compare lower case strings String prefixString = prefix.toString().toLowerCase(); final ArrayList filteredItems = new ArrayList(); // Local to here so we're not changing actual array final ArrayList localItems = new ArrayList(); localItems.addAll(originalItems); final int count = localItems.size(); for (int i = 0; i < count; i++) { final GlycaemicIndexItem item = localItems.get(i); final String itemName = item.getName().toString().toLowerCase(); // First match against the whole, non-splitted value if (itemName.startsWith(prefixString)) { filteredItems.add(item); } else {} /* This is option and taken from the source of ArrayAdapter final String[] words = itemName.split(" "); final int wordCount = words.length; for (int k = 0; k < wordCount; k++) { if (words[k].startsWith(prefixString)) { newItems.add(item); break; } } } */ } // Set and return results.values = filteredItems; results.count = filteredItems.size(); }//end synchronized } return results; } @SuppressWarnings("unchecked") @Override protected void publishResults(CharSequence prefix, FilterResults results) { //noinspection unchecked synchronized(mLock) { final ArrayList localItems = (ArrayList) results.values; notifyDataSetChanged(); clear(); //Add the items back in for (Iterator iterator = localItems.iterator(); iterator .hasNext();) { GlycaemicIndexItem gi = (GlycaemicIndexItem) iterator.next(); add(gi); } }//end synchronized } } } 

Básicamente estoy construyendo una aplicación de salud y nutrición y una pantalla tendrá una lista de artículos basada en el índice glucémico / glucémico. Quiero que los usuarios puedan escribir y tener el autofiltro de la pantalla. Ahora, si solo está usando cadenas, obtiene el autofiltrado de forma gratuita. No obstante, no tengo, tengo mi propia clase personalizada GlycaemicIndexItem que tiene propiedades en ella. Necesito proporcionar mi propio filtro para asegurarme de que la lista que se dibujó en la pantalla se actualice cuando el usuario escribe.

Actualmente, la pantalla es una simple ListActivity, con un ListView y un EditText (que el usuario ingresa). Adjuntaremos un TextWatcher a este EditText para garantizar que recibamos notificaciones de las actualizaciones. Esto significa que debería funcionar para todos los dispositivos independientemente de que el usuario escriba en un teclado duro o blando (tengo un HTC DesireZ y un G1 antiguo).

Aquí está el diseño xml para la pantalla / actividad (¿Puede alguien decirme cómo pegar el código xml aquí, ya que cuando trato de usar código bloque xml no se pega / se muestra correctamente, sino que se interpreta):

diseño de la actividad - giatoz.xml

Como queremos mostrar nuestras filas en estilo personalizado, también tenemos un archivo xml de diseño para la misma fila: Archivo fila xml

Aquí está el código para toda la actividad en sí. Extendiéndose desde ListActivity, esta clase tiene una clase interna que actúa como el adaptador, que se extiende desde ArrayAdapter. Esto se instancia en el onCreate of the Activity y pasa una lista simple de cadenas por ahora. Preste atención a cómo se crea en las líneas 39-40. Nuestro diseño especial para la fila se pasa con la lista de elementos.

La clave para poblar las filas personalizadas es en el método getView del adaptador.

Nuestra clase de adaptador también tiene su propia clase interna llamada GlycaemicIndexItemFilter, que hace el trabajo cuando un usuario escribe. Nuestro filtro está vinculado a nuestro EditText en las líneas 43-44 mediante el uso de un TextWatcher y su método afterTextChanged . La línea 47 es la clave de cómo logramos el filtrado. Llamamos filtro, en nuestro objeto de filtro. Nuestro filtro se crea cuando llamamos a getFilter la primera vez, línea 148-149.

  package com.tilleytech.android.myhealthylife; import java.util.ArrayList; import java.util.Iterator; import android.app.ListActivity; import android.content.Context; import android.content.res.Resources; import android.os.Bundle; import android.text.Editable; import android.text.TextWatcher; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.EditText; import android.widget.Filter; import android.widget.ListView; import android.widget.TextView; public class GlycaemicIndexAtoZActivity extends ListActivity { /** Called when the activity is first created. */ private GlycaemicIndexItemAdapter giAdapter; private TextWatcher filterTextWatcher; private EditText filterText = null; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.giatoz); ListView lv = getListView(); lv.setTextFilterEnabled(true); // By using setAdapter method in listview we an add string array in list. ArrayList list = getListItems(); giAdapter = new GlycaemicIndexItemAdapter(this, R.layout.row, list); giAdapter.notifyDataSetChanged(); setListAdapter(giAdapter); filterText = (EditText)findViewById(R.id.GI_AtoZSearchEditText); filterTextWatcher = new TextWatcher() { public void afterTextChanged(Editable s) { giAdapter.getFilter().filter(s); //Filter from my adapter giAdapter.notifyDataSetChanged(); //Update my view } public void beforeTextChanged(CharSequence s, int start, int count, int after) { } public void onTextChanged(CharSequence s, int start, int before, int count) { } }; filterText.addTextChangedListener(filterTextWatcher); } private ArrayList getListItems() { ArrayList result = new ArrayList(); Resources res = getResources(); //Get our raw strings String[] array = res.getStringArray(R.array.GIList); for (int i = 0; i < array.length; i++) { GlycaemicIndexItem gi = new GlycaemicIndexItem(); gi.setName(array[i]); gi.setGlycaemicIndex(1); result.add(gi); } return result; } private class GlycaemicIndexItemAdapter extends ArrayAdapter { private ArrayList items; private ArrayList originalItems = new ArrayList(); private GlycaemicIndexItemFilter filter; private final Object mLock = new Object(); public GlycaemicIndexItemAdapter(Context context, int textViewResourceId, ArrayList newItems) { super(context, textViewResourceId, newItems); this.items = newItems; cloneItems(newItems); } protected void cloneItems(ArrayList items) { for (Iterator iterator = items.iterator(); iterator .hasNext();) { GlycaemicIndexItem gi = (GlycaemicIndexItem) iterator.next(); originalItems.add(gi); } } @Override public int getCount() { synchronized(mLock) { return items!=null ? items.size() : 0; } } @Override public GlycaemicIndexItem getItem(int item) { GlycaemicIndexItem gi = null; synchronized(mLock) { gi = items!=null ? items.get(item) : null; } return gi; } @Override public View getView(int position, View convertView, ViewGroup parent) { View v = convertView; if (v == null) { LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE); v = vi.inflate(R.layout.row, null); } GlycaemicIndexItem i = null; synchronized(mLock) { i = items.get(position); } if (i != null) { TextView tt = (TextView) v.findViewById(R.id.rowText); TextView bt = (TextView) v.findViewById(R.id.rowText2); if (tt != null) { tt.setText("Name: "+i.getName()); } if(bt != null){ bt.setText("GI Value: " + i.getGlycaemicIndex()); } } return v; } /** * Implementing the Filterable interface. */ public Filter getFilter() { if (filter == null) { filter = new GlycaemicIndexItemFilter(); } return filter; } /** * Custom Filter implementation for the items adapter. * */ private class GlycaemicIndexItemFilter extends Filter { protected FilterResults performFiltering(CharSequence prefix) { // Initiate our results object FilterResults results = new FilterResults(); // No prefix is sent to filter by so we're going to send back the original array if (prefix == null || prefix.length() == 0) { synchronized (mLock) { results.values = originalItems; results.count = originalItems.size(); } } else { synchronized(mLock) { // Compare lower case strings String prefixString = prefix.toString().toLowerCase(); final ArrayList filteredItems = new ArrayList(); // Local to here so we're not changing actual array final ArrayList localItems = new ArrayList(); localItems.addAll(originalItems); final int count = localItems.size(); for (int i = 0; i < count; i++) { final GlycaemicIndexItem item = localItems.get(i); final String itemName = item.getName().toString().toLowerCase(); // First match against the whole, non-splitted value if (itemName.startsWith(prefixString)) { filteredItems.add(item); } else {} /* This is option and taken from the source of ArrayAdapter final String[] words = itemName.split(" "); final int wordCount = words.length; for (int k = 0; k < wordCount; k++) { if (words[k].startsWith(prefixString)) { newItems.add(item); break; } } } */ } // Set and return results.values = filteredItems; results.count = filteredItems.size(); }//end synchronized } return results; } @SuppressWarnings("unchecked") @Override protected void publishResults(CharSequence prefix, FilterResults results) { //noinspection unchecked synchronized(mLock) { final ArrayList localItems = (ArrayList) results.values; notifyDataSetChanged(); clear(); //Add the items back in for (Iterator iterator = localItems.iterator(); iterator .hasNext();) { GlycaemicIndexItem gi = (GlycaemicIndexItem) iterator.next(); add(gi); } }//end synchronized } } } } 

Creo que puedes usar notifyDataSetChanged(); en el método enFilterComplete.