如何使用ArrayAdapter为ListView编写自定义过滤器

时间:2010-03-25 20:59:20

标签: android listview android-arrayadapter

我有一个连接到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”开头的listitems的正确计数,但是适配器没有得到它。第二个调试语句打印一个计数1150项。这是列表中的完整项目数。

因此,过滤器不会告诉适配器它已经过滤了基础数据。

我现在想知道:我的适配器中是否有代码,以便从过滤器获取更新?我必须编写自定义过滤器吗?我该怎么办?

2 个答案:

答案 0 :(得分:27)

实际上

我注意到我应该使用'originalItems'列表在performFiltering中构建新的过滤后的列表。

这将解决您在更改过滤器中的文本时遇到的任何问题。例如。你搜索'面包'然后退格到'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(用户输入)。我们将TextWatcher附加到此EditText,以确保我们收到更新通知。这意味着它应该适用于所有设备,无论用户在硬键盘还是软键盘上打字(我都有HTC DesireZ和旧G1)。

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

layout for the activity - giatoz.xml

由于我们希望以自定义样式显示行,因此我们还为行本身提供了一个布局xml文件: Row xml file

以下是整个Activity本身的代码。从ListActivity扩展,此类有一个内部类充当适配器,从ArrayAdapter扩展。这是在Activity的onCreate中实例化的,并且现在传递了一个简单的字符串列表。注意它在第39-40行上的创建方式。我们的行的特殊布局与项目列表一起传递。

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

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

答案 1 :(得分:1)

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