如何使用 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

如何在 Android ArrayAdapter 中使用对象数组?

如何使用 ArrayAdapter<myClass>

如何使用 ArrayAdapter 在 ArrayList 中的 ListView 中显示项目(图像的路径)?