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

类似于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代码书写,其中步骤如下:
- 绑定SearchView和ListView并设置二者的属性
- 实现ListView的适配器Adapter和其中的过滤规则
- 在Activity中重写搜索改变方法
- 设置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)