Android ListView用EditText实现搜索功能效果
前言
最近在开发一个im项目的时候有一个需求就是,好友搜索功能。即在edittext中输入好友名字,listview列表中动态展示刷选的好友列表。我把这个功能抽取出来了,先贴一下效果图:
分析
在查阅资料以后,发现其实android中已经帮我们实现了这个功能,如果你的listview使用的是系统的arrayadapter,那么恭喜你,下面的事情就很简单了,你只需要调用下面的代码就可以实现了:
searchedittext.addtextchangedlistener(new textwatcher() { @override public void ontextchanged(charsequence cs, int arg1, int arg2, int arg3) { // when user change the text madapter.getfilter().filter(cs); } @override public void beforetextchanged(charsequence cs, int arg1, int arg2, int arg3) { // } @override public void aftertextchanged(editable arg0) { // } });
你没看错,就一行 madapter.getfilter().filter(cs);便可以实现这个搜索功能。不过我相信大多数adapter都是自定义的,基于这个需求,我去分析了下arrayadapter,发现它实现了filterable接口,那么接下来的事情就比较简单了,就让我们自定的adapter也去实现filterable这个接口,不久可以实现这个需求了吗。下面贴出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(); } } } }
实现
首先写了一个model(user)模拟数据
public class user { private int avatarresid; private string name; public user(int avatarresid, string name) { this.avatarresid = avatarresid; this.name = name; } public int getavatarresid() { return avatarresid; } public void setavatarresid(int avatarresid) { this.avatarresid = avatarresid; } public string getname() { return name; } public void setname(string name) { this.name = name; } }
自定义一个adapter(useradapter)继承自baseadapter,实现了filterable接口,adapter一些常见的处理,我都去掉了,这里主要讲讲filterable这个接口。
/** * contains the list of objects that represent the data of this adapter. * adapter数据源 */ private list<user> 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<user> moriginalvalues; private arrayfilter mfilter; @override public filter getfilter() { if (mfilter == null) { mfilter = new arrayfilter(); } return mfilter; }
写一个arrayfilter类继承自filter类,我们需要两个方法:
//执行过滤的方法 protected filterresults performfiltering(charsequence prefix);
//得到过滤结果 protected void publishresults(charsequence prefix, filterresults results);
贴上完整的代码,注释已经写的不能再详细了
/** * 过滤数据的类 */ /** * <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> * <p/> * 一个带有首字母约束的数组过滤器,每一项不是以该首字母开头的都会被移除该list。 */ private class arrayfilter extends filter { //执行刷选 @override protected filterresults performfiltering(charsequence prefix) { filterresults results = new filterresults();//过滤的结果 //原始数据备份为空时,上锁,同步复制原始数据 if (moriginalvalues == null) { synchronized (mlock) { moriginalvalues = new arraylist<>(mdatas); } } //当首字母为空时 if (prefix == null || prefix.length() == 0) { arraylist<user> list; synchronized (mlock) {//同步复制一个原始备份数据 list = new arraylist<>(moriginalvalues); } results.values = list; results.count = list.size();//此时返回的results就是原始的数据,不进行过滤 } else { string prefixstring = prefix.tostring().tolowercase();//转化为小写 arraylist<user> values; synchronized (mlock) {//同步复制一个原始备份数据 values = new arraylist<>(moriginalvalues); } final int count = values.size(); final arraylist<user> newvalues = new arraylist<>(); for (int i = 0; i < count; i++) { final user value = values.get(i);//从list<user>中拿到user对象 // final string valuetext = value.tostring().tolowercase(); final string valuetext = value.getname().tostring().tolowercase();//user对象的name属性作为过滤的参数 // first match against the whole, non-splitted value if (valuetext.startswith(prefixstring) || valuetext.indexof(prefixstring.tostring()) != -1) {//第一个字符是否匹配 newvalues.add(value);//将这个item加入到数组对象中 } 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)) {//一旦找到匹配的就break,跳出for循环 newvalues.add(value); break; } } } } results.values = newvalues;//此时的results就是过滤后的list<user>数组 results.count = newvalues.size(); } return results; } //刷选结果 @override protected void publishresults(charsequence prefix, filterresults results) { //noinspection unchecked mdatas = (list<user>) results.values;//此时,adapter数据源就是过滤后的results if (results.count > 0) { notifydatasetchanged();//这个相当于从mdatas中删除了一些数据,只是数据的变化,故使用notifydatasetchanged() } else { /** * 数据容器变化 ----> notifydatasetinvalidated 容器中的数据变化 ----> notifydatasetchanged */ notifydatasetinvalidated();//当results.count<=0时,此时数据源就是重新new出来的,说明原始的数据源已经失效了 } } }
特别说明
//user对象的name属性作为过滤的参数 final string valuetext = value.getname().tostring().tolowercase();
这个地方是,你要进行搜索的关键字,比如我这里使用的是user对象的name属性,就是把用户名当作关键字来进行过滤筛选的。这里要根据你自己的具体逻辑来进行设置。
if (valuetext.startswith(prefixstring) || valuetext.indexof(prefixstring.tostring()) != -1)
在这里进行关键字匹配,如果你只想使用第一个字符匹配,那么你只需要使用这行代码就可以了:
//首字符匹配 valuetext.startswith(prefixstring)
如果你的需求是只要输入的字符出现在listview列表中,那么该item就要显示出来,那么你就需要这行代码了:
//你输入的关键字包含在了某个item中,位置不做考虑,即可以不是第一个字符 valuetext.indexof(prefixstring.tostring()) != -1
这样就完成了一个edittext + listview实现搜索的功能。我在demo中用两种方法实现了这一效果。第一种是系统的arrayadapter实现,第二种是自定义adapter实现。
demo下载地址:editsearch_jb51.rar
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。