如何使用 ArrayAdapter 为 ListView 编写自定义过滤器
Posted
技术标签:
【中文标题】如何使用 ArrayAdapter 为 ListView 编写自定义过滤器【英文标题】:How to write a custom filter for ListView with ArrayAdapter 【发布时间】:2011-01-31 22:28:45 【问题描述】:我有一个连接到 ArrayAdapter 的 ListView,其中 Artist 是我的一个简单类,它只有一个 id 和一个名称。
现在我想过滤 ListView 所以我打电话:
artistAdapter.getFilter().filter("bla", new Filter.FilterListener()
public void onFilterComplete(int count)
Log.d(Config.LOG_TAG, "filter complete! count: " + count); // returns 8
Log.d(Config.LOG_TAG, "adapter count: " + artistAdapter.getCount()); // return 1150
);
第一个调试语句打印的计数为 8。这是以“bla”开头的列表项的正确计数,但适配器没有得到它。第二个调试语句打印计数 1150 项。这是列表中项目的完整数量。
所以过滤器不知何故并没有告诉适配器它已经过滤了底层数据。
我现在想知道:我是否在我的适配器中编写了一些代码,以便它从过滤器中获取更新?我必须编写自定义过滤器吗?我该怎么办?
【问题讨论】:
***.com/questions/2718202/… - 看起来它可能会回答你的问题 @Anton:你解决了吗。请回复...... 【参考方案1】:其实
我注意到我应该在 performFiltering 中使用“originalItems”列表来构建新的过滤列表。
这将解决您看到的有关更改过滤器中的文本的任何问题。例如。您搜索“面包”,然后退格到“B”,您应该会看到所有“B”。在我原来的帖子中你不会有。
private class GlycaemicIndexItemAdapter extends ArrayAdapter<GlycaemicIndexItem>
private ArrayList<GlycaemicIndexItem> items;
private ArrayList<GlycaemicIndexItem> originalItems = new ArrayList<GlycaemicIndexItem>();
private GlycaemicIndexItemFilter filter;
private final Object mLock = new Object();
public GlycaemicIndexItemAdapter(Context context, int textViewResourceId, ArrayList<GlycaemicIndexItem> newItems)
super(context, textViewResourceId, newItems);
this.items = newItems;
cloneItems(newItems);
protected void cloneItems(ArrayList<GlycaemicIndexItem> items)
for (Iterator iterator = items.iterator(); iterator
.hasNext();)
GlycaemicIndexItem gi = (GlycaemicIndexItem) iterator.next();
originalItems.add(gi);
@Override
public int getCount()
synchronized(mLock)
return items!=null ? items.size() : 0;
@Override
public GlycaemicIndexItem getItem(int item)
GlycaemicIndexItem gi = null;
synchronized(mLock)
gi = items!=null ? items.get(item) : null;
return gi;
@Override
public View getView(int position, View convertView, ViewGroup parent)
View v = convertView;
if (v == null)
LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.row, null);
GlycaemicIndexItem i = null;
synchronized(mLock)
i = items.get(position);
if (i != null)
TextView tt = (TextView) v.findViewById(R.id.rowText);
TextView bt = (TextView) v.findViewById(R.id.rowText2);
if (tt != null)
tt.setText("Name: "+i.getName());
if(bt != null)
bt.setText("GI Value: " + i.getGlycaemicIndex());
return v;
/**
* Implementing the Filterable interface.
*/
public Filter getFilter()
if (filter == null)
filter = new GlycaemicIndexItemFilter();
return filter;
/**
* Custom Filter implementation for the items adapter.
*
*/
private class GlycaemicIndexItemFilter extends Filter
protected FilterResults performFiltering(CharSequence prefix)
// Initiate our results object
FilterResults results = new FilterResults();
// No prefix is sent to filter by so we're going to send back the original array
if (prefix == null || prefix.length() == 0)
synchronized (mLock)
results.values = originalItems;
results.count = originalItems.size();
else
synchronized(mLock)
// Compare lower case strings
String prefixString = prefix.toString().toLowerCase();
final ArrayList<GlycaemicIndexItem> filteredItems = new ArrayList<GlycaemicIndexItem>();
// Local to here so we're not changing actual array
final ArrayList<GlycaemicIndexItem> localItems = new ArrayList<GlycaemicIndexItem>();
localItems.addAll(originalItems);
final int count = localItems.size();
for (int i = 0; i < count; i++)
final GlycaemicIndexItem item = localItems.get(i);
final String itemName = item.getName().toString().toLowerCase();
// First match against the whole, non-splitted value
if (itemName.startsWith(prefixString))
filteredItems.add(item);
else /* This is option and taken from the source of ArrayAdapter
final String[] words = itemName.split(" ");
final int wordCount = words.length;
for (int k = 0; k < wordCount; k++)
if (words[k].startsWith(prefixString))
newItems.add(item);
break;
*/
// Set and return
results.values = filteredItems;
results.count = filteredItems.size();
//end synchronized
return results;
@SuppressWarnings("unchecked")
@Override
protected void publishResults(CharSequence prefix, FilterResults results)
//noinspection unchecked
synchronized(mLock)
final ArrayList<GlycaemicIndexItem> localItems = (ArrayList<GlycaemicIndexItem>) results.values;
notifyDataSetChanged();
clear();
//Add the items back in
for (Iterator iterator = localItems.iterator(); iterator
.hasNext();)
GlycaemicIndexItem gi = (GlycaemicIndexItem) iterator.next();
add(gi);
//end synchronized
基本上,我正在构建一个健康和营养应用程序,一个屏幕将显示基于血糖/血糖指数的项目列表。我希望用户能够键入并拥有屏幕自动过滤器。现在,如果您只使用字符串,您可以免费获得自动过滤。我不是,我有自己的自定义类 GlycaemicIndexItem,它有属性。我需要提供自己的过滤功能,以确保在用户键入时更新用于在屏幕上绘制的列表。
当前屏幕是一个简单的 ListActivity,带有一个 ListView 和一个 EditText(用户输入)。我们将向此 EditText 附加一个 TextWatcher,以确保我们收到有关它的更新的通知。这意味着它应该适用于所有设备,无论用户在硬键盘还是软键盘上打字(我有一个 HTC DesireZ 和一个旧 G1)。
这是屏幕/活动的布局 xml(有人可以告诉我如何将 xml 代码粘贴到此处,因为当我尝试使用代码块时,xml 没有正确粘贴/显示,而是被解释):
由于我们想以自定义样式显示我们的行,我们还有一个用于行本身的布局 xml 文件:
这是整个 Activity 本身的代码。从ListActivity扩展,这个类有一个作为适配器的内部类,它从ArrayAdapter扩展而来。这是在 Activity 的 onCreate 中实例化的,并暂时传递了一个简单的字符串列表。注意它是如何在第 39-40 行创建的。我们对行的特殊布局是与项目列表一起传入的。
填充自定义行的关键在于适配器的方法getView。
我们的适配器类也有它自己的内部类,称为 GlycaemicIndexItemFilter,它在用户键入时执行工作。通过使用 TextWatcher 及其方法 afterTextChanged,我们的过滤器在第 43-44 行绑定到我们的 EditText。第 47 行是我们如何实现过滤的线索。我们在过滤器对象上调用过滤器。我们的过滤器是在我们第一次调用 getFilter 时创建的,第 148-149 行。
package com.tilleytech.android.myhealthylife;
import java.util.ArrayList;
import java.util.Iterator;
import android.app.ListActivity;
import android.content.Context;
import android.content.res.Resources;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.Filter;
import android.widget.ListView;
import android.widget.TextView;
public class GlycaemicIndexAtoZActivity extends ListActivity
/** Called when the activity is first created. */
private GlycaemicIndexItemAdapter giAdapter;
private TextWatcher filterTextWatcher;
private EditText filterText = null;
@Override
public void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.giatoz);
ListView lv = getListView();
lv.setTextFilterEnabled(true);
// By using setAdapter method in listview we an add string array in list.
ArrayList<GlycaemicIndexItem> list = getListItems();
giAdapter = new GlycaemicIndexItemAdapter(this, R.layout.row, list);
giAdapter.notifyDataSetChanged();
setListAdapter(giAdapter);
filterText = (EditText)findViewById(R.id.GI_AtoZSearchEditText);
filterTextWatcher = new TextWatcher()
public void afterTextChanged(Editable s)
giAdapter.getFilter().filter(s); //Filter from my adapter
giAdapter.notifyDataSetChanged(); //Update my view
public void beforeTextChanged(CharSequence s, int start, int count,
int after)
public void onTextChanged(CharSequence s, int start, int before,
int count)
;
filterText.addTextChangedListener(filterTextWatcher);
private ArrayList<GlycaemicIndexItem> getListItems()
ArrayList<GlycaemicIndexItem> result = new ArrayList<GlycaemicIndexItem>();
Resources res = getResources();
//Get our raw strings
String[] array = res.getStringArray(R.array.GIList);
for (int i = 0; i < array.length; i++)
GlycaemicIndexItem gi = new GlycaemicIndexItem();
gi.setName(array[i]);
gi.setGlycaemicIndex(1);
result.add(gi);
return result;
private class GlycaemicIndexItemAdapter extends ArrayAdapter<GlycaemicIndexItem>
private ArrayList<GlycaemicIndexItem> items;
private ArrayList<GlycaemicIndexItem> originalItems = new ArrayList<GlycaemicIndexItem>();
private GlycaemicIndexItemFilter filter;
private final Object mLock = new Object();
public GlycaemicIndexItemAdapter(Context context, int textViewResourceId, ArrayList<GlycaemicIndexItem> newItems)
super(context, textViewResourceId, newItems);
this.items = newItems;
cloneItems(newItems);
protected void cloneItems(ArrayList<GlycaemicIndexItem> items)
for (Iterator iterator = items.iterator(); iterator
.hasNext();)
GlycaemicIndexItem gi = (GlycaemicIndexItem) iterator.next();
originalItems.add(gi);
@Override
public int getCount()
synchronized(mLock)
return items!=null ? items.size() : 0;
@Override
public GlycaemicIndexItem getItem(int item)
GlycaemicIndexItem gi = null;
synchronized(mLock)
gi = items!=null ? items.get(item) : null;
return gi;
@Override
public View getView(int position, View convertView, ViewGroup parent)
View v = convertView;
if (v == null)
LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.row, null);
GlycaemicIndexItem i = null;
synchronized(mLock)
i = items.get(position);
if (i != null)
TextView tt = (TextView) v.findViewById(R.id.rowText);
TextView bt = (TextView) v.findViewById(R.id.rowText2);
if (tt != null)
tt.setText("Name: "+i.getName());
if(bt != null)
bt.setText("GI Value: " + i.getGlycaemicIndex());
return v;
/**
* Implementing the Filterable interface.
*/
public Filter getFilter()
if (filter == null)
filter = new GlycaemicIndexItemFilter();
return filter;
/**
* Custom Filter implementation for the items adapter.
*
*/
private class GlycaemicIndexItemFilter extends Filter
protected FilterResults performFiltering(CharSequence prefix)
// Initiate our results object
FilterResults results = new FilterResults();
// No prefix is sent to filter by so we're going to send back the original array
if (prefix == null || prefix.length() == 0)
synchronized (mLock)
results.values = originalItems;
results.count = originalItems.size();
else
synchronized(mLock)
// Compare lower case strings
String prefixString = prefix.toString().toLowerCase();
final ArrayList<GlycaemicIndexItem> filteredItems = new ArrayList<GlycaemicIndexItem>();
// Local to here so we're not changing actual array
final ArrayList<GlycaemicIndexItem> localItems = new ArrayList<GlycaemicIndexItem>();
localItems.addAll(originalItems);
final int count = localItems.size();
for (int i = 0; i < count; i++)
final GlycaemicIndexItem item = localItems.get(i);
final String itemName = item.getName().toString().toLowerCase();
// First match against the whole, non-splitted value
if (itemName.startsWith(prefixString))
filteredItems.add(item);
else /* This is option and taken from the source of ArrayAdapter
final String[] words = itemName.split(" ");
final int wordCount = words.length;
for (int k = 0; k < wordCount; k++)
if (words[k].startsWith(prefixString))
newItems.add(item);
break;
*/
// Set and return
results.values = filteredItems;
results.count = filteredItems.size();
//end synchronized
return results;
@SuppressWarnings("unchecked")
@Override
protected void publishResults(CharSequence prefix, FilterResults results)
//noinspection unchecked
synchronized(mLock)
final ArrayList<GlycaemicIndexItem> localItems = (ArrayList<GlycaemicIndexItem>) results.values;
notifyDataSetChanged();
clear();
//Add the items back in
for (Iterator iterator = localItems.iterator(); iterator
.hasNext();)
GlycaemicIndexItem gi = (GlycaemicIndexItem) iterator.next();
add(gi);
//end synchronized
【讨论】:
我问一个问题:***.com/questions/5404197/listview-filter-mistake 你能帮我看看我的错误是什么 您好,我刚刚查看了您的其他页面,看起来有些人已经帮助了您。您需要检查 TestFilter 类中的第 33 行。这是一个 NullPointerException,当您尝试调用尚未实例化(创建)的类(称为对象)的实例上的方法或访问属性时会发生这种情况。 也许你能帮我解决这个问题? ***.com/questions/20524417/…【参考方案2】:我认为你可以在 onFilterComplete 方法中使用notifyDataSetChanged();
。
【讨论】:
以上是关于如何使用 ArrayAdapter 为 ListView 编写自定义过滤器的主要内容,如果未能解决你的问题,请参考以下文章
如何修复此 ArrayAdapter 需要资源 ID 为 TextView
android在使用ArrayAdapter时如何分配textViewResourceId
OnItemClickListener 为 ListView 使用 ArrayAdapter