动态更新AutoCompleteTextView适配器

43

我想定期更改AutoCompleteTextView提供的建议,通过从RESTful Web服务获取列表,但是无法使其顺畅运行。我设置了一个硬编码的建议列表来确保它能正常工作:

ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, R.layout.list_item, new String[] {"Hi", "Ho"});
speciesName.setAdapter(adapter);//my autocomplete tv

我在TextView上设置了一个TextWatcher,当文本发生变化时,它会调用一个非阻塞的方法去获取一个新的建议列表 -- 这个获取新列表的部分已经正常工作。然后我想重置适配器,就像这样:

public void setOptionsAndUpdate(String[] options) {
    Log.d(TAG, "setting options");
    //speciesName.setAdapter((ArrayAdapter<String>)null);
    ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, R.layout.list_item, options);
    speciesName.setAdapter(adapter);
}

这个方法被调用了,但是不起作用--建议列表要么消失了,要么显示的建议没有改变,尽管调用了setAdapter方法。

这种方法是否正确?我看了一下SimpleCursorAdapter,但是不知道如何将我的 Web 服务注册为内容提供者。(它的格式类似于http://www.blah.com/query?term=XX,其中XX是来自我的应用程序的输入,并且响应是一个字符串的JSON数组。)


我正在这里做类似的事情!!! https://dev59.com/Y-o6XIcBkEYKwwoYPR_f - Etienne Lawlor
5个回答

60

当我动态地向适配器中添加和更改数据时,使用adapter.notifyDataSetChanged()没有成功。我的情况是,我异步地访问外部API并定期获取完成数据列表。

以下代码清除了适配器,并像您期望的那样添加新数据。但是,我必须调用getFilter().Filter方法来强制显示数据。此外,由于我的API调用是异步的,我还必须根据AutocompleteTextView中的当前文本显式过滤。

adapter.clear();
for (Map<String, String> map : completions) {
     adapter.add(map.get("name"));
}

//Force the adapter to filter itself, necessary to show new data.
//Filter based on the current text because api call is asynchronous. 
adapter.getFilter().filter(autocompleteTextView.getText(), null);

2
你可以传入 autocompleteTextView 来代替传入 null,这样它就会像 AutoCompleteTextView.performFiltering() 方法一样运行,而该方法通常在文本更改后被调用。 - kassim
1
找了好几个小时才找到这个。我欠你一瓶啤酒 :) - Dũng Trần Trung
这是这类问题的最佳解决方案!非常感谢! - Денис Чорный
这是我从doAfterTextChanged内部更新自动完成列表后获取更新的唯一方法,我在其中异步获取相关列表来自DB查询。它避免了每次创建适配器。谢谢! - RedGlyph
请注意:您现在可以通过调用“AutoCompleteTextView.refreshAutoCompleteResults()”(自 API 级别 29 起)手动刷新建议。请参阅 https://developer.android.com/reference/android/widget/AutoCompleteTextView#refreshAutoCompleteResults()。 - Chris_D_Turk

25

这个主题有一个非常好的教程,使用远程数据在Google Map API中填充AutoCompleteTextView此处

如果您需要缓存版本,我从这里检索到了它。 原始教程已被删除,但本质上您需要编写一个ArrayAdapter,并使用类似下面展示的自定义过滤器方式将其分配给您的AutoCompleteTextView。

注意:您需要实现一个autocomplete()方法,执行所需操作以同步获取并返回自动完成项。由于过滤器在后台线程中调用,因此不会阻塞主UI线程。

