Android AutoCompleteTextView与自定义适配器过滤不起作用。

54

我有自定义的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开头的客户会出现!


我认为你需要检查一下传递给CustomerAdapter的列表。它可能包含错误的数据。你是从哪里生成这个列表的? - kosa
不,customerList的数据是正确的,当我在一个Spinner中使用它时,显示也是正确的。 - Mithun Sreedharan
你在显示建议框之前有打印它们吗?就在setText之前?这可能会给一些线索。我还建议卸载并重新安装应用程序。 - kosa
9个回答

126

我必须重写Adapter的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();
            }
        }
    };

}

非常感谢您指引我使用过滤器。在我看来,这是一个巨大的设计错误,因为覆盖标签需要付出如此多的工作,应该有一个简单的getLable()方法,用于在过滤器中进行比较。 - rekire
是的,它确实修改了原始列表。需要检查一下。 - Mohammed Azharuddin Shaikh
3
原始列表被修改的地方在哪里?我认为我遇到了这个问题,但无法确定是在哪里发生的。 - Mike Richards
你的过滤/发布结果无法正常工作,因为当你尝试移除字母时,更新不正确! - user3402040
我和你一样遇到了同样的更新问题,但我解决了原始列表修改问题。我的解决方案如下。希望能对你有所帮助 :) - Abdulkadir NURKALEM
显示剩余6条评论

58

这是我的解决方案。我觉得它比被接受的方案更加简洁(不使用三个独立而混乱的ArrayList),并且有更多的选项。即使用户输入了退格键,它也应该可以工作,因为它不会从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;
    }
}

这个解决方案很容易理解。 - Seenu69
有史以来最佳解决方案 - Sagar Chavada
1
这段代码是否在自动完成下拉菜单中显示所有建议数据? - umerk44
getView的作用是什么?我根据您的代码进行了修改(https://stackoverflow.com/a/49631196/78912),由于我不知道`getView`在那里做了什么,所以我没有复制它,而且没有问题。 - mariotomo
@mariotomo 这可能是因为它是原始代码集的一部分。 目前我的回答已有3.5年历史了,我没有原始测试项目并且无法验证getView重写是否必要。 - Carl Anderson
显示剩余3条评论

12

不需要在适配器中覆盖getFilter()方法,我们可以直接覆盖用户自定义对象(Customer)的toString()方法。在toString()中,根据您需要过滤的内容返回相应字段即可。这种方法对我很有效。

在我的示例中,我是基于名称进行过滤的。

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

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

2
“最简单的事情往往是最真实的。” ——理查德·巴赫 - alizeyn
2
最简单的解决方案 - Ranjan

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

5
你从哪里获取了 getResult 方法? - krisDrOid

2
也许现在有点晚了,你不需要覆盖所有这些函数,唯一需要覆盖的函数是:
  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了。

这是正确的答案,结果不正确是因为项目是基于类列表而不是自定义成员列表进行过滤。一旦删除了“items”成员并使用“getItem(pos)”,结果将是正确的,就像使用原始的“ArrayAdapter”一样。 - SagiLow

1
如果您遇到 ConcurrentModificationException 异常。
请使用线程安全的 CopyOnWriteArrayList 替换 ArrayList
在这里您可以找到详细信息 answer

1

我不知道你是从哪里获取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();
    }
}

1

我希望这篇文章能够帮助未来需要实现类似自定义功能的人们。我基于我在微博应用中用于显示标签建议的适配器版本进行了编写:

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)返回一个经过筛选的标签列表。希望这能够概括和组织必要的信息。

0

我在上面的答案中遇到了无法更新修改原始列表的问题。我用以下代码解决了这个问题。

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

希望这个解决方案能对你有所帮助 :)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接