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

由于数据过滤使用较为普遍,特此把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)方法,实现实时搜索
  • 9
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值