当您准备好向应用添加搜索功能时,Android 可通过显示在 activity 窗口顶部的搜索对话框或您可以在布局中插入的搜索 widget 来帮助您实现界面。搜索对话框和搜索微件均可将用户的搜索查询传递给应用中的特定 activity。这样,用户就可以从提供了搜索对话框或搜索微件的任何 activity 发起搜索,而系统会启动相应的 activity 来执行搜索并显示结果。
搜索对话框和搜索微件提供的其他功能包括:
- 语音搜索
- 基于近期查询的搜索建议
- 与应用数据中的实际结果相符的搜索建议
本文档将为您介绍如何设置应用来提供搜索界面,以在 Android 系统的协助下使用搜索对话框或搜索微件传递搜索查询。
相关资源:
基础知识
在开始之前,请决定是使用搜索对话框还是搜索微件来实现搜索界面。它们提供相同的搜索功能,但方式略有不同:
- 搜索对话框是一个由 Android 系统控制的界面组件。由用户激活后,搜索对话框将显示在 activity 的顶部。
Android 系统控制搜索对话框中的所有事件。当用户提交查询时,系统会将该查询传递给您指定用来处理搜索的 activity。该对话框还可以在用户输入内容的同时提供搜索建议。
- 搜索 widget 是
SearchView
的一个实例,您可以将其放置在布局中的任意位置。默认情况下,搜索微件的行为类似于标准的EditText
微件,它没有什么特殊的作用,但您可以对其进行配置,以使 Android 系统处理所有输入事件,将查询传递给适当的 activity,并提供搜索建议(就像搜索对话框一样)。
当用户从搜索对话框或搜索微件中执行搜索时,系统会创建一个 Intent
并将用户查询存储在其中。然后,系统会启动您声明用来处理搜索的 activity(即“可搜索 activity”),并向其传递该 intent。要设置您的应用来执行这种辅助搜索,您需要以下几项:
- 搜索配置
- 一个 XML 文件,用于为搜索对话框或搜索微件配置一些设置。 其中包含语音搜索、搜索建议和搜索框的提示文本等功能的设置。
- 可搜索 Activity
- 接收搜索查询、搜索数据并显示搜索结果的
Activity
。- 搜索界面,由以下两项中的任意一项提供:
- 搜索对话框
- 默认情况下,搜索对话框处于隐藏状态。当用户点按搜索按钮时,如果您调用
onSearchRequested()
,此对话框会显示在屏幕顶部。SearchView
widget- 使用搜索微件,您可以将搜索框放在 activity 中的任意位置,包括作为应用栏中的操作视图。
本文档的其余部分将为您介绍如何创建搜索配置和可搜索 Activity,以及如何使用搜索对话框或搜索微件来实现搜索界面。
创建可搜索配置
您首先需要的是一个名为搜索配置的 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); }
使用搜索微件

图 1. SearchView
微件作为应用栏中的操作视图。
搜索微件提供的功能与搜索对话框相同。它可以在用户执行搜索时启动适当的 activity,还可以提供搜索建议并执行语音搜索。如果您无法将搜索微件放在应用栏中,可以改为将搜索微件放在 activity 布局中的某个位置。
配置搜索微件
创建搜索配置和可搜索 activity 后,通过调用 setSearchableInfo()
并向其传递表示可搜索配置的 SearchableInfo
对象,为每个 SearchView
启用辅助搜索。
您可以通过在 SearchManager
上调用 getSearchableInfo()
来获取对 SearchableInfo
的引用。
例如,如果您将 SearchView
用作应用栏中的操作视图,则应在 onCreateOptionsMenu()
回调期间启用微件,如以下示例所示:
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; }
搜索微件现已配置完毕,系统会将搜索查询传递给可搜索 Activity。您还可以为搜索微件启用搜索建议。
如需详细了解应用栏中的操作视图,请参阅使用操作视图和操作提供程序。
搜索微件的其他功能
SearchView
微件还提供了您可能需要的一些其他功能:
- 提交按钮
- 默认情况下,没有用于提交搜索查询的按钮,因此用户必须按键盘上的回车键来发起搜索操作。您可以通过调用
setSubmitButtonEnabled(true)
来添加一个“提交”按钮。 - 搜索建议的查询优化
- 启用搜索建议后,您通常希望用户选择一条建议,但他们也可能想要优化建议的搜索查询。您可以通过调用
setQueryRefinementEnabled(true)
在每条建议旁边添加一个按钮,用于将建议插入搜索框以供用户进行优化。 - 切换搜索框可见性的功能
- 默认情况下,搜索 widget 是“图标化”的 widget,这意味着它只由搜索图标(放大镜)来表示。当用户点按该图标时,它会展开即可显示搜索框。如上例所示,您可以通过调用
setIconifiedByDefault(false)
在默认情况下显示搜索框。您还可以通过调用setIconified()
来切换搜索微件的外观。
SearchView
类中还有其他一些 API 可用于自定义搜索微件。不过,其中大多数 API 只有在您自行处理所有用户输入而不是使用 Android 系统传递搜索查询并显示搜索建议时才使用。
同时使用微件和对话框
如果您将搜索微件作为操作视图插入应用栏中,并通过设置 android:showAsAction="ifRoom"
使其在应用栏中有空间时显示在应用栏中,则搜索微件可能不会显示为操作视图。而是可能会在溢出菜单中显示一个菜单项。例如,当您的应用在较小的屏幕上运行时,应用栏中可能没有足够的空间,无法将搜索 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 系统的协助下,搜索对话框和搜索微件均可在用户输入内容时提供搜索建议。系统管理建议的列表,并在用户选择建议时处理相应的事件。
您可以提供两种类型的搜索建议:
- 近期查询搜索建议
- 这些建议就是用户以前在您的应用中用作搜索查询的字词。如需了解详情,请参阅添加自定义搜索建议。
- 自定义搜索建议
- 这些是您通过自己的数据源提供的搜索建议,可帮助用户立即选择他们正在搜索的正确拼写或内容。如需了解详情,请参阅添加自定义搜索建议。