在AutoCompleteTextView中获取过滤后的数组大小

4

我正在处理一个用户可以搜索数据的项目。为此, 我已经实现了 AutoCompleteTextView

 autoComplete.setAdapter(new ArrayAdapter<String>(CheckRiskActivity.this,
                                        R.layout.auto_text_row, druglist));
                                autoComplete.setThreshold(1);
//druglist is my arraylist

以下是文本更改监听器:

autoComplete.addTextChangedListener(new TextWatcher() {

        @Override
        public void afterTextChanged(Editable s) {
            // here I want to get the size of filtered array list every time when the user adds any character.
        }

        @Override
        public void beforeTextChanged(CharSequence s, int start,
                                      int count, int after) {
        }

        @Override
        public void onTextChanged(CharSequence s, int start,
                                  int before, int count) {
        }
    });

解释:如果我的初始数组大小为100,如果用户输入'a',那么我想得到过滤后的数组大小。

注意:我已经尝试过autoComplete.getAdapter().getCount();,但它只能在添加一个字符后才给出实际结果。


请查看以下链接:https://stackoverflow.com/questions/30074316/efficient-way-of-finding-all-strings-of-an-arraylist-which-contains-a-substring - sumit
如果我每次文本更改都去查找子字符串,那么这将是一个耗时的过程。因此会导致结果延迟。 - Ronak Thakkar
让它更有效率,不要在每次字符串变化时都发送请求,可以在点击筛选结果前延迟1到2秒钟再发送请求。 - sumit
它已经实现了,但为了避免那1或2秒钟的时间,我正在寻找替代方案。 如果结果arralist大小为0,则只需显示“未找到数据”。因此,为此,我需要在每个文本更改时获取arraylist大小。 - Ronak Thakkar
@RonakThakkar,请查看我的回答并告诉我解决方案是否清晰。 - Ayaz Alifov
5个回答

5

由于过滤通常需要比TextWatcher事件监听器更长的时间,因此您无法在TextWatcher中获得正确的过滤项计数。因此,在afterTextChanged()中,您会得到不正确的autoComplete.getAdapter().getCount()。我建议使用自定义侦听器,每当过滤项更改时就会调用它。

我将提供2种类似的方法:使用单独的类和仅使用1个类。

方法1: 您的适配器应如下所示:

import android.content.Context;
import android.widget.ArrayAdapter;
import android.widget.Filter;
import java.util.ArrayList;
import java.util.List;

public class AutoCompleteAdapter extends ArrayAdapter
{
    private List<String>    tempItems;
    private List<String>    suggestions;
    private FilterListeners filterListeners;

    public AutoCompleteAdapter(Context context, int resource, List<String> items)
    {
        super(context, resource, 0, items);

        tempItems = new ArrayList<>(items);
        suggestions = new ArrayList<>();
    }

    public void setFilterListeners(FilterListeners filterFinishedListener)
    {
        filterListeners = filterFinishedListener;
    }

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

    Filter nameFilter = new Filter()
    {
        @Override
        protected FilterResults performFiltering(CharSequence constraint)
        {
            if (constraint != null)
            {
                suggestions.clear();
                for (String names : tempItems)
                {
                    if (names.toLowerCase().startsWith(constraint.toString().toLowerCase()))
                    {
                        suggestions.add(names);
                    }
                }
                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)
        {
            List<String> filterList = (ArrayList<String>) results.values;

            if (filterListeners != null && filterList!= null)
                filterListeners.filteringFinished(filterList.size());

            if (results != null && results.count > 0)
            {
                clear();
                for (String item : filterList)
                {
                    add(item);
                    notifyDataSetChanged();
                }
            }
        }
    };
}

一个用于通知您筛选完成的接口:
public interface FilterListeners
{
    void filteringFinished(int filteredItemsCount);
}

而且您可以使用它:

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.AutoCompleteTextView;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends Activity implements FilterListeners
{
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        AutoCompleteTextView autoComplete = (AutoCompleteTextView) findViewById(R.id.autoComplete);
        autoComplete.setThreshold(1);

        List<String> stringList = new ArrayList<>();
        stringList.add("Black");
        stringList.add("White");
        stringList.add("Yellow");
        stringList.add("Blue");
        stringList.add("Brown");


        final AutoCompleteAdapter adapter = new AutoCompleteAdapter(this, android.R.layout.simple_list_item_1, stringList);
        adapter.setFilterListeners(this);
        autoComplete.setAdapter(adapter);
    }

    @Override
    public void filteringFinished(int filteredItemsCount)
    {
        Log.i("LOG_TAG", " filteringFinished  count = " + filteredItemsCount);
    }
}

