当前位置: 移动技术网 > IT编程>移动开发>Android > Android ListView用EditText实现搜索功能效果

Android ListView用EditText实现搜索功能效果

2019年07月24日  | 移动技术网IT编程  | 我要评论

姫川りな,羽毛球世锦赛决赛,太子李明远和贾跃亭

前言

最近在开发一个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下载地址:

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网