【问题标题】:ListView Not Updating After Filtering过滤后ListView不更新
【发布时间】:2011-03-25 18:38:27
【问题描述】:

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

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

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

【问题讨论】:

  • 只是一个想法:我认为如果有更改,您需要取消过滤列表,然后更新列表并最后再次过滤,以使其正常工作。我知道这是一种变通方法,但总比没有好。
  • 谢谢,BennySkogberg。问题是,一旦我对列表执行任何过滤(即使我取消过滤它 - 从而将其恢复到原始状态),任何后续修改(添加、插入、删除)都会在后台成功发生(记录数据内容显示我仍然可以从中添加/删除项目),但即使我调用 notifyDataSetChanged,它也不会反映在显示的 ListView 上。

标签: android listview filter filtering android-arrayadapter


【解决方案1】:

我查看了 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 所建议的]。

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

【讨论】:

  • 要完成这个答案,你可以通过修改 ArrayAdapter 中的方法 sort(Comparator&lt;? super T&gt; comparator) 来解决这个问题,如果你可以访问它:删除 else 语句,Collections.sort(mObjects, comparator); 将被调用,即使在创建副本之后。
  • 你根本不需要锁。 Android 将处理过滤器的线程。您应该观看讲座“listView 的世界”。他们在那里谈论它。
【解决方案2】:

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

listAdapter.notifyDataSetChanged();

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

【讨论】:

  • 是的,我仍然调用 notifyDataSetChanged 希望它能解决我的问题。但即便如此,似乎也被忽略了。我知道即使在过滤然后取消过滤以将列表恢复到其原始状态之后,我仍然能够修改列表。但是,我对适配器/列表的基础数据集所做的任何更改都不会显示在屏幕上。
【解决方案3】:

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

【讨论】:

    【解决方案4】:

    首先我想提一下,@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('filter text')myListView.getFilter().filter('filter text') 而不是 /p>

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

    【讨论】:

      【解决方案5】:

      我在同样的问题上苦苦挣扎。然后我在 StackOverflow 上找到了this 的帖子。

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

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

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

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

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

      【讨论】:

        猜你喜欢
        • 2012-09-15
        • 2011-01-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-07-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多