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()方法:
大家可能对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)方法,实现实时搜索
阅读全文
0 0
- Android数据过滤器Filter探索之使用与改造(一)
- Android数据过滤器:Filter
- Android数据过滤器:Filter
- Android数据过滤器:Filter
- 过滤器filter使用之案例二
- JavaWeb---过滤器Filter---(一)
- Filter过滤器总结(一)
- Servlet之Filter过滤器
- Servlet之过滤器filter
- javaWeb之过滤器Filter
- 【JavaWeb】之过滤器Filter
- Servlet之过滤器Filter
- Java之Filter过滤器
- Swift之过滤器(filter)
- Servlet之Filter过滤器
- angularjs之filter过滤器
- springboot之过滤器(filter)
- Servlet之Filter过滤器
- DOS/UNIX回车符解密
- 使用activeMQ发送短信验证码
- LruCacheUtils内存缓存的使用
- 大于等于0小于等于100的正数用正则表达式表示
- iOS开发网络篇 一一 NSURLConnection和Runloop(面试题)
- Android数据过滤器Filter探索之使用与改造(一)
- odoo 绕过点击按钮时对required字段的校验
- ThreadPoolExecutor机制 及使用探讨
- JavaScript 内建对象Array、String、Math及自定义对象的使用
- 学习笔记之多线程编程synchronized
- #113. isbn号码
- 常用在线工具
- MySQL基础操作
- hibernate中如何用Annotion注解设置model中的字段不持久化
原创粉丝点击
热门IT博客
热门问题
老师的惩罚
人脸识别
我在镇武司摸鱼那些年
重生之率土为王
我在大康的咸鱼生活
盘龙之生命进化
天生仙种
凡人之先天五行
春回大明朝
姑娘不必设防,我是瞎子
遵义医科大学附属医院
遵义市人力资源保障局
遵义市第一人民医院
遵义到贵阳高铁
贵州省遵义医学院
遵义市公共资源交易中心
遵义第一人民医院
遵义房产信息网
遵义旅游景点大全
遵义人才市场
遵义哪里好玩
遵义招聘信息
遵义周边自驾游
遵义站是哪个站
遵义是那个省
遵义市有几个县
遵义市有几个区
尊义
遵从内心
遵从
遵从自己的内心的名言
遵从自己的内心
遵从符文之语属性
暗黑2符文之语遵从
遵化吧
遵化市
遵化人才网
遵化限号
遵化房价
遵化二手房
遵化邮编
河北遵化
遵化 买花
遵化整形
遵化旅游
遵化旅游景点大全
唐山到遵化汽车时刻表
遵化二手房出售信息
遵化大集时间表
遵化2019今日限号表
遵化限号查询