Material Design 系列之 SearchView + Toolbar 开发详解

前言

SearchView 是 Android 原生的搜索框控件,它提供了一个用户界面,可以让用户在文本框内输入文字,并允许通过看监听器监控用户输入,当用户输入完成后提交搜索时,也可通过监听器执行实际的搜索。

一、常见属性

相关方法 解释说明
setIconifiedByDefault(boolean) 设置该搜索框默认是否自动缩小为图标
setImeOptions(int) 设置输入法搜索选项字段,默认是搜索,可以是:下一页、发送、完成等
setInputType(int) 设置输入类型
setMaxWidth(int) 设置最大宽度
setQueryHint(CharSequence) 设置查询提示字符串
setThreshold(int threshold) 设置触发查询的最少字符数(默认 2 个字符才会触发查询)
setIconified(boolean iconify) 置搜索框直接展开显示。左侧有放大镜(在搜索框中) 右侧有叉叉,可以关闭搜索框
setIconifiedByDefault(boolean iconified) 设置搜索框直接展开显示。左侧有放大镜(在搜索框外) 右侧无叉叉 有输入内容后有叉叉,不能关闭搜索框
mSearchView.onActionViewExpanded() 设置搜索框直接展开显示。左侧有无放大镜(在搜索框中) 右侧无叉叉 有输入内容后有叉叉,不能关闭搜索框
setSubmitButtonEnabled(boolean enabled) 设置提交按钮是否可见

使用 SearchView 时可使用如下常用方法。

  • setIconifiedByDefault(Boolean iconified):设置该搜索框默认是否自动缩小为图标。

  • setSubmitButtonEnabled(Boolean enabled):设置是否显示搜索按钮。

  • setQueryHint(CharSequence hint):设置搜索框内默认显示的提示文本。

  • setOnQueryTextListener(SearchView.OnQueryTextListener listener):为该搜索框设置事件监听器。

如果为 SearchView 增加一个配套的 ListView,则可以为 SearchView 增加自动完成功能。下面实例示范如何使用 SearchView。

二、基础使用

SearchView 常规使用结合 ToolBar,完成 APP 首页 Tab 栏的搜索功能,ToolBar 加载 Menu 形式来呈现 SearchView 效果。本文重点讲解 SearchView 使用,如果对 ToolBar 相关知识点属性不熟悉的,请参考阅读:

Android Material Design 系列之 Toolbar 使用详解

1、menu 新建 search.xml 文件

     <?xml version="1.0" encoding="utf-8"?>
    <menu xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        tools:context=".toolbar.SearchViewActivity">
      <item
    android:id="@+id/search"
    android:title="搜索"
    app:actionViewClass="android.widget.SearchView"
    app:showAsAction="always" />
    <item
    android:id="@+id/setting"
    android:title="设置"
    app:showAsAction="never" />
    </menu> 

相信学习过 ToolBar 的对上面 menu 文件配置很熟悉,这里需要重点注意app:actionViewClass="android.widget.SearchView"属性,这个属性是用来加载一个 View,传入 SearchView 的完整包名。

2、SearchView 加载 Toolbar

既然可以将 SearchView 加载到 Toolbar 中,那么自然可以获取到 SearchView 控件实例,然后就可以设置 SearchView 相关属性。重写 Activity 中 onCreateOptionsMenu(Menu menu) 方法完成 menu 加载,可以根据MenuItemCompat.getActionView方法获取 SearchView 实例。

     @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.searchview_menu, menu);
      // 获取搜索Menu
        MenuItem item = menu.findItem(R.id.search);
      // 通过MenuItemCompat.getActionView()方法获取SearchView
        SearchView mSearchView = (SearchView) MenuItemCompat.getActionView(item);
    } 

注意:记得在 onCreate()方法里设置setSupportActionBar(toolbar);

3、SearchView 显示模式

  //设置搜索框直接展开显示。左侧有放大镜(在搜索框中) 右侧有叉叉 可以关闭搜索框
    mSearchView.setIconified(false);
    //设置搜索框直接展开显示。左侧有放大镜(在搜索框外) 右侧无叉叉 有输入内容后有叉叉 不能关闭搜索框
    mSearchView.setIconifiedByDefault(false);
  //设置搜索框直接展开显示。左侧有无放大镜(在搜索框中) 右侧无叉叉 有输入内容后有叉叉 不能关闭搜索框
    mSearchView.onActionViewExpanded();` </pre>

4、SearchView 修改样式

1.设置搜索框提交按钮

搜索状态时,最右侧有一个提交按钮,默认是一个箭头图标,使用mSearchView.setSubmitButtonEnabled(true)方法设置。

image

2.更改提交按钮图标

SearchView 内置图标难免会不满足项目需求,查看 SearchView 源码发现,他加载了一个abc_search_view布局,于是打开 xml 文件发现提交按钮的 id。

    final LayoutInflater inflater = LayoutInflater.from(context);
    final int layoutResId = a.getResourceId(R.styleable.SearchView_layout, R.layout.abc_search_view);
    inflater.inflate(layoutResId, this, true);` </pre>

