当您准备好向应用添加搜索功能时,Android 会通过显示在 activity 窗口顶部的搜索对话框或您可以插入布局中的搜索 widget 来帮助您实现界面。搜索对话框和搜索微件均可将用户的搜索查询传递给应用中的特定 activity。这样,用户便可以从提供搜索对话框或搜索微件的任何 activity 发起搜索,并且系统会启动相应的 activity 以执行搜索和显示结果。
搜索对话框和搜索微件提供的其他功能包括:
- 语音搜索
- 基于近期查询的搜索建议
- 与应用数据中的实际结果匹配的搜索建议
本文档介绍了如何设置您的应用,以使用搜索对话框或搜索 widget,从而提供由 Android 系统辅助传递搜索查询的搜索界面。
相关资源:
基础知识
在开始之前,请决定是使用搜索对话框还是搜索 widget 来实现搜索界面。这两种方法提供相同的搜索功能,但在方式上略有不同:
- 搜索对话框是由 Android 系统控制的界面组件。被用户激活后,搜索对话框将显示在 activity 的顶部。
Android 系统控制搜索对话框中的所有事件。当用户提交查询时,系统会将查询传递给您指定用于处理搜索的 Activity。该对话框还可以在用户输入内容时提供搜索建议。
- 搜索微件是
SearchView
的实例,您可以将其放在布局中的任意位置。默认情况下,搜索 widget 的行为类似于标准的EditText
widget,并且不执行任何操作,但您可以将其配置为,让 Android 系统像搜索对话框一样处理所有输入事件,将查询传递给相应的 activity 并提供搜索建议。
当用户从搜索对话框或搜索 widget 执行搜索时,系统会创建一个 Intent
并将用户查询存储在其中。然后,系统会启动您声明用于处理搜索的 activity(即“可搜索 activity”),并向其传递 intent。如需设置您的应用以支持此类辅助搜索,您需要以下条件:
- 搜索配置
- 此 XML 文件用于为搜索对话框或搜索微件配置某些设置。其中包括语音搜索、搜索建议和搜索框的提示文本等功能的设置。
- 可搜索 activity
- 接收搜索查询、搜索数据并显示搜索结果的
Activity
。- 搜索界面,由以下两项之一提供:
- 搜索对话框
- 默认情况下,搜索对话框处于隐藏状态。如果您调用
onSearchRequested()
,当用户点按 Search 按钮时,该按钮将显示在屏幕顶部。- 一个
SearchView
widget- 使用搜索微件,您可以将搜索框放在 activity 中的任意位置,包括作为应用栏中的操作视图。
- 一个
本文档的其余部分将向您介绍如何创建搜索配置和可搜索 activity,以及如何使用搜索对话框或搜索 widget 实现搜索界面。
创建可搜索配置
首先,您需要一个称为搜索配置的 XML 文件。它会配置搜索对话框或搜索微件的某些界面方面,并定义建议和语音搜索等功能的行为方式。此文件通常名为 searchable.xml
,且必须保存在 res/xml/
项目目录中。
搜索配置文件必须包含 <searchable>
元素作为其根节点,并指定一个或多个属性,如以下示例所示:
<?xml version="1.0" encoding="utf-8"?> <searchable xmlns:android="http://schemas.android.com/apk/res/android" android:label="@string/app_label" android:hint="@string/search_hint" > </searchable>
android:label
属性是唯一一个必需的属性。它指向一个字符串资源,该资源必须是应用名称。此标签不会显示给用户,直到您为快速搜索框启用了搜索建议,此时此标签会显示在系统设置的可搜索项列表中。
虽然并非强制性要求,但我们建议您始终添加 android:hint
属性,该属性会在用户输入查询之前在搜索框中提供一个提示字符串。提示很重要,因为它可以为用户提供重要线索,让他们了解可以搜索的内容。
<searchable>
元素接受其他几个属性。但是,在添加搜索建议和语音搜索等功能之前,您不需要使用大多数属性。如需详细了解搜索配置文件,请参阅搜索配置参考文档。
创建可搜索 activity
可搜索 activity 是应用中基于查询字符串执行搜索并显示搜索结果的 Activity
。
当用户在搜索对话框或搜索微件中执行搜索时,系统会启动可搜索 activity,并通过 ACTION_SEARCH
操作在 Intent
中向其传递搜索查询。可搜索 activity 会从 intent 的 QUERY
extra 中检索查询,然后搜索数据并显示结果。
由于您可以在应用中的任何其他 activity 中添加搜索对话框或 widget,因此系统必须知道哪个 activity 是可搜索 activity,才能正确传递搜索查询。因此,请先在 Android 清单文件中声明可搜索 activity
声明可搜索 activity
如果您还没有这样的网域,请创建一个可执行搜索并显示结果的 Activity
。目前还不需要实现搜索功能,只需创建一个可在清单中声明的 activity 即可。在清单的 <activity>
元素内,执行以下操作:
- 在
<intent-filter>
元素中声明要接受ACTION_SEARCH
intent 的 activity。 - 在
<meta-data>
元素中指定要使用的搜索配置。
具体可见以下示例:
<application ... > <activity android:name=".SearchableActivity" > <intent-filter> <action android:name="android.intent.action.SEARCH" /> </intent-filter> <meta-data android:name="android.app.searchable" android:resource="@xml/searchable"/> </activity> ... </application>
<meta-data>
元素必须包含值为 "android.app.searchable"
的 android:name
属性和引用可搜索配置文件的 android:resource
属性。在前面的示例中,它引用了 res/xml/searchable.xml
文件。
执行搜索
在清单中声明可搜索 activity 后,请按照以下步骤在可搜索 activity 中执行搜索:
接收查询
当用户从搜索对话框或搜索微件执行搜索时,系统会启动可搜索 activity 并向其发送 ACTION_SEARCH
intent。此 intent 在 QUERY
字符串 extra 中携带搜索查询。在 activity 启动时检查此 intent 并提取字符串。
例如,以下代码段展示了如何在可搜索 activity 启动时获取搜索查询:
Kotlin
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.search) // Verify the action and get the query. if (Intent.ACTION_SEARCH == intent.action) { intent.getStringExtra(SearchManager.QUERY)?.also { query -> doMySearch(query) } } }
Java
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.search); // Get the intent, verify the action, and get the query. Intent intent = getIntent(); if (Intent.ACTION_SEARCH.equals(intent.getAction())) { String query = intent.getStringExtra(SearchManager.QUERY); doMySearch(query); } }
QUERY
字符串始终包含在 ACTION_SEARCH
intent 中。在前面的示例中,检索了查询并将其传递给执行实际搜索操作的本地 doMySearch()
方法。
搜索您的数据
存储和搜索数据的过程因您的应用而异。您可以通过多种方式存储和搜索数据,但本文档并未介绍具体方法。根据您的需求和数据格式考虑如何存储和搜索数据。以下是您或许可以参考的提示:
- 如果您的数据存储在设备上的 SQLite 数据库中,则使用 FTS3 而非
LIKE
查询执行全文搜索,可以提供更强大的文本数据搜索,并显著加快生成结果的速度。如需了解 FTS3,请参阅 sqlite.org;如需了解 Android 上的 SQLite,请参阅SQLiteDatabase
类。 - 如果您的数据是在线存储的,则用户的数据连接可能会妨碍感知到的搜索性能。您可能希望在搜索返回结果之前显示进度指示器。如需查看网络 API 的参考信息,请参阅
android.net
;如需了解如何显示进度指示器,请参阅ProgressBar
。
展示结果
无论您的数据位于何处以及如何搜索,我们都建议使用 Adapter
将搜索结果返回到可搜索 activity。这样一来,您就可以在 RecyclerView
中显示所有搜索结果。如果您的数据来自 SQLite 数据库查询,您可以使用 CursorAdapter
将结果应用于 RecyclerView
。如果您的数据采用其他格式,您可以创建 BaseAdapter
的扩展。
Adapter
将一组数据中的每个项绑定到一个 View
对象。将 Adapter
应用于 RecyclerView
时,每条数据都会作为单个视图插入列表中。Adapter
只是一个接口,因此需要 CursorAdapter
(用于绑定来自 Cursor
的数据)等实现。如果现有实现都不适用于您的数据,您可以通过 BaseAdapter
实现自己的实现。
使用搜索对话框
搜索对话框在屏幕顶部提供了一个悬浮搜索框,应用图标位于左侧。搜索对话框可以在用户输入内容时提供搜索建议。当用户执行搜索时,系统会将搜索查询发送到执行搜索的可搜索 activity。
默认情况下,搜索对话框在用户激活之前始终处于隐藏状态。您的应用可以通过调用 onSearchRequested()
激活搜索对话框。不过,在您为 activity 启用搜索对话框之前,此方法不起作用。
如需使搜索对话框能够执行搜索,请向系统指明哪个可搜索 activity 必须从搜索对话框接收搜索查询。例如,在有关创建可搜索 activity 的上一部分中,创建了一个名为 SearchableActivity
的可搜索 activity。如果您希望单独的 activity(例如名为 OtherActivity
的 activity)显示搜索对话框并将搜索结果传递给 SearchableActivity
,请在清单中声明 SearchableActivity
是用于 OtherActivity
中的搜索对话框的可搜索 activity。
如需为 activity 的搜索对话框声明可搜索 activity,请在相应 activity 的 <activity>
元素内添加 <meta-data>
元素。<meta-data>
元素必须包含用于指定可搜索 activity 的类名的 android:value
属性和值为 "android.app.default_searchable"
的 android:name
属性。
例如,下面是可搜索 activity SearchableActivity
和另一个 activity OtherActivity
的声明,后者使用 SearchableActivity
执行在其搜索对话框中执行的搜索:
<application ... > <!-- This is the searchable activity; it performs searches. --> <activity android:name=".SearchableActivity" > <intent-filter> <action android:name="android.intent.action.SEARCH" /> </intent-filter> <meta-data android:name="android.app.searchable" android:resource="@xml/searchable"/> </activity> <!-- This activity enables the search dialog to initiate searches in the SearchableActivity. --> <activity android:name=".OtherActivity" ... > <!-- Enable the search dialog to send searches to SearchableActivity. --> <meta-data android:name="android.app.default_searchable" android:value=".SearchableActivity" /> </activity> ... </application>
由于 OtherActivity
现在包含一个 <meta-data>
元素来声明要用于搜索的可搜索 activity,因此该 activity 会启用搜索对话框。虽然用户位于此 activity 中,但 onSearchRequested()
方法会激活搜索对话框。当用户执行搜索时,系统会启动 SearchableActivity
并向其传递 ACTION_SEARCH
intent。
如果您希望应用中的每个 activity 都提供搜索对话框,请插入前面的 <meta-data>
元素作为 <application>
元素的子元素,而不是每个 <activity>
。这样,每个 activity 都会继承该值,提供搜索对话框,并将搜索传递给同一可搜索 activity。如果您有多个可搜索 activity,则可以通过在各个 activity 内放置不同的 <meta-data>
声明来替换默认的可搜索 activity。
为您的 activity 启用搜索对话框后,您的应用就可以执行搜索了。
调用搜索对话框
虽然某些设备提供专用的搜索按钮,但该按钮的行为可能会因设备而异,并且许多设备根本不提供搜索按钮。因此,在使用搜索对话框时,您必须在界面中提供一个搜索按钮,该按钮通过调用 onSearchRequested()
来激活搜索对话框。
例如,在选项菜单或界面布局中添加调用 onSearchRequested()
的搜索按钮。
您还可以启用“输入内容即可搜索”功能,在用户开始通过键盘输入内容时激活搜索对话框。按键会插入搜索对话框中。您可以通过在 activity 的 onCreate()
方法期间调用 setDefaultKeyMode
或 DEFAULT_KEYS_SEARCH_LOCAL
,在 activity 中启用输入搜索功能。
搜索对话框对 Activity 生命周期的影响
搜索对话框是一个浮动在屏幕顶部的 Dialog
。它不会导致 activity 堆栈发生任何更改,因此当搜索对话框出现时,系统不会调用 onPause()
等生命周期方法。您的 activity 会失去输入焦点,因为输入焦点已分配给搜索对话框。
如果您希望在激活搜索对话框时收到通知,请替换 onSearchRequested()
方法。当系统调用此方法时,表示您的 activity 会失去搜索对话框的输入焦点,因此您可以执行任何适合该事件的工作,例如暂停游戏。除非您要传递搜索上下文数据(如本文档的其他部分中所述),否则应通过调用父类实现来结束该方法:
Kotlin
override fun onSearchRequested(): Boolean { pauseSomeStuff() return super.onSearchRequested() }
Java
@Override public boolean onSearchRequested() { pauseSomeStuff(); return super.onSearchRequested(); }
如果用户通过点按“返回”按钮取消搜索,搜索对话框将关闭,并且 activity 会重新获得输入焦点。您可以进行注册,以便在使用 setOnDismissListener()
和/或 setOnCancelListener()
关闭搜索对话框时收到通知。您只需注册 OnDismissListener
,因为每次搜索对话框关闭时都会调用它。OnCancelListener
仅适用于用户明确退出搜索对话框的事件,因此执行搜索时不会调用它。执行搜索后,搜索对话框会自动消失。
如果当前 activity 不是可搜索 activity,则当用户执行搜索时,会触发常规的 activity 生命周期事件,即当前 activity 会收到 onPause()
,如 activity 简介中所述。不过,如果当前 activity 是可搜索 activity,则会发生以下两种情况之一:
- 默认情况下,可搜索 Activity 会通过调用
onCreate()
来接收ACTION_SEARCH
intent,并且会将该 Activity 的新实例置于 Activity 堆栈的顶部。Activity 堆栈中现在有可搜索 Activity 的两个实例,因此点按“返回”按钮可让您返回到可搜索 Activity 的上一个实例,而不是退出可搜索 Activity。 - 如果您将
android:launchMode
设为"singleTop"
,可搜索 Activity 会通过调用onNewIntent(Intent)
来接收ACTION_SEARCH
intent,并传递新的ACTION_SEARCH
intent。例如,以下示例展示了如何处理这种情况(可搜索 Activity 的启动模式为"singleTop"
):Kotlin
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.search) handleIntent(intent) } override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) setIntent(intent) handleIntent(intent) } private fun handleIntent(intent: Intent) { if (Intent.ACTION_SEARCH == intent.action) { intent.getStringExtra(SearchManager.QUERY)?.also { query -> doMySearch(query) } } }
Java
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.search); handleIntent(getIntent()); } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); setIntent(intent); handleIntent(intent); } private void handleIntent(Intent intent) { if (Intent.ACTION_SEARCH.equals(intent.getAction())) { String query = intent.getStringExtra(SearchManager.QUERY); doMySearch(query); } }
与执行搜索部分中的示例代码相比,用于处理搜索 intent 的所有代码现在位于
handleIntent()
方法中,因此onCreate()
和onNewIntent()
均可执行该代码。当系统调用
onNewIntent(Intent)
时,activity 不会重启,因此getIntent()
方法返回的 intent 与通过onCreate()
接收的 intent 相同。 因此,您必须在onNewIntent(Intent)
内调用setIntent(Intent)
:这样,如果您日后调用getIntent()
,系统会更新 activity 保存的 intent。
通常最好使用 "singleTop"
启动模式,因为在完成搜索后,用户可能会执行其他搜索,而您不希望应用创建多个可搜索 activity 实例。我们建议您在应用清单中将可搜索 activity 设置为 "singleTop"
启动模式,如以下示例所示:
<activity android:name=".SearchableActivity" android:launchMode="singleTop" > <intent-filter> <action android:name="android.intent.action.SEARCH" /> </intent-filter> <meta-data android:name="android.app.searchable" android:resource="@xml/searchable"/> </activity>
传递搜索上下文数据
在某些情况下,您可以针对执行的每个搜索,在可搜索 Activity 中对搜索查询进行必要的优化。不过,如果您想根据用户执行搜索的 activity 优化搜索条件,则可以在系统发送到可搜索 activity 的 intent 中提供其他数据。您可以在 ACTION_SEARCH
intent 中包含的 APP_DATA
Bundle
中传递其他数据。
如需将此类数据传递给可搜索 activity,请为用户可从中执行搜索的 activity 替换 onSearchRequested()
方法,创建包含额外数据的 Bundle
,并调用 startSearch()
以激活搜索对话框。例如:
Kotlin
override fun onSearchRequested(): Boolean { val appData = Bundle().apply { putBoolean(JARGON, true) } startSearch(null, false, appData, false) return true }
Java
@Override public boolean onSearchRequested() { Bundle appData = new Bundle(); appData.putBoolean(SearchableActivity.JARGON, true); startSearch(null, false, appData, false); return true; }
返回 true 表示您已成功处理此回调事件并调用 startSearch()
来激活搜索对话框。用户提交查询后,系统会将查询与您添加的数据一起传递给可搜索 activity。您可以从 APP_DATA
Bundle
中提取额外数据以优化搜索,如以下示例所示:
Kotlin
val jargon: Boolean = intent.getBundleExtra(SearchManager.APP_DATA)?.getBoolean(JARGON) ?: false
Java
Bundle appData = getIntent().getBundleExtra(SearchManager.APP_DATA); if (appData != null) { boolean jargon = appData.getBoolean(SearchableActivity.JARGON); }
使用搜索微件
![一张图片,显示应用顶部栏中的搜索视图](https://developer.android.google.cn/static/images/ui/search-ui-topbar-33.png?authuser=5&hl=nl)
图 1. 将 SearchView
widget 作为应用栏中的操作视图。
搜索微件提供的功能与搜索对话框相同。它会在用户执行搜索时启动相应的 activity,并且可以提供搜索建议并执行语音搜索。如果您无法将搜索 widget 放在应用栏中,可以改为将搜索 widget 放在 activity 布局中的某个位置。
配置搜索微件
创建搜索配置和可搜索 activity 后,通过调用 setSearchableInfo()
并向其传递表示可搜索配置的 SearchableInfo
对象,为每个 SearchView
启用辅助搜索。
您可以通过对 SearchManager
调用 getSearchableInfo()
来获取对 SearchableInfo
的引用。
例如,如果您在应用栏中将 SearchView
用作操作视图,请在 onCreateOptionsMenu()
回调期间启用该 widget,如以下示例所示:
Kotlin
override fun onCreateOptionsMenu(menu: Menu): Boolean { // Inflate the options menu from XML. val inflater = menuInflater inflater.inflate(R.menu.options_menu, menu) // Get the SearchView and set the searchable configuration. val searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager (menu.findItem(R.id.menu_search).actionView as SearchView).apply { // Assumes current activity is the searchable activity. setSearchableInfo(searchManager.getSearchableInfo(componentName)) setIconifiedByDefault(false) // Don't iconify the widget. Expand it by default. } return true }
Java
@Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the options menu from XML. MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.options_menu, menu); // Get the SearchView and set the searchable configuration. SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE); SearchView searchView = (SearchView) menu.findItem(R.id.menu_search).getActionView(); // Assumes current activity is the searchable activity. searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName())); searchView.setIconifiedByDefault(false); // Don't iconify the widget. Expand it by default. return true; }
搜索 widget 现在已配置,系统会将搜索查询传递给可搜索 activity。您还可以为搜索 widget 启用搜索建议。
如需详细了解应用栏中的操作视图,请参阅使用操作视图和操作提供程序。
搜索微件的其他功能
SearchView
widget 提供您可能需要的几项其他功能:
- 提交按钮
- 默认情况下,没有用于提交搜索查询的按钮,因此用户必须按键盘上的 Return 键发起搜索。您可以通过调用
setSubmitButtonEnabled(true)
添加“提交”按钮。 - 搜索建议的查询优化
- 启用搜索建议后,您通常会希望用户选择一条建议,但也可能希望优化建议的搜索查询。您可以通过调用
setQueryRefinementEnabled(true)
在每条建议旁边添加一个按钮,用于将建议插入搜索框以供用户进行优化。 - 切换搜索框可见性的功能
- 默认情况下,搜索 widget 带有“图标化”图标,这意味着它仅由搜索图标(放大镜)表示。当用户点按该图标时,它会展开即可显示搜索框。如上例所示,您可以通过调用
setIconifiedByDefault(false)
默认显示搜索框。 您还可以通过调用setIconified()
来切换搜索微件的外观。
SearchView
类中还有几个其他 API 可用于自定义搜索 widget。不过,其中大多数 API 只有在您自行处理所有用户输入(而不是使用 Android 系统来提供搜索查询和显示搜索建议)时才会使用它们。
同时使用 widget 和对话框
如果您将搜索 widget 作为操作视图插入应用栏中,并通过设置 android:showAsAction="ifRoom"
使其在有空间的情况下显示在应用栏中,那么搜索 widget 可能不会显示为操作视图。实际上,菜单项可能会显示在溢出菜单中。例如,当应用在较小的屏幕上运行时,应用栏中可能没有足够的空间来显示搜索 widget 以及其他操作项或导航元素,因此菜单项会显示在溢出菜单中。放置在溢出菜单中时,菜单项的作用类似于普通菜单项,不会显示操作视图,也就是搜索 widget。
为了处理这种情况,当用户从溢出菜单中选择搜索 widget 时,附加搜索 widget 的菜单项必须激活搜索对话框。为此,请实现 onOptionsItemSelected()
以处理“搜索”菜单项,并通过调用 onSearchRequested()
打开搜索对话框。
如需详细了解应用栏中的项的工作原理以及如何处理这种情况,请参阅添加应用栏。
添加语音搜索
您可以通过向可搜索配置添加 android:voiceSearchMode
属性,向搜索对话框或搜索微件添加语音搜索功能。这样会添加一个用于启动语音提示的语音搜索按钮。当用户说完话后,系统会将转录的搜索查询发送到您的可搜索 activity。
具体可见以下示例:
<?xml version="1.0" encoding="utf-8"?> <searchable xmlns:android="http://schemas.android.com/apk/res/android" android:label="@string/search_label" android:hint="@string/search_hint" android:voiceSearchMode="showVoiceSearchButton|launchRecognizer" > </searchable>
必须设置 showVoiceSearchButton
值才能启用语音搜索。第二个值是 launchRecognizer
,用于指定语音搜索按钮必须启动一个识别器,用于将转录的文本返回给可搜索 activity。
您可以提供其他属性来指定语音搜索行为,例如预期的语言和返回的结果数上限。如需详细了解可用的属性,请参阅搜索配置参考。
添加搜索建议
在 Android 系统的协助下,搜索对话框和搜索 widget 都可以在用户输入内容时提供搜索建议。系统管理建议列表,并在用户选择建议时处理该事件。
您可以提供两种类型的搜索建议:
- 近期查询搜索建议
- 这些建议是用户以前在您的应用中用作搜索查询的字词。如需了解详情,请参阅添加自定义搜索建议。
- 自定义搜索建议
- 这些是您通过自己的数据源提供的搜索建议,可帮助用户立即选择他们正在搜索的正确拼写或内容。如需了解详情,请参阅添加自定义搜索建议。