Android AutoCompleteTextView con filtro de adaptador personalizado no funciona

Tengo el Custom CustomerAdapter

public class CustomerAdapter extends ArrayAdapter { private final String MY_DEBUG_TAG = "CustomerAdapter"; private ArrayList items; private int viewResourceId; public CustomerAdapter(Context context, int viewResourceId, ArrayList items) { super(context, viewResourceId, items); this.items = items; this.viewResourceId = viewResourceId; } public View getView(int position, View convertView, ViewGroup parent) { View v = convertView; if (v == null) { LayoutInflater vi = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); v = vi.inflate(viewResourceId, null); } Customer customer = items.get(position); if (customer != null) { TextView customerNameLabel = (TextView) v.findViewById(R.id.customerNameLabel); if (customerNameLabel != null) { customerNameLabel.setText(String.valueOf(customer.getName())); } } return v; } } 

y diseño de customer_auto

    

y en mi public void onCreate

 AutoCompleteTextView customerAutoComplete = (AutoCompleteTextView) findViewById(R.id.autocomplete_customer); CustomerAdapter customerAdapter = new CustomerAdapter(this, R.layout.customer_auto, customerList); customerAutoComplete.setAdapter(customerAdapter); 

y Customer.java

 public class Customer implements Parcelable { private int id; private String name = ""; public Customer() { // TODO Auto-generated constructor stub } /** * This will be used only by the MyCreator * * @param source */ public Customer(Parcel source) { /* * Reconstruct from the Parcel */ id = source.readInt(); name = source.readString(); } public void setId(int id) { this.id = id; } public void setName(String name) { this.name = name; } public int getId() { return this.id; } public String getName() { return this.name; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(id); dest.writeString(name); } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override public Customer createFromParcel(Parcel source) { return new Customer(source); } @Override public Customer[] newArray(int size) { return new Customer[size]; // TODO Auto-generated method stub } }; @Override public String toString() { return this.name; } } 

Pero el cuadro de sugerencia automática no se filtra correctamente. por ejemplo; ¡Si escribo an mensaje en el cuadro de prueba, aparecerán los clientes que comienzan con br !

Tengo que anular el método getFilter () del Adaptador

Aquí está el código que funcionó para mí, gracias a sacoskun

 public class CustomerAdapter extends ArrayAdapter { private final String MY_DEBUG_TAG = "CustomerAdapter"; private ArrayList items; private ArrayList itemsAll; private ArrayList suggestions; private int viewResourceId; public CustomerAdapter(Context context, int viewResourceId, ArrayList items) { super(context, viewResourceId, items); this.items = items; this.itemsAll = (ArrayList) items.clone(); this.suggestions = new ArrayList(); this.viewResourceId = viewResourceId; } public View getView(int position, View convertView, ViewGroup parent) { View v = convertView; if (v == null) { LayoutInflater vi = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); v = vi.inflate(viewResourceId, null); } Customer customer = items.get(position); if (customer != null) { TextView customerNameLabel = (TextView) v.findViewById(R.id.customerNameLabel); if (customerNameLabel != null) { // Log.i(MY_DEBUG_TAG, "getView Customer Name:"+customer.getName()); customerNameLabel.setText(customer.getName()); } } return v; } @Override public Filter getFilter() { return nameFilter; } Filter nameFilter = new Filter() { @Override public String convertResultToString(Object resultValue) { String str = ((Customer)(resultValue)).getName(); return str; } @Override protected FilterResults performFiltering(CharSequence constraint) { if(constraint != null) { suggestions.clear(); for (Customer customer : itemsAll) { if(customer.getName().toLowerCase().startsWith(constraint.toString().toLowerCase())){ suggestions.add(customer); } } FilterResults filterResults = new FilterResults(); filterResults.values = suggestions; filterResults.count = suggestions.size(); return filterResults; } else { return new FilterResults(); } } @Override protected void publishResults(CharSequence constraint, FilterResults results) { ArrayList filteredList = (ArrayList) results.values; if(results != null && results.count > 0) { clear(); for (Customer c : filteredList) { add(c); } notifyDataSetChanged(); } } }; } 

Esta es mi solución. Siento que es un poco más limpio (no usa 3 ArrayLists separadas y confusas) que el aceptado, y tiene más opciones.

Esto es lo que he resuelto después de un poco de prueba y error. Debería funcionar incluso si el usuario escribe la tecla de retroceso, porque no elimina las entradas originales de mCustomers :

 public class CustomerAdapter extends ArrayAdapter { private LayoutInflater layoutInflater; List mCustomers; private Filter mFilter = new Filter() { @Override public String convertResultToString(Object resultValue) { return ((Customer)resultValue).getName(); } @Override protected FilterResults performFiltering(CharSequence constraint) { FilterResults results = new FilterResults(); if (constraint != null) { ArrayList suggestions = new ArrayList(); for (Customer customer : mCustomers) { // Note: change the "contains" to "startsWith" if you only want starting matches if (customer.getName().toLowerCase().contains(constraint.toString().toLowerCase())) { suggestions.add(customer); } } results.values = suggestions; results.count = suggestions.size(); } return results; } @Override protected void publishResults(CharSequence constraint, FilterResults results) { clear(); if (results != null && results.count > 0) { // we have filtered results addAll((ArrayList) results.values); } else { // no filter, add entire original list back in addAll(mCustomers); } notifyDataSetChanged(); } }; public CustomerAdapter(Context context, int textViewResourceId, List customers) { super(context, textViewResourceId, customers); // copy all the customers into a master list mCustomers = new ArrayList(customers.size()); mCustomers.addAll(customers); layoutInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); } @Override public View getView(int position, View convertView, ViewGroup parent) { View view = convertView; if (view == null) { view = layoutInflater.inflate(R.layout.customerNameLabel, null); } Customer customer = getItem(position); TextView name = (TextView) view.findViewById(R.id.customerNameLabel); name.setText(customer.getName()); return view; } @Override public Filter getFilter() { return mFilter; } } 

En lugar de anular el método getFilter() en el adaptador, simplemente podemos anular toString() del objeto definido por el usuario (Cliente). En toString() solo devuelve el campo en función de lo que necesita filtrar. Funcionó para mí

En mi ejemplo, estoy filtrando en base a nombres:

 public class Customer{ private int id; private String name; @Override public String toString() { return this.name; } } 

En el código anterior, el método publisHResults() proporciona la excepción de modificación concurrente … tenemos que modificar el código como:

 @Override protected void publishResults(CharSequence constraint, FilterResults results) { ArrayList filteredList = (ArrayList) results.values; ArrayList customerList=new ArrayList(); if (results != null && results.count > 0) { clear(); for (Customer c : filteredList) { customerList.add(c); } Iterator customerIterator=getResult.iterator(); while (customerIterator.hasNext()) { Customer customerIterator=customerIterator.next(); add(customerIterator); } notifyDataSetChanged(); } } 

Tal vez sea demasiado tarde, no es necesario que anule todas estas funciones, la única función para anular es:

  public View getView(int position, View convertView, ViewGroup parent) { View v = convertView; if (v == null) { LayoutInflater vi = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); v = vi.inflate(viewResourceId, null); } Customer customer = getItem(position); if (customer != null) { TextView customerNameLabel = (TextView) v.findViewById(R.id.customerNameLabel); if (customerNameLabel != null) { customerNameLabel.setText(String.valueOf(customer.getName())); } } return v; } 

considera que cambio:

  Customer customer = items.get(position); Customer customer = getItem(position); 

presta atención, no deberías declarar nuevos ListItems,

  private ArrayList items; 

porque ArrayAdapter funciona con sus propios mObjetos y filtra esta lista, no su lista de elementos, por lo que debe usar la función getItem para acceder a los elementos. entonces no hay razón para escribir su ArrayFilter.

No sé dónde está recuperando el getResult. Creo que la solución en este caso para no tener la modificación simultánea es:

 @Override protected void publishResults(CharSequence constraint, FilterResults results) { ArrayList filteredList = (ArrayList) results.values; ArrayList customerList=new ArrayList(); if (results != null && results.count > 0) { clear(); try{ for (Customer c : filteredList) { customerList.add(c); } }catch(Exception e){ Log.e("PEEEETAAAAAAAA", "AutoCompletaError: "+e.getMessage()+" "+e.getCause()+" "+e.getLocalizedMessage()); } Iterator customerIterator=customerList.iterator(); while (customerIterator.hasNext()) { Customer customerIterator=customerIterator.next(); add(customerIterator); } notifyDataSetChanged(); } } 

Espero que esta publicación ayude a las personas con la implementación de una funcionalidad personalizada similar en el futuro. Basé esto en mi versión de adaptador utilizada para mostrar sugerencias de tags en mi aplicación de microblogging:

 public class TagSuggestionsAdapter extends ArrayAdapter implements Filterable 

Extendiendo ArrayAdapter para tener menos código repetitivo. Implementando Filterable para cambiar el comportamiento del filtro más tarde.

  private List allTags; private List tagSuggestions; private Context context; public TagSuggestionsAdapter(List initialTagSuggestions, List allTags, Context context) { super(context, R.layout.item_tag_suggestion, initialTagSuggestions); this.tagSuggestions = initialTagSuggestions; this.allTags = allTags; this.context = context; } 

Básicamente, en el constructor necesitas pasar una lista que se mostrará inicialmente; luego se convertirá en una lista con resultados filtrados (esta también es una referencia a una lista que se tomará en consideración cuando se llame a notifyDataSetChanged ()) y obviamente una lista en el que puede basar su filtrado (todos los Tags en mi caso). También estoy pasando Contexto para la inflación de diseño en getView ().

  @NonNull @Override public View getView(final int position, @Nullable View convertView, @NonNull ViewGroup parent) { ViewHolder viewHolder; if (convertView == null) { convertView = LayoutInflater.from(context) .inflate(R.layout.item_tag_suggestion, parent, false); viewHolder = new ViewHolder(convertView); convertView.setTag(viewHolder); } else { viewHolder = (ViewHolder) convertView.getTag(); } viewHolder.tagSuggestionTextView.setText(tagSuggestions.get(position)); return convertView; } static class ViewHolder { @BindView(R.id.tag_suggestion_text_view) TextView tagSuggestionTextView; ViewHolder(View itemView) { ButterKnife.bind(this, itemView); } } 

Arriba puede ver un patrón de titular de vista simple con un poco de ayuda de Butterknife para inflar un diseño de fila personalizado.

  @NonNull @Override public Filter getFilter() { return new Filter() { @Override protected FilterResults performFiltering(CharSequence constraint) { if (constraint != null) { List filteredTags = filterTagSuggestions(constraint.toString(), allTags); FilterResults filterResults = new FilterResults(); filterResults.values = filteredTags; filterResults.count = filteredTags.size(); return filterResults; } else { return new FilterResults(); } } @Override protected void publishResults(CharSequence constraint, FilterResults results) { tagSuggestions.clear(); if (results != null && results.count > 0) { List filteredTags = (List) results.values; for (Object filteredTag : filteredTags) { if (filteredTag instanceof String) { tagSuggestions.add((String) filteredTag); } } } notifyDataSetChanged(); } }; } 

Este es el código menos repetitivo que podría escribir. Su única preocupación es el método filterTagSuggestions que debe devolver una lista filtrada de tags basadas en la entrada del usuario ( CharSequence constraint ). Espero que la información necesaria resumida y organizada un poco.

Tengo no actualizar y modificar los problemas de la lista original de la respuesta anterior. Solucioné este problema con estos códigos.

 public class AdapterAutoCompleteTextView extends ArrayAdapter { private int LayoutID; private int TextViewID; private LayoutInflater Inflater; private List ObjectsList; public AdapterAutoCompleteTextView(Context ActivityContext, int ResourceID, int TextViewResourceID, List WordList) { super(ActivityContext, ResourceID, TextViewResourceID, new ArrayList()); LayoutID = ResourceID; TextViewID = TextViewResourceID; ObjectsList = WordList; Inflater = LayoutInflater.from(ActivityContext); } @Override public View getView(int Position, View ConvertView, ViewGroup Parent) { ItemWord Word = getItem(Position); if(ConvertView == null) { ConvertView = Inflater.inflate(LayoutID, null); ResultHolder Holder = new ResultHolder(); Holder.ResultLabel= (TextView) ConvertView.findViewById(TextViewID); ConvertView.setTag(Holder); } ResultHolder Holder = (ResultHolder) ConvertView.getTag(); Holder.ResultLabel.setText(Word.getSpelling()); return ConvertView; } @Override public Filter getFilter() { return CustomFilter; } private Filter CustomFilter = new Filter() { @Override public CharSequence convertResultToString(Object ResultValue) { return ((ItemWord) ResultValue).getSpelling(); } @Override protected FilterResults performFiltering(CharSequence Constraint) { FilterResults ResultsFilter = new FilterResults(); ArrayList OriginalValues = new ArrayList(ObjectsList); if(Constraint == null || Constraint.length() == 0){ ResultsFilter.values = OriginalValues; ResultsFilter.count = OriginalValues.size(); } else { String PrefixString = Constraint.toString().toLowerCase(); final ArrayList NewValues = new ArrayList(); for(ItemWord Word : OriginalValues){ String ValueText = Word.getSpelling().toLowerCase(); if(ValueText.startsWith(PrefixString)) NewValues.add(Word); } ResultsFilter.values = NewValues; ResultsFilter.count = NewValues.size(); } return ResultsFilter; } @Override protected void publishResults(CharSequence Constraint, FilterResults Results) { clear(); if(Results.count > 0) addAll(((ArrayList) Results.values)); else notifyDataSetInvalidated(); } }; private static class ResultHolder { TextView ResultLabel; } } 

Esta es la línea más importante para no actualizar y modificar el problema de la lista original:

 super(ActivityContext, ResourceID, TextViewResourceID, new ArrayList()); 

Particularmente aquellos

super (ActivityContext, ResourceID, TextViewResourceID, new ArrayList () );

Espero que esta solución te ayude 🙂

Si obtiene la excepción ConcurrentModificationException.

Reemplace ArrayList con el hilo seguro CopyOnWriteArrayList.

Aquí puedes encontrar detatils.

https://examples.javacodegeeks.com/java-basics/exceptions/java-util-concurrentmodificationexception-how-to-handle-concurrent-modification-exception/