方法2:

    import android.app.Activity;
    import android.os.Bundle;
    import android.util.Log;
    import android.widget.ArrayAdapter;
    import android.widget.AutoCompleteTextView;
    import android.widget.Filter;
    import java.util.ArrayList;
    import java.util.List;

    public class MainActivity extends Activity
    {
        @Override
        protected void onCreate(Bundle savedInstanceState)
        {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);

            final AutoCompleteTextView autoComplete = (AutoCompleteTextView) findViewById(R.id.autoComplete);
            autoComplete.setThreshold(1);

            final List<String> stringList = new ArrayList<>();
            stringList.add("Black");
            stringList.add("White");
            stringList.add("Yellow");
            stringList.add("Blue");
            stringList.add("Brown");

            final ArrayAdapter arrayAdapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, stringList)
            {
                private List<String> tempItems   = stringList;
                private List<String> suggestions = new ArrayList<>();

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

                Filter nameFilter = new Filter()
                {
                    @Override
                    protected FilterResults performFiltering(CharSequence constraint)
                    {
                        if (constraint != null)
                        {
                            suggestions.clear();
                            for (String names : tempItems)
                            {
                                if (names.toLowerCase().startsWith(constraint.toString().toLowerCase()))
                                {
                                    suggestions.add(names);
                                }
                            }
                            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)
                {
                    List<String> filterList = (ArrayList<String>) results.values;
                    filteringFinished(filterList.size());

                    if (results != null && results.count > 0)
                    {
                        clear();
                        for (String item : filterList)
                        {
                            add(item);
                            notifyDataSetChanged();
                        }
                    }
                }
            };
        };

        autoComplete.setAdapter(arrayAdapter);
    }

    private void filteringFinished(int filteredItemsCount)
    {
        Log.i("LOG_TAG", " filteringFinished  count = " + filteredItemsCount);
    }
}

当您输入内容并进行过滤时,filteringFinished()方法将被调用,与自动完成输入字段相关。 更新 ( Trie 搜索):
我创建了一个 Github 项目,其中包含使用Trie搜索算法来大大提高自动完成性能的简单示例。

https://github.com/saqada/android-AutoCompleteWithTrie


这个程序运行得很完美,但是显示结果需要很长时间。有没有可能进行优化? - Ronak Thakkar
在我的情况下,我使用了我发布的答案。然而,从一般的角度来看(演示),你的答案非常有效,因此你完全值得这个赏金声誉。非常感谢。 - Ronak Thakkar
实际上,我有超过30K个值。所以我理解这可能需要一些时间。 - Ronak Thakkar
我也发布了一个答案,这是推荐的方式吗? - Ronak Thakkar
@AyazAlifov 请告诉我为什么?还有看看我的更新,第二个解决方案怎么样? - e.hadid
显示剩余4条评论

4
根据Ayaz Alifov的答案,您无法在TextWatcher中获取正确的已过滤项数量,因为过滤通常比TextWatcher事件监听器花费更长的时间。
但是我用一个timerTask做了一个小技巧。这样,在计数完成后,TextWatcher就会执行。
editText.addTextChangedListener(
new TextWatcher() {
    @Override public void onTextChanged(CharSequence s, int start, int before, int count) { }
    @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { }

    private Timer timer=new Timer();
    private final long DELAY = 1000; // milliseconds

    @Override
    public void afterTextChanged(final Editable s) {
        timer.cancel();
        timer = new Timer();
        timer.schedule(
            new TimerTask() {
                @Override
                public void run() {
                      // adapter.getCount()  will give you the correct item's counts 
                      Log.d(TAG, "run: afterTextChanged " + adapter.getCount());
                }
            }, 
            DELAY
        );
    }
}
);

编辑时间:2019年9月5日

您还可以通过设置registerDataSetObserver来获取项目数量。

adapter.registerDataSetObserver(new DataSetObserver() {
    @Override
    public void onChanged() {
        super.onChanged();
        Log.d(TAG, "onChanged: " + adapter.getCount());
    }
});

通过这种方式,onChanged() 将在每次文本更改时被调用。但是如果建议列表变为空,它将不会被调用。


autoeditText.postDelayed 也可以用来获取适配器的数量。 - praveenb

