简体   繁体   English

AutoCompleteTextView自定义ArrayAdapter和过滤器性能

[英]AutoCompleteTextView custom ArrayAdapter & Filter performance

I have an AutoCompleteTextView with a custom ArrayAdapter that uses ArrayList<Product> . 我有一个带有使用ArrayList<Product>的自定义ArrayAdapter的AutoCompleteTextView。

I've came to the conclusion that a custom ArrayAdapter of an AutoCompleteTextView must implements Filterable and you have to make your own Filtering. 我得出的结论是,AutoCompleteTextView的自定义ArrayAdapter必须implements Filterable并且您必须进行自己的Filtering。

From this SO-question & accepted answer and this example , I have made the following ArrayAdapter: 这个SO问题和可接受的答案以及这个示例 ,我制作了以下ArrayAdapter:

public class AutoCompleteAdapter extends ArrayAdapter<Product> implements Filterable
{
    // Logcat tag
    private static final String TAG = "AutoCompleteAdapter";

    // The OrderedProductItem we need to get the Filtered ProductNames
    OrderedProductItem orderedProductItem;

    private Context context;
    private ArrayList<Product> productsShown, productsAll;

    // Default Constructor for an ArrayAdapter
    public AutoCompleteAdapter(Context c, int layoutId, ArrayList<Product> objects, OrderedProductItem opi){
        // Though we don't use the Layout-ResourceID , we still need it for the super
        super(c, layoutId, objects);

        L.Log(TAG, "AutoCompleteAdapter Constructor", LogType.VERBOSE);

        // ArrayAdapter's setNotifyOnChange is true by default,
        // but I set it nonetheless, just in case
        setNotifyOnChange(true);

        context = c;
        replaceList(objects, true);
        orderedProductItem = opi;
    }

    // Setup the ListItem's UI-elements
    @Override
    public View getView(int position, View convertView, ViewGroup parent){
        return createTextViewAsItem(position);
    }
    @Override
    public View getDropDownView(int position, View convertView, ViewGroup parent){
        return createTextViewAsItem(position);
    }
    // To prevent repetition, we have this private method
    private TextView createTextViewAsItem(int position){
        TextView label = new TextView(context);
        String name = "";
        if(productsShown != null && productsShown.size() > 0 && position >= 0 && position < productsShown.size() - 1)
            name = productsShown.get(position).getName();
        label.setText(name);

        return label;
    }

    // Replace the List
    // When the boolean is set, we replace this ArrayAdapter's List entirely,
    // instead of just the filtering
    @SuppressWarnings("unchecked")
    public void replaceList(ArrayList<Product> p, boolean replaceInitialList){
        if(p != null && p.size() > 0){
            productsShown = p;
            if(replaceInitialList)
                productsAll = (ArrayList<Product>)productsShown.clone();
            notifyDataSetChanged();
        }
    }

    // Since we are using an AutoCompleteTextView, the Filtering has been reset and we need to apply this ourselves..
    Filter filter = new Filter(){
        @Override
        public String convertResultToString(Object resultValue){
            return ((Product)resultValue).getName();
        }

        @Override
        protected FilterResults performFiltering(CharSequence constraint){
            FilterResults filterResults = new FilterResults();
            if(productsAll != null){
                // If no constraint is given, return the whole list
                if(constraint == null){
                    filterResults.values = productsAll;
                    filterResults.count = productsAll.size();
                }
                else if(V.notNull(constraint.toString(), true)){
                    L.Log(TAG, "performFiltering: " + constraint.toString(), LogType.VERBOSE);

                    ArrayList<Product> suggestions = new ArrayList<Product>();

                    if(p.size() > 0)
                        for(Product p : productsAll)
                            if(p.getName().toLowerCase(Locale.ENGLISH).contains(constraint.toString().toLowerCase(Locale.ENGLISH)))
                                suggestions.add(p);

                    filterResults.values = suggestions;
                    filterResults.count = suggestions.size();
                }
            }
            return filterResults;
        }

        @SuppressWarnings("unchecked")
        @Override
        protected void publishResults(CharSequence constraint, FilterResults results) {
            if(results != null && results.count > 0)
                replaceList((ArrayList<Product>)results.values, false);
        }
    };

