创建搜索界面

尝试使用 Compose 方式
Jetpack Compose 是推荐用于 Android 的界面工具包。了解如何在 Compose 中添加搜索功能。

当您准备好向应用添加搜索功能时,Android 可通过显示在 activity 窗口顶部的搜索对话框或您可以在布局中插入的搜索 widget 来帮助您实现界面。搜索对话框和搜索微件均可将用户的搜索查询传递给应用中的特定 activity。这样,用户就可以从提供了搜索对话框或搜索微件的任何 activity 发起搜索,而系统会启动相应的 activity 来执行搜索并显示结果。

搜索对话框和搜索微件提供的其他功能包括:

  • 语音搜索
  • 基于近期查询的搜索建议
  • 与应用数据中的实际结果相符的搜索建议

本文档将为您介绍如何设置应用来提供搜索界面,以在 Android 系统的协助下使用搜索对话框或搜索微件传递搜索查询。

相关资源:

基础知识

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

  • 搜索对话框是一个由 Android 系统控制的界面组件。由用户激活后,搜索对话框将显示在 activity 的顶部。

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

  • 搜索 widgetSearchView 的一个实例,您可以将其放置在布局中的任意位置。默认情况下,搜索微件的行为类似于标准的 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> 元素内,执行以下操作:

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

执行搜索

在清单中声明可搜索 Activity 后,请按照以下步骤在可搜索 Activity 中执行搜索:

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

接收查询

当用户从搜索对话框或搜索微件中执行搜索时,系统会启动可搜索 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() 方法执行期间调用 setDefaultKeyModeDEFAULT_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 系统的协助下,搜索对话框和搜索微件均可在用户输入内容时提供搜索建议。系统管理建议的列表,并在用户选择建议时处理相应的事件。

您可以提供两种类型的搜索建议:

近期查询搜索建议
这些建议就是用户以前在您的应用中用作搜索查询的字词。如需了解详情,请参阅添加自定义搜索建议
自定义搜索建议
这些是您通过自己的数据源提供的搜索建议,可帮助用户立即选择他们正在搜索的正确拼写或内容。如需了解详情,请参阅添加自定义搜索建议