欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2023-11-23 13:04:34
前言 最近在开发一个im项目的时候有一个需求就是,好友搜索功能。即在edittext中输入好友名字,listview列表中动态展示刷选的好友列表。我把这个功能抽取出来了,...

前言

最近在开发一个im项目的时候有一个需求就是,好友搜索功能。即在edittext中输入好友名字,listview列表中动态展示刷选的好友列表。我把这个功能抽取出来了,先贴一下效果图:

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

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

分析

在查阅资料以后,发现其实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

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