如何使用ArrayAdapter为ListView编写自定义filter

我有一个连接到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 } }); 

第一个debugging语句输出8的计数。这是以​​“bla”开头的列表项的计数,但是适配器没有得到它。 第二个debugging语句打印一个计数1150个项目。 这是列表中项目的完整数量。

所以filter不知道它已经过滤了底层的数据。

我现在想知道:我是否已经在适配器中编写了代码,以便从filter中获取更新? 我是否必须编写自定义filter? 我需要做什么?

其实

我注意到,我应该使用'originalItems'列表来build立新的过滤在执行过滤。

这将解决您在更改filter中的文本时看到的任何问题。 例如,您search“面包”,然后退格为“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 } } } 

基本上我正在build立一个健康和营养应用程序,一个屏幕将有一个基于血糖/血糖指数的项目列表。 我希望用户能够键入和屏幕自动筛选。 现在,如果你只使用string,你可以自由过滤。 我不是,我有我自己的自定义类GlycaemicIndexItem具有它的属性。 我需要提供我自己的过滤,以确保用户input时更新屏幕上绘制的列表。

目前该屏幕是一个简单的ListActivity,带有一个ListView和一个EditText(用户input)。 我们将附加一个TextWatcher到这个EditText,以确保我们收到更新通知。 这意味着它应该适用于所有设备,无论用户在硬键还是软键盘上input(我有一个HTC DesireZ和一个老的G1)。

这里是屏幕/活动的布局xml(有人可以告诉我如何粘贴xml代码到这里,因为当我尝试使用代码块xml不粘贴/正确显示,但解释):

活动的布局 -  giatoz.xml

因为我们想要以自定义样式显示我们的行,所以我们也有一个布局xml文件的行本身: 行xml文件

以下是整个活动本身的代码。 从ListActivity扩展而来,这个类有一个内部类,它作为从ArrayAdapter扩展的适配器。 这是在Activity的onCreate中实例化的,现在传递了一个简单的string列表。 注意它是如何在第39-40行创build的。 我们的行的特殊布局与物品列表一起被传入。

填充自定义行的关键在于适配器的方法getView

我们的适配器类也有自己的内部类,称为GlycaemicIndexItemFilter,它可以在用户input时进行工作。 我们的filter通过使用TextWatcher及其方法afterTextChanged绑定到第43-44行的EditText。 第47行是我们如何实现过滤的线索。 我们在filter对象上调用filter。 我们的filter是在我们第一次调用getFilter时创build的,第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 } } } } 

我认为你可以使用notifyDataSetChanged(); 在onFilterComplete方法中。