这下大家应该清楚接下来我要做什么骚操作了吧,没错就是 findViewById 获取到提交 View,手动setImageResource()给他设置图片。

    ImageView mSearchGoBtn = mSearchView.findViewById(R.id.search_go_btn);
    mSearchGoBtn.setImageResource(R.drawable.ic_baseline_emoji_emotions_24); 
image

3.设置搜索框属性

我们既然已经掌握如何更改提交按钮图标,那搜索框属性自然也可以使用同样的手段实现,先看下abc_search_view布局中搜索框是如何定义的。

  <view class="androidx.appcompat.widget.SearchView$SearchAutoComplete"
  android:id="@+id/search_src_text"
  android:layout_height="36dip"
  android:layout_width="0dp"
  android:layout_weight="1"
  android:layout_gravity="center_vertical"
  android:paddingLeft="@dimen/abc_dropdownitem_text_padding_left"
  android:paddingRight="@dimen/abc_dropdownitem_text_padding_right"
  android:singleLine="true"
  android:ellipsize="end"
  android:background="@null"
  android:inputType="text|textAutoComplete|textNoSuggestions"
  android:imeOptions="actionSearch"
  android:dropDownHeight="wrap_content"
  android:dropDownAnchor="@id/search_edit_frame"
  android:dropDownVerticalOffset="0dip"
  android:dropDownHorizontalOffset="0dip" /> 

我们发现居然不是直接继承 EditText,而是一个 SearchView 的内部类 SearchAutoComplete,跟踪到源码中发现,它继承自AppCompatAutoCompleteTextViewAppCompatAutoCompleteTextView又继承AutoCompleteTextView,这个类我们应该很熟悉了,那就可以猜想 SearchView 应该也能实现输入内容检索提示的功能。

    public static class SearchAutoComplete extends AppCompatAutoCompleteTextView {}
    public class AppCompatAutoCompleteTextView extends AutoCompleteTextView implements TintableBackgroundView {}
    public class AutoCompleteTextView extends EditText implements Filter.FilterListener {} 

同理 findViewById 获取 SearchAutoComplete,设置相关默认属性:

    SearchView.SearchAutoComplete mSearchEditView = mSearchView.findViewById(R.id.search_src_text);
     mSearchEditView.setHint("请输入搜索内容");  
    mSearchEditView.setHintTextColor(getResources().getColor(R.color.selectable_item_background_general_light_accent))      ;
    mSearchEditView.setTextSize(TypedValue.COMPLEX_UNIT_DIP,15); 

如果只想设置默认提示的话,使用mSearchView.setQueryHint("请输入搜索内容")方法也可以实现,根据自己需求选择合适的方法。

image

4.SearchView 监听事件

  • 搜索框打开监听

      mSearchView.setOnSearchClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
            Toast.makeText(SearchViewActivity.this,"open",Toast.LENGTH_SHORT).show();
      }
    }); 
    
  • 搜索框关闭监听

            mSearchView.setOnCloseListener(new SearchView.OnCloseListener() {
            @Override
            public boolean onClose() {
              Toast.makeText(SearchViewActivity.this,"close",Toast.LENGTH_SHORT).show();
                          return false;
              }
        }); 
    
  • 输入文本变化监听

          mSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
          @Override
          public boolean onQueryTextSubmit(String query) {
            // 提交文本时调用
            Snackbar.make(mSearchGoBtn,query,Snackbar.LENGTH_SHORT).show();
              return false;
        }
    
        @Override
        public boolean onQueryTextChange(String newText) {
            // 文本搜索框发生变化时调用
              return false;
        }
    }); 
    
image

三、实战案例

以上已经介绍了 SearchView 的基本使用,日常开发过程中,往往需求跟系统自带的 View 有所差距(不知道是产品经理问题还是 UI 设计师问题,总之就是很烦)。下面根据自己项目中的经历,讲解下综合实战的方法。

1、Toolbar 返回键监听搜索框

场景:当 SearchView 打开时,点击 Toolbar 返回键,先关闭掉搜索框。

SearchView 并没有公开关闭搜索框方法,但是 SearchView 源码中,有一个onCloseClicked()方法,这个方法里面就是处理了"X"按钮的逻辑,如果搜索框有文本输入,则清除文本内容,否则直接关闭搜索框。

 void onCloseClicked() {
CharSequence text = mSearchSrcTextView.getText();
if (TextUtils.isEmpty(text)) {
    if (mIconifiedByDefault) {
        // If the app doesn't override the close behavior
        if (mOnCloseListener == null || !mOnCloseListener.onClose()) {
            // hide the keyboard and remove focus
            clearFocus();
            // collapse the search field
            updateViewsVisibility(true);
        }
    }
} else {
    mSearchSrcTextView.setText("");
    mSearchSrcTextView.requestFocus();
    mSearchSrcTextView.setImeVisibility(true);
}
  } 

