Android AutoCompleteTextView с фильтрацией пользовательского адаптера не работает

У меня есть Custom CustomerAdapter

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

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

и на моем public void onCreate

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

и клиент.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;
    }

}

Но окно автоматического предложения фильтрует неправильно. например; если я наберу an в тестовом поле, появятся клиенты, начинающиеся с br!


person Mithun Sreedharan    schedule 09.01.2012    source источник
comment
Я думаю, вам нужно проверить список, который вы передаете CustomerAdapter. Возможно, у него неверные данные. Откуда вы генерируете этот список?   -  person kosa    schedule 09.01.2012
comment
Нет, customerList имеет правильные данные, когда я использовал то же самое в Spinner, он показал правильно   -  person Mithun Sreedharan    schedule 09.01.2012
comment
Вы распечатывали их перед отображением в ящике для предложений? прямо перед setText? это может дать некоторую подсказку. Также я бы посоветовал удалить и переустановить приложение.   -  person kosa    schedule 09.01.2012


Ответы (9)


Мне нужно переопределить getFilter() метод адаптера

Вот код, который у меня сработал благодаря 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();
            }
        }
    };

}
person Mithun Sreedharan    schedule 09.01.2012
comment
Большое спасибо, что указали мне на фильтр. ИМХО, это огромная ошибка проектирования, что так много работы по переопределению метки, что должен быть простой метод getLable(), который следует использовать для сравнения в фильтре. - person rekire; 08.10.2013
comment
Да, действительно, он изменяет исходный список. нужно проверить - person Mohammed Azharuddin Shaikh; 12.12.2013
comment
Где изменяется первоначальный список? Я думаю, что столкнулся с этой проблемой и не могу понять, где это происходит. - person Mike Richards; 07.08.2014
comment
@Mithun, вы когда-нибудь собираетесь исправить свой ответ здесь, чтобы включить отзывы из других ответов, перечисленных здесь? - person Carl Anderson; 05.11.2014
comment
Вот еще одно лучшее решение, ребята! stackoverflow.com/questions/19820736 / - person nAkhmedov; 28.11.2014
comment
Здорово! И вы можете вызывать веб-сервис в performFiltering(), потому что он работает в рабочем потоке! - person Aron Lorincz; 17.07.2015
comment
ваш результат фильтрации/публикации работает неправильно, потому что при попытке удалить письмо не обновляется правильно! - person ; 13.11.2015
comment
У меня такая же проблема с обновлением, как и у вас, и я исправил проблему с исходным списком modidy. Мое решение ниже. Я надеюсь, что это поможет для вас :) - person Abdulkadir NURKALEM; 25.01.2016
comment
исходный список изменен, осталась одна запись. как это исправить. - person ralphgabb; 18.10.2016
comment
У меня такая же проблема, может ли кто-нибудь изучить мой вопрос? - person Moeez; 31.10.2017
comment
Вы не можете изменить коллекцию, перебирая ее с помощью Iterator - person Alireza Noorali; 09.12.2018

Это мое решение. Я чувствую, что он немного чище (не использует 3 отдельных, запутанных ArrayList), чем принятый, и имеет больше возможностей. Это должно работать, даже если пользователь вводит backspace, потому что он не удаляет исходные записи из 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;
    }
}
person Carl Anderson    schedule 06.11.2014
comment
Это решение легко понять. - person Seenu69; 03.09.2015
comment
лучшее решение - person Sagar Chavada; 22.09.2016
comment
этот код показывает все данные в предложении? в раскрывающемся списке автозаполнения я имею в виду - person umerk44; 07.03.2017
comment
в чем смысл getView? Я поработал над вашим кодом, и, поскольку я не понял, что там делает getView, я не копировал его, а мне и без этого хорошо. - person mariotomo; 03.04.2018
comment
@mariotomo, вероятно, там, потому что он был частью исходного набора кода. На данный момент моему ответу 3,5 года, у меня нет исходного тестового проекта, и я не могу проверить, необходимо ли переопределение getView или нет. - person Carl Anderson; 04.04.2018
comment
mCustomer должен быть копией исходного списка, а не ссылкой на него. Именно так работает ArrayAdapter. - person YuTang; 29.09.2019
comment
идеальное решение - person CrazyMind; 24.02.2020
comment
Вызовите так - - - Адаптер CustomerAdapter = new CustomerAdapter(mContext, R.layout.autocomplete_list_item, customerList); - person CrazyMind; 24.02.2020

