Android AutoCompleteTextView con filtro de adaptador personalizado no funciona

Tengo el CustomerAdapter personalizado

public class CustomerAdapter extends ArrayAdapter<Customer> { private final String MY_DEBUG_TAG = "CustomerAdapter"; private ArrayList<Customer> items; private int viewResourceId; public CustomerAdapter(Context context, int viewResourceId, ArrayList<Customer> 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 customer_auto diseño de customer_auto

 <?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/customerNameLabel" android:layout_width="fill_parent" android:layout_height="fill_parent" android:padding="10dp" android:textSize="16sp" android:textColor="#000"> </TextView> 

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 la caja de sugestión automática no filtra correctamente. Por ejemplo; Si escribo an en la caja de prueba los clientes que comienzan con br están apareciendo!

Tengo que sobrevalorar el método getFilter () del adaptador

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

 public class CustomerAdapter extends ArrayAdapter<Customer> { private final String MY_DEBUG_TAG = "CustomerAdapter"; private ArrayList<Customer> items; private ArrayList<Customer> itemsAll; private ArrayList<Customer> suggestions; private int viewResourceId; public CustomerAdapter(Context context, int viewResourceId, ArrayList<Customer> items) { super(context, viewResourceId, items); this.items = items; this.itemsAll = (ArrayList<Customer>) items.clone(); this.suggestions = new ArrayList<Customer>(); 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<Customer> filteredList = (ArrayList<Customer>) 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 utiliza 3 separar, confundir ArrayLists) que el aceptado, y tiene más opciones.

Aquí es lo que he trabajado después de un poco de ensayo y error. Debe funcionar incluso si el usuario escribe retroceso, ya que no elimina las entradas originales de mCustomers :

 public class CustomerAdapter extends ArrayAdapter<Customer> { private LayoutInflater layoutInflater; List<Customer> 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<Customer> suggestions = new ArrayList<Customer>(); 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<Customer>) results.values); } else { // no filter, add entire original list back in addAll(mCustomers); } notifyDataSetChanged(); } }; public CustomerAdapter(Context context, int textViewResourceId, List<Customer> customers) { super(context, textViewResourceId, customers); // copy all the customers into a master list mCustomers = new ArrayList<Customer>(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 el código anterior publisHResults() método da la excepción de modificación concurrente …. tenemos que modificar el código como:

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

Tal vez esto es demasiado tarde, usted no necesita anular 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; } 

Considere cambiar:

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

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

  private ArrayList<Customer> items; 

Porque ArrayAdapter trabaja con sus propios mObjects, y filtrar esta lista no su lista de artículos, por lo que debe utilizar la función getItem para acceder a los elementos. Entonces no hay razón para escribir su ArrayFilter.

No sé dónde recuperar el getResult. Creo que la solución en este caso para no tener la modificación concurrente es:

 @Override protected void publishResults(CharSequence constraint, FilterResults results) { ArrayList<Customer> filteredList = (ArrayList<Customer>) results.values; ArrayList<Customer> customerList=new ArrayList<Customer>(); 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<Customer> customerIterator=customerList.iterator(); while (customerIterator.hasNext()) { Customer customerIterator=customerIterator.next(); add(customerIterator); } notifyDataSetChanged(); } } 

En lugar de reemplazar el método getFilter () en el adaptador, simplemente podemos sobrescribir el toString () del objeto userDefined (Customer). En toString () sólo devolver el campo sobre la base de lo que necesita filter.It trabajado para mí. En mi ejemplo Im filtrado basado en nombres.

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

Tengo no actualización y modificar los problemas de la lista original de la respuesta anterior. He arreglado este problema con estos códigos.

 public class AdapterAutoCompleteTextView extends ArrayAdapter<ItemWord> { private int LayoutID; private int TextViewID; private LayoutInflater Inflater; private List<ItemWord> ObjectsList; public AdapterAutoCompleteTextView(Context ActivityContext, int ResourceID, int TextViewResourceID, List<ItemWord> WordList) { super(ActivityContext, ResourceID, TextViewResourceID, new ArrayList<ItemWord>()); 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<ItemWord> OriginalValues = new ArrayList<ItemWord>(ObjectsList); if(Constraint == null || Constraint.length() == 0){ ResultsFilter.values = OriginalValues; ResultsFilter.count = OriginalValues.size(); } else { String PrefixString = Constraint.toString().toLowerCase(); final ArrayList<ItemWord> NewValues = new ArrayList<ItemWord>(); 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<ItemWord>) 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<ItemWord>()); 

Particularmente aquellos

Super (ActivityContext, ResourceID, TextViewResourceID, nuevo ArrayList () );

Espero que esta solución sea de ayuda 🙂

Si obtiene la excepción ConcurrentModificationException.

Reemplace ArrayList con el encabezado seguro CopyOnWriteArrayList.

Aquí puede encontrar detatils.

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

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

 public class TagSuggestionsAdapter extends ArrayAdapter<String> implements Filterable 

Extender ArrayAdapter para que tenga un código menor. Implementación de Filterable para cambiar el comportamiento del filtro más adelante.

  private List<String> allTags; private List<String> tagSuggestions; private Context context; public TagSuggestionsAdapter(List<String> initialTagSuggestions, List<String> 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 que necesita para pasar una lista que se mostrará inicialmente – que más adelante se convertirá en una lista con los resultados filtrados (esto también es una referencia a una lista que se tendrá en cuenta al llamar a notifyDataSetChanged ()) y, obviamente, una lista En el que puede basar su filtrado (allTags 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); } } 

Encima de usted puede ver un patrón simple del sostenedor de la visión con una poca ayuda de Butterknife para inflar una disposición de fila de encargo.

  @NonNull @Override public Filter getFilter() { return new Filter() { @Override protected FilterResults performFiltering(CharSequence constraint) { if (constraint != null) { List<String> 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 calumnioso que pude escribir. Su única preocupación es el método filterTagSuggestions que debe devolver una lista filtrada de etiquetas basada en la entrada del usuario ( CharSequence constraint ). Espero que resumió y organizó información necesaria un poco.

  • Cómo mostrar las sugerencias de AutoCompleteTextView en orientación horizontal
  • Cómo resolver "El contenido del adaptador ha cambiado pero ListView no ha recibido una notificación" excepción
  • Anulando filterresults en android autocompletetextview?
  • Estilo en la lista desplegable AutoCompleteTextView
  • "Android: dropDownAnchor" no funciona en Android Nougat (API 24)
  • AutocompleteTextView dinámico con ArrayAdapter y TextWatcher
  • Implementación de SearchView en la barra de acción
  • Cómo evitar que se llamen ambos: onItemClicked y onTextChanged en AutoCompleteTextView
  • Android: Utilizar el elemento seleccionado en AutoCompleteTextView para rellenar otro campo
  • Cómo personalizar el menú desplegable AutoCompleteTextView en Android
  • Diacríticos / caracteres internacionales en AutoCompleteTextView
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.