1
基本上,我们必须在适配器类中实现Filterable。
public class DrugListAdapter extends BaseAdapter implements
    Filterable {
Context context;
LayoutInflater inflater;
drugsFilter drugsFilter;

List<Drug> drugList = new ArrayList<>();
private List<Drug> drugListOrig;

public DrugListAdapter(Context context,
        List<Drug> drugList) {
    super();

    this.context = context;
    this.drugList = drugList;
    this.drugListOrig = new ArrayList<>(
            drugList);
    inflater = LayoutInflater.from(context);
}

public void resetData() {
    drugList = drugListOrig;
}



@Override
public int getCount() {
    return drugList.size();
}

@Override
public Drug getItem(int position) {
    return drugList.get(position);
}

@Override
public long getItemId(int id) {
    return id;
}

private class ViewHolder {
    TextView mVendorName;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    View view = convertView;
    ViewHolder viewHolder;
    Drug item = drugList.get(position);
    if (view == null) {
        viewHolder = new ViewHolder();
        view = inflater.inflate(R.layout.item_drug,
                parent, false);
        viewHolder.mVendorName = (TextView) view
                .findViewById(R.id.item_drug_drug_name);
        view.setTag(viewHolder);
    } else {
        viewHolder = (ViewHolder) view.getTag();

    }
    viewHolder.mVendorName.setText(item.getDrug_name());
    return view;
}

@Override
public Filter getFilter() {
    if (drugsFilter == null) {
        drugsFilter = new DrugsFilter();
    }
    return drugsFilter;
}

public class DrugsFilter extends Filter {

    @Override
    protected FilterResults performFiltering(CharSequence constraint) {
        FilterResults results = new FilterResults();
        // We implement here the filter logic
        if (constraint == null || constraint.length() == 0) {
            // No filter implemented we return all the list
            results.values = drugListOrig;
            results.count = drugListOrig.size();
        } else {
            // We perform filtering operation
            List<Drug> sList = new ArrayList<>();
            for (Drug p : drugList) {
                if (p.getDrug_name().toUpperCase()
                        .startsWith(constraint.toString().toUpperCase()))
                    sList.add(p);
            }

            results.values = sList;
            results.count = sList.size();

        }
        return results;
    }

    @SuppressWarnings("unchecked")
    @Override
    protected void publishResults(CharSequence constraint,
            FilterResults results) {
        if (results.count == 0)
            notifyDataSetInvalidated();
        else {
            drugList = (List<Drug>) results.values;
            notifyDataSetChanged();
        }

    }

}

}

这部分是关于 EditTextTextWatcher 的。
    String m;
    mDrugEditText.addTextChangedListener(new TextWatcher() {

        @Override
        public void onTextChanged(CharSequence s, int start, int before,
                                  int count) {

            if (count < before) {
                adapter.resetData();
                adapter.notifyDataSetChanged();
            }
            adapter.getFilter().filter(s.toString());

        }

        @Override
        public void beforeTextChanged(CharSequence s, int start,
                                      int before, int count) {
            if (s.length() == 0 || s.length() == 1) {
                mDrugEditText.invalidate();
            }
            if (s.length() == 3) {
                if (mDrugEditText
                        .isPerformingCompletion()) {
                    return;
                }
                adapter.resetData();
                adapter.notifyDataSetChanged();

            }
        }

        @Override
        public void afterTextChanged(Editable s) {
            m = s.toString();
            adapter.getFilter().filter(s.toString());
        }
    });

0
Sa Qada的回答是一个非常好的方法,然而,在我的情况下,我下面的回答给了我更好的性能。
autoCompleteTextViewCheckRisk.setAdapter(new ArrayAdapter<String>
            (CheckRiskActivity.this, R.layout.auto_text_row, druglist));
//druglist is the Arraylist of String.
autoCompleteTextViewCheckRisk.setThreshold(1);

文本更改监听器:

autoCompleteTextViewCheckRisk.addTextChangedListener(new TextWatcher() {

        @Override
        public void afterTextChanged(Editable s) {
            filter(druglist, s.toString());
        }

        @Override
        public void beforeTextChanged(CharSequence s, int start,
                                      int count, int after) {
        }

        @Override
        public void onTextChanged(CharSequence s, int start,
                                  int before, int count) {

        }
    });

过滤方法:

private void filter(ArrayList<String> originalArrayList, String query) {

    query = query.toLowerCase();
    filteredArrayList.clear();
    //filtered arraylist is also Arraylist of String, Just declared as global
    for (String itemName : originalArrayList) {
        final String text = itemName.toLowerCase();
        if (text.startsWith(query)) {
            filteredArrayList.add(itemName);
        }
    }

    if (filteredArrayList.size() == 0) {
        Log.i(TAG, "filter: No data found");
    }
}

0

我假设您已经熟悉了Android/Java中的基本搜索选项,并且对结果不满意。

如果您不想在每次文本更改时都浏览整个列表,唯一的方法是实现一个可以做到这一点的数据结构。

显然的解决方案将是trie。阅读此内容以了解trie的概念

现在,它的工作原理是在搜索之前对数据进行预处理。由于元素数量有限,所以不会花费太多时间,您可以在页面加载时进行处理。

步骤 - - 在加载时处理和索引所有元素。将索引放在k叉树上(它将是32叉的,每个字符都是一个字母)。 - 在文本更改时 - 遍历节点并获取计数。这将需要O(1)的时间。

我相信这是您可以达到的最快速度。

如果您已经对单词进行了索引或者只需执行startswith操作,则以上方法效果最佳。


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