创建搜索界面

准备好向应用添加搜索功能时,Android 可帮助你实现用户界面,可选择显示在 activity 窗口顶部的搜索对话框,或者可插入布局中的搜索 widget。搜索对话框和 widget 都可以将用户的搜索查询传递到应用中的特定 activity。这样一来,用户就可以从任何提供搜索对话框或 widget 的 activity 启动搜索,系统会启动相应的 activity 来执行搜索并呈现结果。

搜索对话框和 widget 还提供以下其他功能:

  • 语音搜索
  • 基于最近查询的搜索建议
  • 与应用数据中实际结果匹配的搜索建议

本文档演示如何使用 Android 系统的帮助,通过搜索对话框或搜索 widget 配置应用,以提供搜索查询辅助的搜索界面。

相关资源

基础知识

开始之前,请决定是使用搜索对话框还是搜索 widget 来实现搜索界面。它们提供了相同的搜索功能,但方式略有不同:

  • 搜索对话框是 Android 系统控制的 UI 组件。当用户激活时,搜索对话框会出现在 activity 顶部。

    Android 系统控制搜索对话框中的所有事件。当用户提交查询时,系统会将查询传递到你指定的处理搜索的 activity。对话框还可以在用户输入时提供搜索建议。

  • 搜索 widgetSearchView 的一个实例,你可以将其放置在布局中的任意位置。默认情况下,搜索 widget 的行为类似于标准的 EditText widget,并且不执行任何操作,但你可以对其进行配置,以便 Android 系统处理所有输入事件,将查询传递到相应的 activity,并提供搜索建议,就像搜索对话框一样。

当用户从搜索对话框或搜索 widget 执行搜索时,系统会创建一个 Intent 并将用户查询存储在其中。然后,系统会启动你声明用于处理搜索的 activity(即“可搜索 activity”)并将 intent 传递给它。要为这种辅助搜索设置应用,你需要以下内容:

  • 搜索配置
    一个 XML 文件,用于配置搜索对话框或 widget 的某些 UI 设置。它包含语音搜索、搜索建议和搜索框的提示文本等功能的设置。
  • 可搜索 activity
    根据查询字符串执行搜索并显示搜索结果的 Activity
  • 搜索界面,由以下任一方式提供:
    • 搜索对话框
      默认情况下,搜索对话框处于隐藏状态。当用户点击搜索按钮时,如果你调用 onSearchRequested() 激活它,它会出现在屏幕顶部。
    • 一个 SearchView widget
      使用搜索 widget 可以将搜索框放置在 activity 的任意位置,包括作为应用栏中的操作视图。

本文档的其余部分将向你展示如何创建搜索配置和可搜索 activity,以及如何使用搜索对话框或搜索 widget 实现搜索界面。

创建可搜索配置

你需要做的第一件事是创建一个名为可搜索配置的 XML 文件。它配置了搜索对话框或 widget 的某些 UI 方面,并定义了建议和语音搜索等功能的行为方式。此文件通常命名为 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

当用户在搜索对话框或 widget 中执行搜索时,系统会启动你的可搜索 activity,并通过带有 ACTION_SEARCH 操作的 Intent 将搜索查询传递给它。你的可搜索 activity 从 intent 的 QUERY extra 中检索查询,然后搜索你的数据并呈现结果。

因为你可以在应用中的任何其他 activity 中包含搜索对话框或 widget,所以系统必须知道哪个 activity 是你的可搜索 activity,以便它可以正确地传递搜索查询。因此,首先在 Android manifest 文件中声明你的可搜索 activity。

声明可搜索 activity

如果你还没有,创建一个执行搜索并呈现结果的 Activity。你不需要立即实现搜索功能,只需创建一个可以在 manifest 中声明的 activity。在 manifest 的 <activity> 元素内部,执行以下操作:

  1. <intent-filter> 元素中声明 activity 接受 ACTION_SEARCH intent。
  2. <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:name 属性,其值为 "android.app.searchable",以及 android:resource 属性,其值为对可搜索配置文件的引用。在前面的示例中,它引用了 res/xml/searchable.xml 文件。

执行搜索

在 manifest 中声明可搜索 activity 后,按照以下步骤在你的可搜索 activity 中执行搜索:

  1. 接收查询.
  2. 搜索你的数据.
  3. 呈现结果.

接收查询

当用户从搜索对话框或 widget 执行搜索时,系统会启动你的可搜索 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,则在 manifest 中声明 SearchableActivityOtherActivity 搜索对话框中要使用的可搜索 activity。

要为 activity 的搜索对话框声明可搜索 activity,请在各自 activity 的 <activity> 元素内部添加一个 <meta-data> 元素。<meta-data> 元素必须包含 android:value 属性,该属性指定可搜索 activity 的类名,以及 android:name 属性,其值为 "android.app.default_searchable"