    @Override
    public Filter getFilter(){
        return filter;
    }
}

Everything works perfectly. 一切正常。 However, since I have a list of around 1250 Products and these are all looped every time the User changes his input in the AutoCompleteTextView, including the creation of two new instantiations (FilterResults and ArrayList), I was wondering if there is a better solution for this without having to loop though everything on every user input change. 但是,由于我有大约1250种产品的列表,并且每次用户在AutoCompleteTextView中更改其输入时都会循环这些产品,包括创建两个新的实例化(FilterResults和ArrayList),我想知道是否有更好的解决方案不必循环遍历每个用户输入上的所有内容。

If there isn't I just keep this. 如果没有,我就保留它。 I was just wondering since with an AutoCompleteTextView containing around 1250 objects, with a custom ArrayAdapter (including custom Filtering) and a custom TextWatcher, it isn't that good for the performance. 我只是想知道,因为包含约1250个对象的AutoCompleteTextView,自定义ArrayAdapter(包括自定义Filtering)和自定义TextWatcher,对性能没有好处。 Especially since this AutoCompleteTextView is used inside the item of a ListView. 特别是因为此AutoCompleteTextView在ListView的项内使用。 Which means I have an AutoCompleteTextView for every item (potentially ranging from ~ 5 to 50, with an average of around 15). 这意味着我为每个项目都有一个AutoCompleteTextView(可能在〜5到50之间,平均约为15)。

This is coming fairly late, however I thought I'd weigh in on your problem...mostly because the rather scaring things occurring with your implementation. 这来得很晚,但是我想我会考虑您的问题...主要是因为在您的实现中发生了一些令人恐惧的事情。 To answer your immediate question, there's not much you can easily do to avoid the full ArrayList iteration when filtering. 要回答您的紧迫问题,在过滤时要避免完整的ArrayList迭代,您无需做太多事情。 If you need something faster, you'll need to look into pre-processing your data into something with faster search times. 如果您需要更快的速度,则需要研究将数据预处理为搜索速度更快的速度。 AutoComplete Algorithm? 自动完成算法? .

I have a general rule of thumb for customizing the ArrayAdapter filtering logic. 我有一个自定义ArrayAdapter过滤逻辑的一般经验法则。 Don't do it. 不要这样 Whenever you run into this situation, the correct solution is to roll your own adapter solution (Using BaseAdapter )...or find a 3rd party solution that allows you too. 每当遇到这种情况时,正确的解决方案是推出自己的适配器解决方案(使用BaseAdapter )...或寻找一个也允许您使用的第三方解决方案 Part of the issue is that internally the ArrayAdapter has it's own two lists for filtering and it's own internal synchronized lock. 问题的部分原因是ArrayAdapter在内部具有自己的两个列表进行过滤,并且具有自己的内部同步锁。 Your AutoCompleteAdapter is exposing a ton of mutators, all of which synchronize on an object you can't sync on. 您的AutoCompleteAdapter公开了大量的变种器,所有变种器都在您无法同步的对象上进行同步。 That means you risk concurrency issues if the adapter is mutated while filtering is occurring. 这意味着,如果在进行过滤时适配器发生了突变,则可能会带来并发问题。

As it stands with your code, the ArrayAdapter is linked up with your productsAll list. 正如您的代码所示, ArrayAdapter与您的productsAll列表链接在一起。 Any mutations, accessors, methods etc will always reference that list. 任何突变,访问者,方法等都将始终引用该列表。 At first I was surprised your solution worked! 起初,我很惊讶您的解决方案有效! Then I realized you aren't using getItem as is the norm. 然后,我意识到您没有像正常情况那样使用getItem I imagine you are completely neglecting all the other ArrayAdapter methods, else you'd have seen rather strange behavior. 我想您会完全忽略所有其他ArrayAdapter方法,否则您将看到相当奇怪的行为。 If that's the case, ArrayAdapter isn't really doing anything for you and you're loading up this huge class for nothing. 如果真是这样, ArrayAdapter并没有为您做任何真正的事情,并且您正在免费加载这个庞大的类。 Would be trivial to switch it out with BaseAdapter . 使用BaseAdapter将其切换出来将是微不足道的。

