Android开发:SearchView与搜索

搜索控件SearchView介绍

我们在Android开发中,需要实现搜索功能,例如文章搜索、城市地点搜索和字典搜索等等,对此实现搜索功能可以使用SearchView,它是Android的一个控件,继承自LinearLayout。

SearchView的继承关系

类似于EditText,其能接收用户在搜索框输入的查询,通过适当配置可以将查询交给对应搜索Activity并返回相应结果,效果如下:

城市地点搜索

 一般情况下就是SearchView和ListView(或是RecyclerView)配合使用,前者接收用户的查询,后者用于返回显示的查询结果。

SearchView的XML属性

SearchView具备xml属性无非就这五种,在设计布局时可以自己选择相应的属性进行设置。

属性关联方法描述

android:iconifiedByDefault

setIconifiedByDefault(boolean)搜索视图的默认状态(是否在搜索框内)

android:imeOptions

setImeOptions(int)

要在查询文本字段上设置的 IME 选项。

必须是以下常量值的一个或多个(用“|”分隔)

android:inputType

setInputType(int)

要在查询文本字段上设置的输入类型。

必须是以下常量值的一个或多个(用“|”分隔)

android:maxWidth

setMaxWidth(int)设置SearchView最大宽度

android:queryHint

setQueryHint(CharSequence)

要在空查询字段中显示的可选查询提示字符串。

可以是字符串值,使用 '\\;' 为unicode字符转义诸如 '\\n' 或 '\\uxxxx' 之类的字符

若是使用SearchView进行搜索,则通常情况下将inputType属性设置成 "textFilter" 进行文本过滤,属性queryHint中输入想提示用户的内容,代码如下:

​<androidx.appcompat.widget.SearchView
    android:id="@+id/searchView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:iconifiedByDefault="false"
    android:inputType="textFilter"
    app:defaultQueryHint="请输入搜索内容" />

为了适配其他Android版本机型,需要使用命名空间app,在布局中引入 xmlns:app="http://schemas.android.com/apk/res-auto" 这段代码。

实现可搜索的Activity

只有SearchView的话可不能实现搜索功能,在用户眼里就和EditView无异。这时就要进入激动人心的java代码书写,其中步骤如下:

  1. 绑定SearchView和ListView并设置二者的属性
  2. 实现ListView的适配器Adapter和其中的过滤规则
  3. 在Activity中重写搜索改变方法
  4. 设置ListView各项的监听器实现后续逻辑

其中第一步过于简单可以略过,从第二步开始其实就是ListView的相关内容,即数据List和Adapter适配

适配器Adatper和过滤规则Filter

关于Adatper的内容不多赘述,其中值得注意的是,在写ListView的Adapter时不光继承BaseAdapter,同时需要实现Filterable,即 public class SearchAdapter extends BaseAdapter implements Filterable {...} 来创建我们的过滤规则,而且对于绑定的数据,List需要两份,一份用于数据和ListView的绑定,另一份用于展示过滤后的数据,演示的java代码如下:

​
public class SearchAdapter extends BaseAdapter implements Filterable {
    private Context context;
    private List<String> searchList;
    private List<String> backSearchList;

    public SearchAdapter(Context context, List<String> list) {
        this.context = context;
        this.SearchList = list;
        this.backSearchList = list;
    }

    //...
}

接着便要着手我们的自定义过滤规则了,通过继承Filter类,重写performFiltering方法定义规则如下:

​/**
 * 定义过滤器的类来定义过滤规则
 */
class MyFilter extends Filter {
    //在performFiltering(CharSequence charSequence)这个方法中定义过滤规则
    @Override
    protected FilterResults performFiltering(CharSequence constraint) {
        List<String> list = new ArrayList<>();
        if (!TextUtils.isEmpty(constraint)) {
            //当过滤的关键字不为空的时候,把符合条件的数据对象添加到集合中
            for (String search : searchList) {
                if (search.contains(constraint)) {
                    //要匹配的item中的view
                    list.add(search);
                }
            }
        }
        FilterResults results = new FilterResults();
        results.values = list;
        results.count = list.size();
        return results;
    }

其中我们先定义了一个list来暂存过滤后的结果数据列表,通过判断用户输入的constraint内容后,如果不是空则遍历原先定义的search,并把包含constraint的内容保存到list中,最后返回包含list数据和大小的result过滤结果。

然后重写publishResults方法让适配器更新搜索界面。

//在publishResults方法中让适配器更新界面
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
    backSearchList = (List<String>) results.values;
    if (results.count > 0) {
        notifyDataSetChanged(); //通知数据发生了改变
    } else {
        notifyDataSetInvalidated(); //通知数据失效
    }
}

