【问题标题】:Android AutoCompleteTextView with Custom Adapter filtering not working带有自定义适配器过滤的 Android AutoCompleteTextView 不起作用
【发布时间】:2012-01-09 05:21:50
【问题描述】:

我有自定义 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 的列表。它可能有错误的数据。从哪里生成该列表?
  • 不,customerList 的数据正确,当我在 Spinner 中使用相同的数据时,它显示正确
  • 您是否在显示在建议框中之前将它们打印出来?就在 setText 之前?这可能会提供一些线索。以及我建议卸载并重新安装该应用程序。

标签: android autocompletetextview


【解决方案1】:

我必须重写 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() 方法,应该用于过滤器中的比较。
  • 是的,它确实修改了原始列表。需要退房
  • 原来的列表在哪里被修改了?我想我遇到了这个问题,无法弄清楚它发生在哪里。
  • @Mithun 您是否打算在此处修复您的答案以合并此处列出的其他“答案”的反馈?
  • 这是另一个最佳解决方案! stackoverflow.com/questions/19820736/…
【解决方案2】:

这是我的解决方案。我觉得它比公认的更干净(不使用 3 个单独的、令人困惑的 ArrayLists),并且有更多选择。即使用户键入退格,它也应该可以工作,因为它不会从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;
    }
}

【讨论】:

  • 这个方案很容易理解。
  • 有史以来最好的解决方案
  • 这段代码显示了建议中的所有数据?在自动完成下拉我的意思是
  • getView 的意义何在?我worked upon your code,由于我没有得到getView 在那里所做的事情,所以我没有复制它,没有它我很好。
  • @mariotomo 它可能在那里,因为它是原始代码集的一部分。此时我的答案是 3.5 年,我没有原始测试项目,我无法验证 getView 是否需要覆盖。
【解决方案3】:

我们可以简单地覆盖用户定义对象(客户)的toString(),而不是覆盖适配器中的getFilter()方法。 在toString() 中,只需根据您需要过滤的内容返回该字段。它对我有用。

在我的示例中,我根据名称进行过滤:

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

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

【讨论】:

  • “最简单的事情往往是最真实的。” ——理查德·巴赫
  • 最简单的解决方案
【解决方案4】:

上面代码中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();
    }
}

【讨论】:

  • 您从哪里检索 getResult 方法?
【解决方案5】:

也许为时已晚,你不需要重写所有这些函数,唯一要重写的函数是:

  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
【解决方案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 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() 中传递 Context 以进行布局膨胀。

       @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)返回过滤后的标签列表。希望把必要的信息总结整理一下。

    【讨论】:

      【解决方案8】:

      如果你得到 ConcurrentModificationException 异常。

      ArrayList 替换为线程安全的 CopyOnWriteArrayList。

      在这里你可以找到详细信息answer

      【讨论】:

        【解决方案9】:

        我有非更新修改原始列表上述答案的问题。我用这段代码解决了这个问题。

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

        我希望这个解决方案能帮到你:)

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2023-03-19
          • 1970-01-01
          • 2013-10-07
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多