In fact I'm surprised you aren't seeing other strange problems. 实际上,我很惊讶您没有看到其他奇怪的问题。 For instance, no matter what your filtered list shows, your adapter is always registering the productsAll list count instead of the productsShown count. 例如,无论您显示的过滤列表是什么,适配器总是在注册productsAll列表计数而不是productsShown计数。 Which may be why you have all these index out of bounds checks? 这可能是为什么您需要所有这些索引越界检查的原因? Typically not needed. 通常不需要。

I'm also surprised your filtering operation updates the list since you fail to invoke notifyDataSetChanged when finished. 我也很惊讶您的筛选操作会更新列表,因为完成后您无法调用notifyDataSetChanged

Next big problem, you should never nest adapters. 下一个大问题,您永远不要嵌套适配器。 I'm usually advocating this because people embed ListViews ...which is another no no of itself. 我之所以提倡这一点,是因为人们嵌入了ListViews ...这本身就是另外一个问题。 This is the first I've heard about nesting with AutoCompleteTextView though. 不过,这是我第一次听说与AutoCompleteTextView嵌套。 Slightly different situation, yet I'd still say this is a bad idea. 情况略有不同,但是我仍然会说这是一个坏主意。 Why? 为什么? There's no guarantee how many times getView will be invoked for a given position. 不能保证给定位置调用getView多少次。 It could call it once...it could call it 4 times...or more. 它可以调用一次...可以调用4次...或更多。 So imagine recreating your adapter 4 times per item. 因此,想象每个项目重新创建适配器4次。 Even if only 10 items display at a time, you're looking at 40 instantiations of your custom adapter! 即使一次仅显示10个项目,您也要查看自定义适配器的40个实例! I sure hope you figured out a way to recycle those adapters to lower that number. 我肯定希望您找到了一种回收这些适配器以降低该数量的方法。

However considering you aren't using the ViewHolder I'm assuming you don't even know about the recycling behavior? 但是考虑到您没有使用ViewHolder我假设您甚至不了解回收行为? ViewHolder is a must do for any adapter. ViewHolder是任何适配器的必备工具。 It single handily will provide an enormous performance boast. 它可以轻松提供巨大的性能优势。 Right now, you are creating a new view with every getView invocation and ignoring any of the recycled views provided. 现在,您将在每次getView调用时创建一个新视图,而忽略提供的任何回收视图。 There a million examples online that show and explain the ViewHolder . 网上有100万个显示和解释ViewHolder Here's one such link . 这是一个这样的链接

Side note, ArrayAdapter already implements Filterable . 旁注, ArrayAdapter已经实现了Filterable Re-adding the implements in your custom adapter is not needed. 不需要在自定义适配器中重新添加工具。

To sum up: 总结一下:

  • Implement BaseAdapter instead BaseAdapter
  • Don't embed adapters. 不要嵌入适配器。 Find a different way to display your UI without requiring multiple AutoCompleteTextViews inside of a ListView . 寻找一种不同的方式来显示UI,而无需在ListView内使用多个AutoCompleteTextViews
  • Can't really improve your filtering logic without some heavy data pre-processing. 如果不进行大量数据预处理,就无法真正改善您的过滤逻辑。
  • Use ViewHolder paradigm. 使用ViewHolder范例。

Must watch video from Google I/O about ListViews and adapters. 必须注意的视频来自谷歌I /约列表视图和适配器O操作。 Here's some further readings about the ArrayAdapter . 下面是一些进一步阅读有关ArrayAdapter

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM