概述
由于数据过滤使用较为普遍,特此把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)方法,实现实时搜索
最后
以上就是生动大地为你收集整理的Android数据过滤器Filter探索之使用与改造(一)的全部内容,希望文章能够帮你解决Android数据过滤器Filter探索之使用与改造(一)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复