Android数据过滤器Filter探索之使用与改造(一)

来源:互联网 发布:石油 知乎 编辑:程序博客网 时间:2024/05/31 18:37

由于数据过滤使用较为普遍,特此把Google官方提供的Filter类以及Filterable接口写成一个系列的文章,从使用到源码深入分析,供大家一起分享学习

先附上效果图:
使用场景:一般都是通过EditText中关键字搜索,实现ListView的数据过滤,得到含有关键字的数据。那么Filter类在其中的角色就是过滤者。完成过滤任务后,它会返回过滤后的匹配数据源。
在开始研究Filter的使用前,我们先了解下过滤的大致原理:
首先是对EditText控件的TextChanged进行实时监听,然后对输入的关键字与ListView中的数据源进行循环遍历、过滤,再把新数据源通过适配器刷新到ListView上。
原理其实比较好理解,那么下面我们来看一下Android系统是如何利用Filter、Filterable再配合ArrayAdapter实现上述效果的:
mSearchEt.addTextChangedListener(new TextWatcher() {    @Override    public void onTextChanged(CharSequence cs, int arg1, int arg2, int arg3) {        // 文本实时变化的回调        mAdapter.getFilter().filter(cs);    }        @Override    public void beforeTextChanged(CharSequence cs, int arg1, int arg2, int arg3) {        // 当文本被改变前的回调    }        @Override    public void afterTextChanged(Editable arg0) {        // 文本变化以后的回调    }});
1.首先对EditText的文字变化实时的监听
2.在onTextChanged回调方法中,获取实时text的内容。同时把获取的实时关键字传到适配器的Filter中,实现过滤。由于要实现实时的过滤效果,即输入一个关键字后,自动会执行过滤筛选。所以没有在afterTextChanged方法(text变化完成后,获取text)中执行过滤操作,而是在onTextChanged方法(获取实时text内容)中执行。
也正是mAdapter.getFilter().filter(cs)这一行代码,实现了数据的过滤以及刷新展示的效果。来顺着我们的思路一起往下走,看看这行代码的真实面目。
先进来ArrayAdapter:
public class ArrayAdapter<T> extends BaseAdapter implements Filterable {    /**     * Contains the list of objects that represent the data of this ArrayAdapter.     * The content of this list is referred to as "the array" in the documentation.     */    private List<T> mObjects;        /**     * Lock used to modify the content of {@link #mObjects}. Any write operation     * performed on the array should be synchronized on this lock. This lock is also     * used by the filter (see {@link #getFilter()} to make a synchronized copy of     * the original array of data.     */    private final Object mLock = new Object();        // A copy of the original mObjects array, initialized from and then used instead as soon as    // the mFilter ArrayFilter is used. mObjects will then only contain the filtered values.    private ArrayList<T> mOriginalValues;    private ArrayFilter mFilter;        ...        public Filter getFilter() {        if (mFilter == null) {            mFilter = new ArrayFilter();        }        return mFilter;    }    /**     * <p>An array filter constrains the content of the array adapter with     * a prefix. Each item that does not start with the supplied prefix     * is removed from the list.</p>     */    private class ArrayFilter extends Filter {        @Override        protected FilterResults performFiltering(CharSequence prefix) {            FilterResults results = new FilterResults();            if (mOriginalValues == null) {                synchronized (mLock) {                    mOriginalValues = new ArrayList<T>(mObjects);                }            }            if (prefix == null || prefix.length() == 0) {                ArrayList<T> list;                synchronized (mLock) {                    list = new ArrayList<T>(mOriginalValues);                }                results.values = list;                results.count = list.size();            } else {                String prefixString = prefix.toString().toLowerCase();                ArrayList<T> values;                synchronized (mLock) {                    values = new ArrayList<T>(mOriginalValues);                }                final int count = values.size();                final ArrayList<T> newValues = new ArrayList<T>();                for (int i = 0; i < count; i++) {                    final T value = values.get(i);                    final String valueText = value.toString().toLowerCase();                    // First match against the whole, non-splitted value                    if (valueText.startsWith(prefixString)) {                        newValues.add(value);                    } else {                        final String[] words = valueText.split(" ");                        final int wordCount = words.length;                        // Start at index 0, in case valueText starts with space(s)                        for (int k = 0; k < wordCount; k++) {                            if (words[k].startsWith(prefixString)) {                                newValues.add(value);                                break;                            }                        }                    }                }                results.values = newValues;                results.count = newValues.size();            }            return results;        }        @Override        protected void publishResults(CharSequence constraint, FilterResults results) {            //noinspection unchecked            mObjects = (List<T>) results.values;            if (results.count > 0) {                notifyDataSetChanged();            } else {                notifyDataSetInvalidated();            }        }    }}
发现ArrayAdapter实现了Filterable接口,重写了getFilter方法。同时还添加了继承自Filter的ArrayFilter内部类。
对于getFilter方法,它主要是获得ArrayFilter对象。接下来,我们看下ArrayFilter类都做了什么:
这个类中重写了两个方法:
performFiltering()和publishResults()。performFiltering()是执行过滤的方法,publishResults()是得到过滤结果的方法。
那我们依次看一下,
performFiltering()方法:
先判空数据源,空则加锁赋值最新的data;再对输入的首字母(单复数都可能)进行判空,空则加锁赋值最新的data,同时取出当前的数据源的值和size作为FilterResults。否则,取出首字母,加锁赋值最新的data,对data进行循环遍历,一一和取到的首字母进行比较,匹配,加入新集合。不匹配,则把所有含空格的字符全部拆开,再进行一一比对。最后返回匹配的集合和集合的size。
publishResults()方法:
把得到的结果赋值给当前适配器数据源;如果结果的数量大于0,刷新适配器。否则,认为数据源是新new出来的,以前的数据源失效。
大家可能对notifyDataSetChanged()很熟悉了,但很少听过或者使用到notifyDataSetInvalidated()。下面我简单介绍下这两个方法的区别:
notifyDataSetChanged():通知数据观察者当前所关联的数据源已经发生了改变,任何与该数据有关的视图都应该去刷新自己。
notifyDataSetInvalidated():通知数据观察者当前所关联的数据源已经无效或者不能获得了,一旦触发了这个方法当前的adapter就变得无效了,也不应该报告自己的数据改变了。
对于第一种情况,这里就不做多叙述了;对于第二种情况,由于过滤结果数据源为null,根据performFiltering()中的逻辑,下次再次执行该方法时,肯定重新new了一个数据源,那么数据源的引用就发生了变化,而之前的适配器使用的还是之前的引用,所以这个数据源对象就无效了,所以需要通知适配器更换当前的数据源对象。
其实通过performFiltering()方法中过滤的逻辑valueText.startsWith(prefixString),我们可以得知,它只是对关键字作为首字母,与参数匹配,做筛选,那么我们要想实现自己的过滤逻辑,就可以在这里做文章。
通过ArrayFilter中的过滤和结果产出、数据刷新,就完成了关键字搜索功能。ArrayAdapter只需要通过getFilter()获取过滤对象,再把关键字传入给performFiltering()的prefix就可以了。也就是一开始就提到的mAdapter.getFilter().filter(cs)。这一行代码背后的故事我们听完了,那还有.filter(cs)呢?怎么没看见分析里面有它。这个就是我们后续文章将会分析的Filter源码部分了,此篇文章我们只是先学习下使用和改造。大家可以先想一下,其实无非就是怎么把cs传到performFiltering()的prefix的过程。
好了,ArrayAdapter中的ArrayFilter中方法介绍以及大致流程已经说完了,由于ArrayAdapter源码部分为了保留了其源码性,并没有做注释,如果大家还是有不明白的地方,没关系。毕竟我们只是去分析Android系统中ArrayAdapter是如何使用Filter的。下面我们会通过实际应用改造,让大家更深刻的去理解学习,而且下面做了详细的注释。
我们大多数Adapter都是自定义的,基于这个需求,也参考了ArrayAdapter中的代码,现在就开启我们的改造之路。
首先写了一个TaskModel实体类,模拟数据
public class TaskModel implements Serializable{    private static final long serialVersionUID = 1L;    private String title;//任务名称       public String getTitle() {        return title;    }    public void setTitle(String title) {        this.title = title;    }
自定义一个Adapter(MyTaskListAdapter)继承自BaseAdapter,实现了Filterable接口。
    /**     * Contains the list of objects that represent the data of this Adapter.     * Adapter数据源     */    private List<TaskModel> mDatas;    /**     * This lock is also used by the filter     * (see {@link #getFilter()} to make a synchronized copy of     * the original array of data.     * 过滤器上的锁可以同步复制原始数据。     *      */    private final Object mLock = new Object();    // A copy of the original mObjects array, initialized from and then used instead as soon as    // the mFilter ArrayFilter is used. mObjects will then only contain the filtered values.    //对象数组的备份,当调用ArrayFilter的时候初始化和使用。此时,对象数组只包含已经过滤的数据。    private ArrayList<TaskModel> mOriginalValues;    private ArrayFilter mFilter; @Override    public Filter getFilter() {        if (mFilter == null) {            mFilter = new ArrayFilter();        }        return mFilter;    }
再在MyTaskListAdapter写一个ArrayFilter内部类继承自Filter类
/**     * 对于edittext配合listview过滤数据的类     * 一个带有条件约束的数组过滤器,每一项不符合判断条件的都会被移除该list     * Created by wzg on 2017/12/20 0020.     */    private class ArrayFilter extends Filter {        /**         * 执行过滤的方法         * @param prefix         * @return         */        @Override        protected FilterResults performFiltering(CharSequence prefix) {            // 过滤的结果            FilterResults results = new FilterResults();            // 原始数据备份为空时,上锁,同步复制原始数据            if (mOriginalValues == null) {                synchronized (mLock) {                    mOriginalValues = new ArrayList<>(data);                }            }            // 当首字母为空时            if (prefix == null || prefix.length() == 0) {                ArrayList<TaskModel> list;                // 同步复制一个原始备份数据                synchronized (mLock) {                    list = new ArrayList<>(mOriginalValues);                }                // 此时返回的results就是原始的数据,不进行过滤                results.values = list;                results.count = list.size();            } else {                String prefixString = prefix.toString().toLowerCase();                ArrayList<TaskModel> values;                // 同步复制一个原始备份数据                synchronized (mLock) {                    values = new ArrayList<>(mOriginalValues);                }                final int count = values.size();                final ArrayList<TaskModel> newValues = new ArrayList<>();                for (int i = 0; i < count; i++) {                    // 从List<TaskModel>中拿到TaskModel对象                    final TaskModel value = values.get(i);                    // TaskModel对象的任务名称属性作为过滤的参数                    final String valueText = value.getTitle().toString().toLowerCase();                    // 关键字是否和item的过滤参数匹配                    if (valueText.indexOf(prefixString.toString()) != -1) {                        // 将这个item加入到数组对象中                        newValues.add(value);                    } else {                        // 处理首字符是空格                        final String[] words = valueText.split(" ");                        final int wordCount = words.length;                        for (int k = 0; k < wordCount; k++) {                            // 一旦找到匹配的就break,跳出for循环                            if (words[k].indexOf(prefixString) != -1) {                                newValues.add(value);                                break;                            }                        }                    }                }                // 此时的results就是过滤后的List<TaskModel>数组                results.values = newValues;                results.count = newValues.size();            }            return results;        }        /**         * 得到过滤结果         *         * @param prefix         * @param results         */        @Override        protected void publishResults(CharSequence prefix, FilterResults results) {            // 此时,Adapter数据源就是过滤后的Results            data = (List<TaskModel>) results.values;            if (results.count > 0) {                // 这个相当于从mDatas中删除了一些数据,只是数据的变化,故使用notifyDataSetChanged()                notifyDataSetChanged();            } else {                // 当results.count<=0时,此时数据源就是重新new出来的,说明原始的数据源已经失效了                notifyDataSetInvalidated();            }        }    }
上文说过,在筛选条件这里可以根据自己需求来设置,这里把首字母过滤换成了只要title属性值中不包含该关键字,就过滤掉:if (valueText.indexOf(prefixString.toString()) != -1)
UserAdapter代码完成之后,我们只需要在自己的Activity中进行对EditText的文字变化监听,在onTextChanged方法中执行mMyTaskListAdapter.getFilter().filter(cs)即可实现实时搜索
使用和改造都讲完了,下面我们来总结下:
1.自定义适配器实现Filterable接口,添加继承Filter的过滤内部类
2.在过滤内部类的performFiltering()方法中编写自己的过滤参数和过滤条件
3.在Activity中对EditText的文本内容变化实时监听,在其回调方法onTextChanged中调用自定义适配器的getFilter().filter(cs)方法,实现实时搜索
原创粉丝点击