问题描述
我有一个 ListView,它连接到一个 ArrayAdapter,其中 Artist 是我的一个简单类,它只有一个 id 和一个名称.
I have a ListView which is connected to a ArrayAdapter where Artist is a simple class of mine, that has only an id and a name.
现在我想过滤 ListView 所以我调用:
Now I want to filter the ListView so I call:
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 个项目.这是列表中的完整项目数.
The first debug statement prints a count of 8. That's the corrent count for listitems that start with "bla" but the adapter does not get it. The second debug statement prints a count 1150 items. That's the complete number of items in the list.
所以不知何故过滤器没有告诉适配器它已经过滤了基础数据.
So somehow the filter does not tell the adapter that it has filtered the underlying data.
我现在想知道:我是否在适配器中编写了一些代码,以便它从过滤器中获取更新?我是否必须编写自定义过滤器?我该怎么办?
I want to know now: do I have do code something in my adapter so it gets the updates from the filter? Do I have to write a custom filter? What do I have to do?
推荐答案
实际上
我注意到我应该使用originalItems"列表在 performFiltering 中构建新的过滤列表.
I noticed that I should have been using 'originalItems' list to build the new filtered one in performFiltering.
这将解决您在更改过滤器中的文本时遇到的任何问题.例如.你搜索面包",然后退格到B",你应该看到所有的B".在我原来的帖子中你不会有.
This will fix any issues that you see regarding changing the text in the filter. E.g. you search for 'Bread' then backspace to just a 'B' and you should see all 'B's. In my original post you would not have.
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,它具有属性.我需要提供自己的过滤器,以确保在用户键入时更新用于在屏幕上绘制的列表.
Basically I am building a health and nutrition application and one screen will have a list of items based on the glycaemic/glycemic index. I want users to be able to type and have the screen autofilter. Now, if you are only using strings, you get autofiltering for free. I am not though, I have my own custom class GlycaemicIndexItem which has properties on it. I need to provide my own filtering to ensure that the list used to be drawn on screen is updated when the user types.
当前屏幕是一个简单的 ListActivity,带有一个 ListView 和一个 EditText(用户输入).我们将 TextWatcher 附加到此 EditText 以确保我们收到更新通知.这意味着它应该适用于所有设备,无论用户是在硬键盘还是软键盘上打字(我有一个 HTC DesireZ 和一个旧的 G1).
Currently the screen is a simple ListActivity, with a ListView and an EditText (which the user types in). We will attach a TextWatcher to this EditText to ensure that we are notified of updates to it. This means that it should work for all devices regardless of the user typing on a hard or soft keyboard (I have an HTC DesireZ and an old G1).
这是屏幕/活动的布局 xml(有人可以告诉我如何将 xml 代码粘贴到这里,因为当我尝试使用代码块时,xml 没有被正确粘贴/显示,而是被解释了):
Here is the layout xml for the screen/activity (Can someone tell me how to paste xml code into here, as when I try to use code block xml does not get pasted/displayed properly, but interpreted):
因为我们想以自定义样式显示我们的行,所以我们还有一个用于行本身的布局 xml 文件:
As we want to display our rows in custom style, we also have a layout xml file for the row itself:
这是整个活动本身的代码.该类继承自ListActivity,有一个内部类作为适配器,继承自ArrayAdapter.这是在 Activity 的 onCreate 中实例化的,并暂时传递了一个简单的字符串列表.注意它是如何在第 39-40 行创建的.我们对行的特殊布局与项目列表一起传入.
Here is the code for the entire Activity itself. Extending from ListActivity, this class has an inner class that acts as the adapter, which extends from ArrayAdapter. This is instantiated in the onCreate of the Activity and passed a simple list of strings for now. Pay attention to how it is created on lines 39-40. Our special layout for the row is passed in with the list of items.
填充自定义行的关键在于适配器的方法 getView.
The key to populating the custom rows is in the adapter's method getView.
我们的适配器类也有自己的内部类,称为 GlycaemicIndexItemFilter,它在用户键入时进行工作.我们的过滤器在第 43-44 行通过使用 TextWatcher 及其方法 afterTextChanged 绑定到我们的 EditText.第 47 行是我们如何实现过滤的线索.我们在过滤器对象上调用过滤器.我们的过滤器是在我们第一次调用 getFilter 时创建的,第 148-149 行.
Our adapter class also has its own inner class called GlycaemicIndexItemFilter which does the work when a user types. Our filter is bound to our EditText on lines 43-44 by use of a TextWatcher and its method afterTextChanged. The line 47 is the clue as to how we achieve filtering. We call filter, on our filter object. Our filter is created when we call getFilter the first time, line 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
}
}
}
}
这篇关于如何使用 ArrayAdapter 为 ListView 编写自定义过滤器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!