private class PlacesAutoCompleteAdapter extends ArrayAdapter<String> implements Filterable {
private ArrayList<String> resultList;

public PlacesAutoCompleteAdapter(Context context, int textViewResourceId) {
    super(context, textViewResourceId);
}

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

@Override
public String getItem(int index) {
    return resultList.get(index);
}

@Override
public Filter getFilter() {
    Filter filter = new Filter() {
        @Override
        protected FilterResults performFiltering(CharSequence constraint) {
            FilterResults filterResults = new FilterResults();
            if (constraint != null) {
                // Retrieve the autocomplete results.
                resultList = autocomplete(constraint.toString());

                // Assign the data to the FilterResults
                filterResults.values = resultList;
                filterResults.count = resultList.size();
            }
            return filterResults;
        }

        @Override
        protected void publishResults(CharSequence constraint, FilterResults results) {
            if (results != null && results.count > 0) {
                notifyDataSetChanged();
            }
            else {
                notifyDataSetInvalidated();
            }
        }};
    return filter;
}
}

我也有同样关于UI线程的问题。@manyobject,你能解释一下吗? - Hadi Tok
1
我已经测试过了,没有收到NetworkOnMainThreadException异常。所以可以放心使用。 - Hadi Tok
1
网络调用不会阻塞用户界面,因为Android会在后台异步线程上执行过滤操作。因此,您可以/应该编写autocomplete()方法以同步获取数据(阻塞)。 - user937783
这应该是被接受的答案!!!干得好。我用 Retrofit 同步调用验证了一下。可以在不阻塞 UI 线程的情况下工作!! - AkshayT
谢谢,它有效! :) 一个建议:如果您打字速度很快(resultList正在更新),可能会导致IndexOutOfBoundsException。解决方案:在函数publishResults()中更新本地resultList,而不是在performFiltering()中更新。 - steve

24

这是我更新AutoCompleteTextView的方法:

String[] data = terms.toArray(new String[terms.size()]);  // terms is a List<String>
ArrayAdapter<?> adapter = new ArrayAdapter<Object>(activity, android.R.layout.simple_dropdown_item_1line, data);
keywordField.setAdapter(adapter);  // keywordField is a AutoCompleteTextView
if(terms.size() < 40) keywordField.setThreshold(1); 
else keywordField.setThreshold(2);

当然,这是静态的,并且不涉及到空中建议,但是在您将其分配给AutoCompleteTextView后,我还建议您通知适配器进行更改:

adapter.notifyDataSetChanged();   

希望这能帮到你。

-serkan


@serkan:太好了,这正是我需要的。但是,我认为阈值没有更新,对吧?因此,在您的示例中,如果terms.size() < 40在开始时成立,那么阈值将永远保持为1。当然,您可以再次运行该代码行以进行更新。谢谢! - Luis A. Florit
谢谢Luis。那么,我在做什么就是说,如果我有一个小的事物列表,即使只输入了1个字符,我也可以开始建议;但是如果我的池很大,那么我应该等到输入两个字符后再建议任何东西-出于效率考虑。你肯定始终保持1或2。 - serkanozel

2

由于我无法添加评论,所以我提供了一个新的答案。 不需要清除适配器或调用adapter.getFilter().filter(...)... 要动态更新AutoCompleteTextView适配器,只需将新项添加到适配器中并再次设置适配器即可。对于原始问题中给出的示例,我尝试了以下操作,它可以工作(下面的代码未显示适配器的初始设置,因为这里有多个答案涵盖了该部分。这只是动态更新适配器的代码)。适配器更新可以与更新ArrayAdapter相关联的List的代码一起进行。

    adapter.add(String newSuggestion); // this goes inside a loop for adding multiple suggestions
    speciesName.setAdapter(adapter) ; // speciesName is an AutoCompleteTextView as given in the original question.

1
我发现更新适配器的最佳解决方案:
Editable text = autocomplete.getText();
autocomplete.setText(text);
autocomplete.setSelection(text.length());

工作原理:

我们设置autoCompleteTextView的文本为其当前文本,以便适配器通知数据已更改并更新listView的内容。

但是通过这个技巧,光标移动到编辑框的开头。因此,我们使用autocomplete.setSelection(text.length())将光标移动到结尾。

非常有效!

编辑:

此外,您必须直接在ArrayAdapter上使用clear()add()remove()方法,而不是在ArrayList上使用。


1
我尝试了许多不同的解决方案,但这个是唯一一个对我有效的解决方案。 - Matt

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