如何在安卓上动态更新ListView

160

在Android上,我如何创建一个ListView,可以根据用户输入进行过滤,并且所显示的项会根据TextView的值动态更新?

我正在寻找像这样的东西:

-------------------------
| Text View             |
-------------------------
| List item             |
| List item             |
| List item             |
| List item             |
|                       |
|                       |
|                       |
|                       |
-------------------------

8
我提名重新开放。我已更新文本,以使其更具疑问性,而且显然它是一个有价值的社区资源,因为答案仍在不断涌现。 - Hamy
请重新开启这个问题。这很明显有帮助。 - SilentNot
3个回答

287

首先,您需要创建一个包含EditText和ListView的XML布局。

<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <!-- Pretty hint text, and maxLines -->
    <EditText android:id="@+building_list/search_box" 
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:hint="type to filter"
        android:inputType="text"
        android:maxLines="1"/>

    <!-- Set height to 0, and let the weight param expand it -->
    <!-- Note the use of the default ID! This lets us use a 
         ListActivity still! -->
    <ListView android:id="@android:id/list"
        android:layout_width="fill_parent"
        android:layout_height="0dip"
        android:layout_weight="1" 
         /> 

</LinearLayout>

这将正确地放置所有内容,一个漂亮的EditText在ListView上方。接下来,像通常一样创建ListActivity,但在onCreate()方法中添加setContentView()调用以使用我们最近声明的布局。请记住,我们特别标识了ListView,使用android:id="@android:id/list"。这使得ListActivity知道我们想要在我们声明的布局中使用哪个ListView。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.filterable_listview);

        setListAdapter(new ArrayAdapter<String>(this,
                       android.R.layout.simple_list_item_1, 
                       getStringArrayList());
    }

现在运行应用程序应该会显示您以前的ListView,上面有一个漂亮的框。为了让这个框做些事情,我们需要获取它的输入,并将该输入过滤列表。虽然很多人尝试手动完成这项工作,但大多数ListViewAdapter类都配备了可以用于自动执行过滤的Filter对象。我们只需要将来自EditText的输入传输到Filter中即可。原来这非常简单。要进行快速测试,请在onCreate()调用中添加此行

adapter.getFilter().filter(s);

请注意,您需要将 ListAdapter 保存到变量中以使其起作用 - 我已经将之前的 ArrayAdapter<String> 保存到一个名为“adapter”的变量中。

下一步是获取 EditText 中的输入。这实际上需要一些思考。您可以向 EditText 添加 OnKeyListener()。但是,此侦听器仅接收某些按键事件。例如,如果用户输入“wyw”,则预测文本可能会推荐“eye”。直到用户选择“wyw”或“eye”,您的 OnKeyListener 才会收到按键事件。有些人可能喜欢这种解决方案,但我觉得它很让人沮丧。我想要每个按键事件,所以我可以选择过滤或不过滤。解决方案是使用 TextWatcher。只需创建并添加一个 TextWatcherEditText,并在每次文本更改时向 ListAdapterFilter 发送一个过滤请求。记得在 OnDestroy() 中删除 TextWatcher!这是最终的解决方案:

private EditText filterText = null;
ArrayAdapter<String> adapter = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.filterable_listview);

    filterText = (EditText) findViewById(R.id.search_box);
    filterText.addTextChangedListener(filterTextWatcher);

    setListAdapter(new ArrayAdapter<String>(this,
                   android.R.layout.simple_list_item_1, 
                   getStringArrayList());
}

private TextWatcher filterTextWatcher = new TextWatcher() {

    public void afterTextChanged(Editable s) {
    }

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

    public void onTextChanged(CharSequence s, int start, int before,
            int count) {
        adapter.getFilter().filter(s);
    }

};

@Override
protected void onDestroy() {
    super.onDestroy();
    filterText.removeTextChangedListener(filterTextWatcher);
}

7
有没有一种简单的方法来过滤ListView,而不是像这个解决方案那样以“开始于”方式进行过滤,而是以“包含”方式进行过滤? - Viktor Brešan
12
如果你感兴趣的单词是用空格分隔的,那么它会自动执行此操作。否则,就不会自动执行。可能最简单的方法是通过扩展适配器(Adapter)来创建子类并覆盖getFilter方法,以返回你定义的Filter对象。请参见http://github.com/android/platform_frameworks_base/blob/master/core/java/android/widget/ArrayAdapter.java#L449,了解默认的ArrayFilter如何工作 - 复制这个代码的95%并更改第479和486行即可。 - Hamy
2
Hamy,写得非常好!不过我有一个问题:我已经实现了这个功能,但是每次我输入一个字母后,ListView 就会消失几秒钟,然后再回来,被过滤了。你有遇到过这种情况吗?我的直觉告诉我,这是因为列表中有600多个项目,并且 toString() 函数不是很简单。 - lowellk
2
在小屏幕的横屏模式下,EditText和键盘占据了整个屏幕,ListView不可见!有什么解决办法吗? - Martin Konicek
4
这真的有必要吗?
记得在OnDestroy()中移除TextWatcher。
- Jojo
显示剩余14条评论

10

运行该程序将导致强制关闭。

我替换了以下行:

android:id="@+building_list/search_box"

变为

android:id="@+id/search_box"

这可能是问题所在吗? '@+building_list' 是用来做什么的?


Johe,当你说R.id.something时,something存在于id中,因为你说了android:id="@+id/search_box"。如果你说android:id="@+building_list/search_box",那么在代码中,你可以调用findViewById(R.building_list.search_box)。你得到的顶级异常是什么?这段代码是没有经过测试编译就复制的,所以我可能在其中留下了至少一个错误。 - Hamy
嗨Hamy,你在代码中引用了“@+building_list/search_box”,并使用“filterText = (EditText) findViewById(R.id.search_box);”进行了调用。这就是我想知道的原因。 - j7nn7k
1
开始输入时出现强制关闭。错误的一部分:ERROR/AndroidRuntime(188): java.lang.NullPointerException 02-11 07:30:29.828: ERROR/AndroidRuntime(188): at xxx.com.ListFilter$1.onTextChanged(ListFilter.java:46) 02-11 07:30:29.828: ERROR/AndroidRuntime(188): at android.widget.TextView.sendOnTextChanged(TextView.java:6102) 02-11 07:30:29.828: ERROR/AndroidRuntime(188): at android.widget.TextView.handleTextChanged(TextView.java:6143) 02-11 07:30:29.828: ERROR/AndroidRuntime(188): at android.widget.TextView$ChangeWatcher.onTextChanged(TextView.java:6286) - j7nn7k
嘿,糟糕了 - 看来我试图修改代码以去除 @+building_list(因为那是一个混淆的点),但我没有在所有地方都注意到。谢谢你的提示!只要将它改为 @+id 应该就可以解决问题了。 - Hamy

4
我在过滤时遇到了问题,结果被过滤了,但并没有恢复! 在过滤之前(活动开始),我创建了一个备份列表..(只是另一个包含相同数据的列表) 在过滤时,过滤器和ListAdapter连接到主列表。 但是过滤器本身使用备份列表中的数据。 在我的情况下,这确保了列表立即更新,即使在删除搜索词字符时,该列表在每种情况下都能成功地恢复 :) 不管怎样,感谢您提供的解决方案。

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