【问题标题】:AutoCompleteTextView custom ArrayAdapter & Filter performanceAutoCompleteTextView 自定义 ArrayAdapter & Filter 性能
【发布时间】:2014-09-23 08:29:12
【问题描述】:

我有一个带有自定义 ArrayAdapter 的 AutoCompleteTextView,它使用 ArrayList<Product>

我得出的结论是 AutoCompleteTextView 的自定义 ArrayAdapter 必须 implements Filterable 并且您必须进行自己的过滤。

this SO-question & accepted answerthis example,我制作了以下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;
    }
}

一切都很完美。但是,由于我有一个大约 1250 个产品的列表,并且每次用户更改他在 AutoCompleteTextView 中的输入时,这些产品都会循环,包括创建两个新实例(FilterResults 和 ArrayList),我想知道是否有更好的解决方案这无需循环每次用户输入更改的所有内容。

如果没有,我就保留这个。我只是想知道,因为使用包含大约 1250 个对象的 AutoCompleteTextView、自定义 ArrayAdapter(包括自定义过滤)和自定义 TextWatcher,它对性能来说并不是那么好。特别是因为这个 AutoCompleteTextView 是在 ListView 的项目内使用的。这意味着我对每个项目都有一个 AutoCompleteTextView(可能范围从 ~ 5 到 50,平均约为 15)。

【问题讨论】:

    标签: android performance autocomplete android-arrayadapter autocompletetextview


    【解决方案1】:

    这来得太晚了,但是我想我会权衡一下您的问题...主要是因为您的实施发生了相当可怕的事情。要回答您的直接问题,在过滤时您可以轻松避免完整的ArrayList 迭代。如果您需要更快的东西,您需要考虑将数据预处理为具有更快搜索时间的东西。 AutoComplete Algorithm?.

    我有一个自定义ArrayAdapter 过滤逻辑的一般经验法则。不要这样做。每当您遇到这种情况时,正确的解决方案是推出您自己的适配器解决方案(使用BaseAdapter)...或者找到一个也允许您使用的3rd party solution。部分问题是ArrayAdapter 在内部有自己的两个过滤列表和自己的内部同步锁。您的AutoCompleteAdapter 暴露了大量的突变体,所有这些突变体都在您无法同步的对象上同步。这意味着如果在过滤发生时适配器发生突变,您将面临并发问题的风险。

    就您的代码而言,ArrayAdapter 与您的productsAll 列表相关联。任何突变、访问器、方法等都将始终引用该列表。起初我很惊讶你的解决方案奏效了!然后我意识到你没有像规范那样使用getItem。我想您完全忽略了所有其他 ArrayAdapter 方法,否则您会看到相当奇怪的行为。如果是这样的话,ArrayAdapter 并没有真正为你做任何事情,而且你正在加载这个庞大的类。使用BaseAdapter 将其切换出来是微不足道的。

    事实上,我很惊讶你没有看到其他奇怪的问题。例如,无论您的过滤列表显示什么,您的适配器始终注册productsAll 列表计数而不是productsShown 计数。这可能是为什么您要进行所有这些索引越界检查的原因?通常不需要。

    我也很惊讶您的过滤操作会更新列表,因为您在完成时未能调用 notifyDataSetChanged

    下一个大问题,你不应该嵌套适配器。我通常提倡这一点,因为人们嵌入了ListViews...这本身就是另一个不。这是我第一次听说使用AutoCompleteTextView 嵌套。情况略有不同,但我仍然认为这是一个坏主意。为什么?无法保证给定位置会调用多少次getView。它可以调用它一次……它可以调用它 4 次……或更多。因此,想象一下每个项目重新创建适配器 4 次。即使一次只显示 10 个项目,您正在查看自定义适配器的 40 个实例!我当然希望你找到一种方法来回收这些适配器以降低这个数字。

    但是考虑到您没有使用ViewHolder,我假设您甚至不知道回收行为? ViewHolder 是任何适配器都必须做的。它可以轻松地提供巨大的性能夸耀。现在,您正在使用每个 getView 调用创建一个新视图,并忽略提供的任何回收视图。网上有一百万个例子显示和解释了ViewHolder。这是一个这样的link

    旁注,ArrayAdapter 已经实现了Filterable。不需要在您的自定义适配器中重新添加工具。

    总结一下:

    • 改用BaseAdapter
    • 不要嵌入适配器。寻找一种不同的方式来显示您的 UI,而无需在 ListView 内使用多个 AutoCompleteTextViews
    • 如果没有大量的数据预处理,就无法真正改进您的过滤逻辑。
    • 使用 ViewHolder 范例。

    必须观看来自 Google I/O 的 video 关于 ListViews 和适配器的信息。 这里还有一些关于ArrayAdapterreadings

    【讨论】:

    • 目前我正在使用 C# WPF 应用程序,但在阅读了您的帖子后,您确实得到了一些好处。关于 ViewHolder 模式,后来我意识到我在这里也错过了它之后才实施。这段代码中的算法是个好主意;数据已经排序,所以我可以在排序后的数据上使用算法来比 O(N) 更快地搜索。尽管如此,我还是无法真正实现您的想法,因为我正在从事的项目已经结束(这是我的毕业项目),而我目前正在实际工作。不过,你的想法是有根据的,所以我接受了..
    猜你喜欢
    • 2012-01-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-20
    • 1970-01-01
    • 2014-04-21
    • 2013-02-20
    • 2011-11-13
    相关资源
    最近更新 更多