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);
и 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;
}
}
Но окно автоматического предложения не фильтрует правильно. например, если я набираю an
в тестовом поле, то появляются клиенты, начинающиеся с br
!
Ответы
Ответ 1
Мне нужно переопределить метод 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();
}
}
};
}
Ответ 2
Это мое решение. Я чувствую, что он немного чище (не использует 3 отдельных, запутывающих ArrayLists), чем принятый, и имеет больше возможностей.
Вот что я разработал после нескольких проб и ошибок. Он должен работать, даже если пользователь вводит 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;
}
}
Ответ 3
В приведенном выше коде 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();
}
}
Ответ 4
Возможно, это слишком поздно, вам не нужно переопределять все эти функции, единственная функция для переопределения:
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.
Ответ 5
Вместо того, чтобы переопределять метод getFilter() в адаптере, мы можем переопределить toString() объекта userDefined (Customer). В toString() просто верните поле в зависимости от того, что вам нужно для фильтрации. Он работал у меня. В моем примере im фильтруется на основе имен.
public class Customer{
private int id;
private String name;
@Override
public String toString() {
return this.name;
}
}
Ответ 6
Я не знаю, откуда вы извлекаете 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();
}
}
Ответ 7
У меня есть не обновлять и изменить исходный список. Я исправил эту проблему с помощью этих кодов.
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, новый ArrayList());
Я надеюсь, что это решение поможет вам:)
Ответ 8
Если вы получаете исключение ConcurrentModificationException.
Замените ArrayList безопасным потоком CopyOnWriteArrayList.
Здесь вы можете найти detatils.
https://examples.javacodegeeks.com/java-basics/exceptions/java-util-concurrentmodificationexception-how-to-handle-concurrent-modification-exception/
Ответ 9
Я надеюсь, что этот пост поможет людям с реализацией аналогичных пользовательских функций в будущем. Я основывался на этой версии адаптера, используемой для отображения предложений тегов в моем приложении для микроблогов:
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 в моем случае). Я также передаю контекст для инфляции макета в 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
). Надеюсь, что немного обобщил и организовал необходимую информацию.