web-dev-qa-db-fra.com

Android AutoCompleteTextView avec le filtrage de l'adaptateur personnalisé ne fonctionne pas

J'ai l'adaptateur client personnalisé

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;
    }
}

et customer_auto disposition

<?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>

et sur mon public void onCreate

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

et 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;
    }

}

Mais la boîte de suggestion automatique ne filtre pas correctement. par exemple; si je tape an dans la boîte de test, les clients commençant par br apparaissent!

44
Mithun Sreedharan

Je dois contourner la méthode getFilter () de l'adaptateur

Voici le code qui a fonctionné pour moi, grâce à 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();
            }
        }
    };

}
114
Mithun Sreedharan

C'est ma solution. Je pense que c'est un peu plus propre (n'utilise pas 3 ArrayLists distinctes et déroutantes) que celui accepté, et a plus d'options. Cela devrait fonctionner même si l'utilisateur tape le retour arrière, car il ne supprime pas les entrées originales de mCustomers (contrairement à la réponse acceptée):

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;
    }
}
54
Carl Anderson

Au lieu de remplacer la méthode getFilter() dans l'adaptateur, nous pouvons simplement remplacer la toString() de l'objet userDefined (Customer). Dans toString() retourne simplement le champ en fonction de ce que vous devez filtrer. Ça a marché pour moi.

Dans mon exemple, je filtre en fonction des noms:

public class Customer{
    private int id;
    private String name;

    @Override
    public String toString() {
        return this.name;
    }
}
7
kavinraj M

Dans le code ci-dessus, la méthode publisHResults() donne l'exception de modification simultanée .... nous devons modifier le code comme suit:

@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();
    }
}
3
Shivani Sangewar

C'est peut-être trop tard, vous n'avez pas besoin de remplacer toutes ces fonctions, la seule fonction à remplacer est:

  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;
}

considère que je change:

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

faites attention, vous ne devez pas déclarer de nouveaux ListItems,

 private ArrayList<Customer> items;

car ArrayAdapter fonctionne avec ses propres mObjects et filtre cette liste et non votre liste d'éléments. Vous devez donc utiliser la fonction getItem pour accéder aux éléments. alors il n'y a aucune raison d'écrire votre ArrayFilter.

2
Mehrdad Faraji

J'espère que ce message aidera les gens à implémenter une fonctionnalité personnalisée similaire à l'avenir. J'ai basé cela sur ma version de l'adaptateur utilisé pour afficher les suggestions de balises dans mon application de microblogging:

public class TagSuggestionsAdapter extends ArrayAdapter<String> implements Filterable

Étendre ArrayAdapter pour avoir moins de code passe-partout. Implémentation de Filtrable pour changer le comportement du filtre ultérieurement.

    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;
}

Fondamentalement, dans le constructeur, vous devez passer une liste qui sera affichée initialement - elle deviendra plus tard une liste avec des résultats filtrés (c'est également une référence à une liste qui sera prise en considération lors de l'appel de notifyDataSetChanged ()) et évidemment une liste sur lequel vous pouvez baser votre filtrage (allTags dans mon cas). Je passe également Context pour l'inflation de la mise en page dans 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);
        }
    }

Ci-dessus, vous pouvez voir un modèle de support de vue simple avec un peu d'aide de Butterknife pour gonfler une disposition de ligne personnalisée.

 @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();
            }
        };
    }

C'est le code le moins standard que j'ai pu écrire. Votre seule préoccupation est la méthode filterTagSuggestions qui devrait renvoyer une liste filtrée de balises basée sur les entrées de l'utilisateur (CharSequence constraint). J'espère que les informations nécessaires résumées et organisées un peu.

1
matdziu

Je ne sais pas où vous récupérez le getResult. Je pense que la solution dans ce cas pour ne pas avoir la modification simultanée est:

@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();
    }
}

J'ai non-mise à jour et modifier la liste d'origine problèmes de la réponse ci-dessus. J'ai résolu ce problème avec ces codes.

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;
    }

}

C'est la ligne la plus importante pour les problèmes de non-mise à jour et de modification de la liste d'origine:

super(ActivityContext, ResourceID, TextViewResourceID, new ArrayList<ItemWord>());

en particulier ceux

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

J'espère que cette solution vous aidera:)

0
Abdulkadir NURKALEM

Si vous obtenez l'exception ConcurrentModificationException .

Remplacez ArrayList par le thread safe CopyOnWriteArrayList.

Ici vous pouvez trouver des détails réponse

0
Zoltán Buzás