我有一个ListView(带有setTextFilterEnabled(true))和一个自定义适配器(extends ArrayAdapter),每当添加/插入新项目时,我都会从主UI线程更新。一切都工作正常 - 新项目立即显示在列表中。但是,当我尝试过滤列表时,这就停止了。
过滤有效,但我这样做了一次,并且我所有成功修改列表内容的尝试(添加,删除)都不再显示。我使用Log来查看适配器的列表数据是否得到了正确的更新,但确实如此,但它不再与显示的ListView同步。
任何想法是什么造成了这个以及如何最好地解决这个问题?
答案 0 :(得分:16)
我查看了ArrayAdapter的实际源代码,看起来它实际上是按照这种方式编写的。
ArrayAdapter有两个列表:mObjects和mOriginalValues。 mObjects是适配器将使用的主要数据集。使用add()函数,例如:
public void add(T object) {
if (mOriginalValues != null) {
synchronized (mLock) {
mOriginalValues.add(object);
if (mNotifyOnChange) notifyDataSetChanged();
}
} else {
mObjects.add(object);
if (mNotifyOnChange) notifyDataSetChanged();
}
}
mOriginalValues最初为null,因此所有操作(add,insert,remove,clear)默认都是针对mObjects的。在您决定在列表上启用过滤并实际执行过滤之前,这一切都很好。第一次过滤使用mObjects具有的mOriginalValues初始化:
private class ArrayFilter extends Filter {
@Override
protected FilterResults performFiltering(CharSequence prefix) {
FilterResults results = new FilterResults();
if (mOriginalValues == null) {
synchronized (mLock) {
mOriginalValues = new ArrayList<T>(mObjects);
//mOriginalValues is no longer null
}
}
if (prefix == null || prefix.length() == 0) {
synchronized (mLock) {
ArrayList<T> list = new ArrayList<T>(mOriginalValues);
results.values = list;
results.count = list.size();
}
} else {
//filtering work happens here and a new filtered set is stored in newValues
results.values = newValues;
results.count = newValues.size();
}
return results;
}
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
//noinspection unchecked
mObjects = (List<T>) results.values;
if (results.count > 0) {
notifyDataSetChanged();
} else {
notifyDataSetInvalidated();
}
}
}
mOriginalValues现在有原始值/项的副本,因此适配器可以完成其工作并通过mObjects显示已过滤的列表,而不会丢失预过滤的数据。
现在原谅我(并且请告诉和解释)如果我的想法不正确但我觉得这很奇怪,因为现在mOriginalValues不再为null,所有后续调用任何适配器操作都只会修改mOriginalValues。但是,由于适配器设置为将mObjects视为其主要数据集,因此屏幕上将显示没有任何事情发生。直到你执行另一轮过滤。删除过滤器会触发:
if (prefix == null || prefix.length() == 0) {
synchronized (mLock) {
ArrayList<T> list = new ArrayList<T>(mOriginalValues);
results.values = list;
results.count = list.size();
}
}
mOriginalValues,我们自第一个过滤器以来一直在修改(虽然我们看不到它在屏幕上发生)存储在另一个列表中并复制到mObjects,最后显示所做的更改。不过从这一点开始它将是这样的:所有操作都将在mOriginalValues上完成,并且只有在过滤后才会出现更改。
至于解决方案,我现在提出的是(1)放置一个布尔标志,告诉适配器操作是否有正在进行的过滤 - 如果过滤完成,则复制将mOriginalValues的内容转移到mObjects,或者(2)简单地调用适配器的Filter对象并传递一个空字符串* .getFilter()。filter(“”)以在每次操作后强制过滤器[同样由BennySkogberg建议]。
如果有人能够对这个问题有所了解或确认我刚刚做了什么,我们将不胜感激。谢谢!
答案 1 :(得分:0)
如果我理解你的问题 - 你是否调用notifyDataSetChanged()方法?它强制listview重绘自己。
listAdapter.notifyDataSetChanged();
只要你做某事(例如:延迟加载图像)就可以使用它来更新列表视图。
答案 2 :(得分:0)
自2010年7月以来,该问题被报告为缺陷。请查看我的comment #5
答案 3 :(得分:0)
首先我想提一下,@ jhie的答案非常好。然而,这些事实并没有真正解决我的问题。但是在内部使用机制如何工作的知识的帮助下,我可以将它写成我自己的过滤函数:
public ArrayList<String> listArray = new ArrayList<>();
public ArrayList<String> tempListArray = new ArrayList<>();
public ArrayAdapter<String> tempAdapter;
public String currentFilter;
在onCreate中:
// Fill my ORIGINAL listArray;
listArray.add("Cookies are delicious.");
// After adding everything to listArray, I add everything to tempListArray
for (String element : listArray){
tempListArray.add(element);
}
// Setting up the Adapter...
tempAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, tempListArray);
myListView.setAdapter(tempAdapter);
过滤
// Defining filter functions
public void customFilterList(String filter){
tempListArray.clear();
for (String item : listArray){
if (item.toLowerCase().contains(filter.toLowerCase())){
tempListArray.add(item);
}
}
currentFilter = filter;
tempAdapter.notifyDataSetChanged();
}
public void updateList(){
customFilterList(currentFilter);
}
我用它在我的列表中实现搜索功能。
最大优势:您拥有更多过滤功能:在customFilterList()中,您可以轻松实现正则表达式的使用或将其用作案例 敏感过滤器。
您可以调用 updateList()而不是 notifyDataSetChanged 。
您可以调用 customFilterList(&#39;过滤器文本&#39)
目前这仅适用于一个ListView。也许 - 通过一些工作 - 您可以将其实现为自定义阵列适配器。
答案 4 :(得分:0)
我正在努力解决同样的问题。然后我在StackOverflow上发现了this帖子。
在@MattDavis发布的答案中,传入的arraylist被分配给2个不同的arraylists。
public PromotionListAdapter(Activity a, ArrayList<HashMap<String, String>> d)
{
activity = a;
inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
imageLoader = new ImageLoader(activity.getApplicationContext());
//To start, set both data sources to the incoming data
originalData = d;
filteredData = d;
}
originalData和filteredData。
originalData是传入的原始arraylist的存储位置,而过滤后的数据是调用getCount和getItem时使用的数据集。
在我自己的ArrayAdapter中,我在调用getItem时保持返回原始数据。因此,ListView基本上会调用getCount()并查看项目数量与原始数据(不是过滤后的数据)匹配,getItem将从原始数据中获取请求的索引项目,因此完全忽略新的过滤数据集。
这就是为什么似乎notifyDatasetChanged调用没有更新ListView,实际上它只是保持从原始arraylist而不是过滤集更新。
希望这有助于将来为其他人澄清此问题。
如果我错了,请随时纠正我,因为我每天都在学习!