Вместо переопределения метода getFilter() в адаптере мы можем просто переопределить toString() объекта userDefined (Customer). В toString() просто верните поле на основе того, что вам нужно отфильтровать. Это сработало для меня.

В моем примере я фильтрую по именам:

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

    @Override
    public String toString() {
        return this.name;
    }
}
person kavinraj M    schedule 25.10.2016
comment
«Самые простые вещи часто бывают самыми верными». — Ричард Бах - person alizeyn; 21.05.2019
comment
Самое простое решение - person Ranjan; 31.08.2019

В приведенном выше коде метод publisHResults() дает исключение одновременной модификации.... мы должны изменить код как:

@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();
    }
}
person Shivani Sangewar    schedule 06.09.2012
comment
откуда вы получаете метод getResult? - person krisDrOid; 03.06.2013

Может быть, уже слишком поздно, вам не нужно переопределять все эти функции, единственная функция, которую нужно переопределить, это:

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

считай меняю:

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

обратите внимание, вы не должны объявлять новые ListItems,

 private ArrayList<Customer> items;

потому что ArrayAdapter работает со своими собственными mObjects и фильтрует этот список, а не список ваших элементов, поэтому вы должны использовать функцию getItem для доступа к элементам. тогда нет причин писать свой ArrayFilter.

person Mehrdad Faraji    schedule 24.01.2016
comment
Это правильный ответ, результаты неверны, поскольку элементы фильтруются в списке базового класса, а не в пользовательском списке членов. После удаления члена items и использования getItem(pos) результаты будут правильными, как если бы использовался исходный ArrayAdapter. - person SagiLow; 27.01.2017

Я не знаю, где вы получаете getResult. Я думаю, что решение в этом случае для отсутствия одновременной модификации:

@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();
    }
}
person Jachumbelechao Unto Mantekilla    schedule 31.07.2013

Я надеюсь, что этот пост поможет людям с реализацией аналогичного пользовательского функционала в будущем. Я основывался на своей версии адаптера, используемого для отображения предложений тегов в моем приложении для микроблогов:

public class TagSuggestionsAdapter extends ArrayAdapter<String> implements Filterable

Расширение ArrayAdapter для уменьшения шаблонного кода. Реализация Filterable для изменения поведения фильтра позже.

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

В основном в конструкторе вам нужно передать список, который будет отображаться изначально - позже он станет списком с отфильтрованными результатами (это также ссылка на список, который будет учитываться при вызове notifyDataSetChanged()) и, очевидно, список на котором вы можете основывать свою фильтрацию (allTags в моем случае). Я также передаю Context для инфляции макета в 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);
        }
    }

Выше вы можете увидеть простой шаблон держателя представления с небольшой помощью Butterknife, чтобы раздуть пользовательский макет строки.

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

Это наименьший шаблонный код, который я мог написать. Вас беспокоит только метод filterTagSuggestions, который должен возвращать отфильтрованный список тегов на основе ввода пользователя (CharSequence constraint). Надеюсь, что немного обобщил и систематизировал необходимую информацию.

person matdziu    schedule 10.03.2017

Если вы получаете исключение ConcurrentModificationException.

Замените ArrayList на потокобезопасный CopyOnWriteArrayList.

Здесь вы можете найти подробности ответить

person Zoltán Buzás    schedule 17.01.2017

У меня есть проблемы необновления и изменения исходного списка из приведенного выше ответа. Я исправил эту проблему с помощью этих кодов.

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

}

Это наиболее важная строка для решения проблемы, связанной с отсутствием обновления и изменения исходного списка:

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

Особенно те

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

Надеюсь, это решение поможет вам :)

person Abdulkadir NURKALEM    schedule 25.01.2016