在代码中我们将先前返回的results的内容保存至另一份backSearchList中,并通知数据改变与否。

然后别忘了在SearchAdapter类中声明我们的自定义myFilter,同时需要重写getFilter方法。

下面是完整的SearchAdatper类的代码:

public class SearchAdapter extends BaseAdapter implements Filterable {
    private Context context;
    private List<CitySearch> searchList;
    private List<CitySearch> backSearchList;
    MyFilter mFilter;
    
    //Search为自定义的类
    public SearchAdapter(Context context, List<Search> searchList) {
        this.context = context;
        this.searchList = searchList;
        this.backSearchList = searchList;
    }

    public SearchAdapter() {

    }

    //getCount和getItem都要返回backSearchList的数据
    @Override
    public int getCount() {
        return backSearchList.size();
    }

    @Override
    public Object getItem(int position) {
        return backSearchList.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    class ViewHolder {

        //定义控件

    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder = null;
        if (convertView == null) {
            convertView = View.inflate(context, R.layout.activity_search_item, null);
            holder = new ViewHolder();

            //holder定义的控件绑定布局控件

            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        //控件内容和list数据绑定

        return convertView;
    }

    @Override
    public Filter getFilter() {
        if (mFilter == null) {
            mFilter = new MyFilter();
        }
        return mFilter;
    }

    /**
     * 定义过滤器的类来定义过滤规则
     */
    class MyFilter extends Filter {
        //在performFiltering(CharSequence charSequence)这个方法中定义过滤规则
        @Override
        protected FilterResults performFiltering(CharSequence constraint) {
            List<Search> list = new ArrayList<>();
            if (!TextUtils.isEmpty(constraint)) {
                //当过滤的关键字不为空的时候,把符合条件的数据对象添加到集合中
                for (Search search : citySearchList) {
                    if (search.getAreaName().contains(constraint)) {
                        //要匹配的item中的view
                        list.add(search);
                    }
                }
            }
            //results保存过滤的数据
            FilterResults results = new FilterResults();
            results.values = list;
            results.count = list.size();
            return results;
        }

        //在publishResults方法中让适配器更新界面
        @Override
        protected void publishResults(CharSequence constraint, FilterResults results) {
            backSearchList = (List<Search>) results.values;
            if (results.count > 0) {
                notifyDataSetChanged(); //通知数据发生了改变
            } else {
                notifyDataSetInvalidated(); //通知数据失效
            }
        }
    }
}

需要注意的是,在重写getCount和getItem要返回的是backSearchList的数据,因为最后为用户展示的是过滤后的结果,这些结果是与backSearchList数据对应的。

在活动Activity中重写搜索方法

上面一口气说了这么多,接下来就简单许多了,在我们的搜索Activity中只需重写onQueryTextChange和onQueryTextSubmit两个方法,前者在用户输入字符时激发,后者则是单击搜索按钮时激发。

@Override 
public boolean onQueryTextChange(String newText) {     
    //如果newText是长度为0的字符串
    if (TextUtils.isEmpty(newText)) {
        //清除ListView的过滤
        searchListView.clearTextFilter();
        searchAdapter.getFilter().filter("");
    } else {
        //使用用户输入的内容对ListView的列表项进行过滤
        //searchListView.setFilterText(newText);
        searchAdapter.getFilter().filter(newText);
    }
    return true;
}

@Override
public boolean onQueryTextSubmit(String query) {
    return true;
}

在其中我们不难发现,当搜索框中没有输入时,先使用clearTextFilter清除原本的过滤,同时设置filter为空,这样不显示没有过滤的全部数据,当然如果想显示所有数据那就删除这句代码。如果搜索框有内容时就将newText设置过滤,此时ListView就会显示包含newText搜索内容的数据。然后在Activity中添加

searchView.setOnQueryTextListener(this);

这句代码以设置SearchView的监听器

完成你的后续逻辑

你觉得仅是这样就结束了吗,这个时候只是完成了搜索和过滤功能,用户只能进行搜索,如果用户想搜索后点击查看,例如百度搜索跳转界面。这个时候你还需要添加你自己的后续逻辑,如ListView中设置监听器。

searchListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                //search获取点击的item
                Search search = (Search) parent.getAdapter().getItem(position);

                //后续逻辑例如根据search内容跳转

            }
        });

中间的内容就根据自己的代码进行修改,例如使用intent跳转。

后记

这篇文章是在本人开发天气App所遇到的技术问题中得到的经验和总结,若是文章中出现的错误还望批评指正,另附上参考链接和天气App的GitHub地址:

创建搜索界面  |  Android 开发者  |  Android Developers (google.cn)

SearchView  |  Android Developers (google.cn)

Gilbert2510/WindowWeather (github.com)

  • 12
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柚之语

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值