所以我们利用反射(反射相关知识属于 Java 基础,如果还不会使用的小伙伴,自己补习一下),就可以实现应用场景的效果:

toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
    try {
        if (mSearchEditView.isShown()) {
            Method method = mSearchView.getClass().getDeclaredMethod("onCloseClicked");
            method.setAccessible(true);
            method.invoke(mSearchView);
        } else {
            finish();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}
});  

2、去掉搜索框默认下划线

看到网上很多朋友说需要利用反射去掉,当然反射很强大,可以实现,但是这里介绍一种更简便的方式:

获取R.id.search_plateR.id.submit_area对应的 LinearLayout,设置 setBackground 为 null

    // 去掉搜索框默认的下划线
    search_plate = mSearchView.findViewById(R.id.search_plate);
    submit_area = mSearchView.findViewById(R.id.submit_area);
    search_plate.setBackground(null);
    submit_area.setBackground(null);` </pre>

3、设置搜索框背景样式

  • 定义 shape_toolbar.xml 样式文件

    <?xml version="1.0" encoding="utf-8"?>
      <shape xmlns:android="http://schemas.android.com/apk/res/android">
      <corners android:radius="3dp" />
      <solid android:color="#FFF" />
      </shape> 
    
  • 在获取的 SearchView.SearchAutoComplete 设置背景属性

      // 设置搜索框背景样式
    mSearchEditView.setBackground(getDrawable(R.drawable.shape_toolbar));` </pre>
    

四、SearchView 搜索自动提示

上面提到过 SearchView.SearchAutoComplete 是继承 AutoCompleteTextView 类,根据setSuggestionsAdapter()方法可以实现输入搜索内容,自动提示的功能。关于 AutoCompleteTextView 类的使用,这里不做详细讲解,如果还有不会使用的朋友自行补习。

1、获取手机通讯录

setSuggestionsAdapter(CursorAdapter adapter)方法需要一个 CursorAdapter 参数,这里看到 Cursor,很多人就应该清楚,Cursor 光标或者游标。

正常情况下这里应该采用 Cursor 操作数据库,可以实现查询筛选功能,因为案例展示,这里以手机通讯录为例,搜索框输入信息后展示手机通讯录列表,不支持根据输入内容过滤功能。

    // 获取手机通讯录
    Cursor cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null); 

提示:获取通讯录需要手动申请权限

2、SimpleCursorAdapter 对象

      SimpleCursorAdapter simpleAdapter = new SimpleCursorAdapter(
                SearchViewActivity.this,
                R.layout.searchview_item, cursor,
                new String[]{ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME},
                new int[]{R.id.name});` </pre>

参数:

  • context:要使用的上下文环境
  • layout:列表 item 布局资源文件
  • c:Cursor 对象,可以理解为数据集
  • from:是一个 String[]类型的参数,该参数决定提取 Map 对象中哪些 key 对应的 value 来生成列表项
  • to:该参数是一个 int[]类型的参数,该参数决定填充哪些组件

3、SearchView 设置适配器

    // 设置SearchView 适配器
    mSearchView.setSuggestionsAdapter(simpleAdapter);
    //设置触发查询的最少字符数(默认2个字符才会触发查询)
    mSearchEditView.setThreshold(1);` </pre>
image

4、优化搜索列表

SearchView 展示的列表稍微有点丑,正常情况下都是在 Activity 中展示一个列表,所以在 Activity 布局中设置一个 ListView,使用方式和 SearchView.SearchAutoComplete 一致。

    private void setAdapter() {
Cursor cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,null, null, null, null);
if (listView.getAdapter() != null) {
    ((SimpleCursorAdapter) listView.getAdapter()).changeCursor(cursor);
    } else {
    SimpleCursorAdapter simpleAdapter = new SimpleCursorAdapter(SearchViewActivity.this,
            R.layout.searchview_item, cursor,
            new String[]{ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME},
            new int[]{R.id.name});
    listView.setAdapter(simpleAdapter);
}
  } 

在 SearchView 监听事件 setOnQueryTextListener 回调方法onQueryTextChange(String newText)中调用setAdapter()方法,上文讲过 onQueryTextChange 方法在搜索框文本发生变化时调用,可以实现实时刷新功能。

image

五、总结

SearchView 配合 Toolbar 在开发过程中可以快捷的实现 Tab 搜索效果,随着 Android 系统发展,原生控件也越来越漂亮,很多大厂品牌产品都采取原生的控件,所以 Android 高级 UI 部分还是非常值得学习的,如果你还不会,那就抓紧时间上车吧!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容