例如,以下是可搜索 activity (SearchableActivity) 和另一个使用 SearchableActivity 执行从其搜索对话框执行的搜索的 activity (OtherActivity) 的声明:

<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 启用,你的应用已准备好执行搜索。

调用搜索对话框

虽然某些设备提供专用的搜索按钮,但按钮的行为在不同设备之间可能有所差异,并且许多设备根本不提供搜索按钮。因此,在使用搜索对话框时,你必须在 UI 中提供一个搜索按钮,通过调用 onSearchRequested() 来激活搜索对话框。

例如,在选项菜单或 UI 布局中添加一个调用 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() 方法返回与 onCreate() 接收到的相同的 intent。这就是为什么你必须在 onNewIntent(Intent) 内部调用 setIntent(Intent) 的原因:以便 activity 保存的 intent 在你将来调用 getIntent() 时得到更新。

通常更倾向于第二种情况,即使用 "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);
}

使用搜索 widget

An image showing a search view in the app top bar

图 1. SearchView widget 作为应用栏中的操作视图。

搜索 widget 提供的功能与搜索对话框相同。当用户执行搜索时,它会启动相应的 activity,并且可以提供搜索建议和执行语音搜索。如果无法将搜索 widget 放置在应用栏中,你可以将搜索 widget 放置在 activity 布局中的其他位置。

配置搜索 widget

创建搜索配置可搜索 activity后,通过调用 setSearchableInfo() 并向其传递表示可搜索配置的 SearchableInfo 对象来启用每个 SearchView 的辅助搜索。

你可以通过调用 getSearchableInfo()SearchManager 上获取对 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 启用搜索建议

有关应用栏中操作视图的更多信息,请参阅使用操作视图和操作提供程序

其他搜索 widget 功能

SearchView widget 提供了一些你可能需要的其他功能:

提交按钮
默认情况下,没有提交搜索查询的按钮,因此用户必须按键盘上的 回车 键才能发起搜索。你可以通过调用 setSubmitButtonEnabled(true) 添加“提交”按钮。
搜索建议的查询细化
启用搜索建议后,你通常希望用户选择建议,但他们也可能想细化建议的搜索查询。你可以通过调用 setQueryRefinementEnabled(true) 在每个建议旁边添加一个按钮,该按钮会将建议插入到搜索框中供用户细化。
切换搜索框可见性的功能
默认情况下,搜索 widget 是“图标化”的,这意味着它仅由搜索图标(放大镜)表示。当用户点击图标时,它会展开以显示搜索框。如前例所示,你可以通过调用 setIconifiedByDefault(false) 来默认显示搜索框。你也可以通过调用 setIconified() 来切换搜索 widget 的外观。

SearchView 类中还有其他几个 API,允许你自定义搜索 widget。但是,大多数 API 仅在你自行处理所有用户输入,而不是使用 Android 系统传递搜索查询和显示搜索建议时使用。

同时使用 widget 和对话框

如果你将搜索 widget 作为操作视图插入到应用栏中,并启用它以便在空间允许的情况下出现在应用栏中(通过设置 android:showAsAction="ifRoom"),则搜索 widget 可能不会显示为操作视图。相反,菜单项可能会出现在溢出菜单中。例如,当你的应用在较小的屏幕上运行时,应用栏中可能没有足够的空间与其他操作项或导航元素一起显示搜索 widget,因此菜单项会改为显示在溢出菜单中。当放置在溢出菜单中时,该项的行为类似于普通的菜单项,并且不显示操作视图(即搜索 widget)。

为了处理这种情况,你在其中附加搜索 widget 的菜单项必须在用户从溢出菜单中选择它时激活搜索对话框。为了实现这一点,实现 onOptionsItemSelected() 来处理“搜索”菜单项,并通过调用 onSearchRequested() 打开搜索对话框。

有关应用栏中项目的工作原理以及如何处理这种情况的更多信息,请参阅添加应用栏

添加语音搜索

你可以通过向可搜索配置中添加 android:voiceSearchMode 属性,为搜索对话框或 widget 添加语音搜索功能。这将添加一个语音搜索按钮,用于启动语音提示。用户说完后,转录的搜索查询将发送到你的可搜索 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。

你可以提供额外的属性来指定语音搜索的行为,例如预期的语言和返回结果的最大数量。有关可用属性的更多信息,请参阅搜索配置参考文档。

添加搜索建议

搜索对话框和搜索 widget 都可以借助 Android 系统的帮助,在用户输入时提供搜索建议。系统管理建议列表,并处理用户选择建议时的事件。

你可以提供两种搜索建议:

最近查询搜索建议
这些建议是用户之前在你的应用中用作搜索查询的词语。有关更多信息,请参阅添加自定义搜索建议
自定义搜索建议
这些是你从自己的数据源提供的搜索建议,旨在帮助用户立即选择他们正在搜索的正确拼写或项目。有关更多信息,请参阅添加自定义搜索建议