15

我有一个 ListView(带有 setTextFilterEnabled(true))和一个自定义适配器(扩展 ArrayAdapter),每当添加/插入新项目时,我都会从主 UI 线程更新它们。一开始一切正常——新项目立即出现在列表中。但是,这会在我尝试过滤列表时停止。

过滤有效,但我只做了一次,我所有修改列表内容(添加、删除)的成功尝试都不再显示。我使用日志查看适配器的列表数据是否正确更新,并且确实更新了,但它不再与显示的 ListView 同步。

任何想法是什么导致了这种情况以及如何最好地解决这个问题?

4

5 回答 5

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,因此默认情况下所有操作(添加、插入、删除、清除)都针对 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 不再为空,所有对任何适配器操作的后续调用都只会修改 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 所建议的]。

如果有人能对这个问题有更多的了解或确认我刚刚做了什么,我们将不胜感激。谢谢!

于 2010-08-05T20:34:14.647 回答
0

如果我很好地理解了您的问题-您是否调用 notifyDataSetChanged() 方法?它强制列表视图重绘自身。

listAdapter.notifyDataSetChanged();

只要你做某事(例如:图像的延迟加载)就使用它来更新列表视图。

于 2010-08-05T12:45:13.637 回答
0

自 2010 年 7 月以来,该问题被报告为缺陷。查看我的评论 #5

于 2012-12-19T22:56:47.727 回答
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() 中,您可以轻松实现正则表达式的使用或将其用作区分大小写的过滤器。

而不是 notifyDataSetChanged你会调用updateList()

而不是 myListView.getFilter().filter('filter text')你会调用customFilterList('filter text')

目前这只适用于一个 ListView。也许 - 通过一些工作 - 您可以将其实现为自定义数组适配器。

于 2017-06-07T10:31:15.000 回答
0

我在同样的问题上苦苦挣扎。然后我在 StackOverflow 上找到了这篇文章。

在@MattDavis 发布的答案中,传入的arraylist 被分配给2 个不同的arraylist。

 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 是存储传入的原始数组列表的位置,而过滤后的数据是调用 getCount 和 getItem 时使用的数据集。

在我自己的 ArrayAdapter 中,当调用 getItem 时,我一直返回原始数据。因此,ListView 基本上会调用 getCount() 并查看项目的数量是否与我的原始数据(不是过滤的数据)匹配,并且 getItem 将从原始数据中获取请求的索引项目,因此完全忽略新的过滤数据集。

这就是为什么 notifyDatasetChanged 调用似乎没有更新 ListView 的原因,而实际上它只是从原始数组列表而不是过滤集不断更新。

希望这有助于将来为其他人澄清这个问题。

如果我错了,请随时纠正我,因为我每天都在学习!

于 2018-06-11T07:33:29.540 回答