创建搜索界面

当您准备向应用添加搜索功能时,Android 可以帮助您使用出现在活动窗口顶部的搜索对话框或您可以插入布局中的搜索小部件来实现用户界面。搜索对话框和小部件都可以将用户的搜索查询传递到应用中的特定活动。这样,用户可以从任何提供搜索对话框或小部件的活动中启动搜索,系统将启动相应的活动来执行搜索并显示结果。

搜索对话框和小部件还提供以下功能:

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

本文档展示了如何设置应用以提供由 Android 系统协助提供搜索查询的搜索界面,使用搜索对话框或搜索小部件。

相关资源

基础知识

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

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

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

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

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

  • 搜索配置
    一个 XML 文件,用于配置搜索对话框或小部件的一些设置。它包括语音搜索、搜索建议和搜索框提示文本等功能的设置。
  • 可搜索活动
    接收搜索查询、搜索您的数据并显示搜索结果的 Activity
  • 由以下任一提供的搜索界面:
    • 搜索对话框
      默认情况下,搜索对话框是隐藏的。当用户点击您的**搜索**按钮时,您调用 onSearchRequested() 时,它会出现在屏幕顶部。
    • SearchView 小部件
      使用搜索小部件,您可以将搜索框放置在活动的任何位置,包括应用栏中的操作视图。

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

创建可搜索配置

您首先需要一个名为搜索配置的 XML 文件。它配置搜索对话框或小部件的某些 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

当用户在搜索对话框或小部件中执行搜索时,系统会启动您的可搜索活动,并使用带有 ACTION_SEARCH 操作的 Intent 将搜索查询传递给它。您的可搜索活动从 Intent 的 QUERY 附加数据中检索查询,然后搜索您的数据并显示结果。

由于您可以将搜索对话框或小部件包含在应用中的任何其他活动中,因此系统必须知道哪个活动是您的可搜索活动,以便它能够正确传递搜索查询。因此,首先在 Android 清单文件中声明您的可搜索活动。

声明可搜索活动

如果您还没有,请创建一个执行搜索并显示结果的 Activity。您无需立即实现搜索功能——只需创建一个可以在清单中声明的活动即可。在清单的 <activity> 元素内,执行以下操作:

  1. <intent-filter> 元素中声明活动以接受 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 文件。

执行搜索

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

  1. 接收查询.
  2. 搜索您的数据.
  3. 显示结果

    .

接收查询

当用户从搜索对话框或小部件执行搜索时,系统会启动可搜索活动并向其发送一个 ACTION_SEARCH 意图。此意图在 QUERY 字符串额外信息中携带搜索查询。在活动启动时检查此意图并提取字符串。例如,以下是如何在可搜索活动启动时获取搜索查询的示例

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 意图中。在前面的示例中,检索查询并将其传递给本地 doMySearch() 方法,在该方法中执行实际的搜索操作。

搜索您的数据

存储和搜索数据的过程对于您的应用来说是独一无二的。您可以通过多种方式存储和搜索数据,本文档不会说明如何操作。请根据您的需求和数据格式考虑如何存储和搜索数据。以下是一些您可以应用的技巧

  • 如果您的数据存储在设备上的 SQLite 数据库中,则执行全文搜索(使用 FTS3 而不是 LIKE 查询)可以提供更强大的文本数据搜索,并且可以更快地生成结果。有关 FTS3 的信息,请参阅 sqlite.org;有关 Android 上 SQLite 的信息,请参阅 SQLiteDatabase 类。
  • 如果您的数据存储在线上,则用户的网络连接可能会影响感知到的搜索性能。您可能需要显示进度指示器,直到搜索返回结果。有关网络 API 的参考,请参阅 android.net;有关如何显示进度指示器的信息,请参阅 ProgressBar

显示结果

无论您的数据存储在哪里以及如何搜索,我们都建议您使用 Adapter 将搜索结果返回到可搜索活动。这样,您就可以在 RecyclerView 中显示所有搜索结果。如果您的数据来自 SQLite 数据库查询,则可以使用 CursorAdapter 将结果应用于 RecyclerView。如果您的数据采用其他格式,则可以创建 BaseAdapter 的扩展。

Adapter 将一组数据中的每个项目绑定到 View 对象。当 Adapter 应用于 RecyclerView 时,每个数据片段都作为单个视图插入到列表中。Adapter 只是一个接口,因此需要实现(例如,用于绑定来自 Cursor 的数据的 CursorAdapter)。如果现有的实现都不适合您的数据,则可以从 BaseAdapter 实现自己的实现。

使用搜索对话框

搜索对话框在屏幕顶部提供一个浮动搜索框,左侧显示应用图标。搜索对话框可以在用户键入时提供搜索建议。当用户执行搜索时,系统会将搜索查询发送到执行搜索的可搜索活动。

默认情况下,搜索对话框始终隐藏,直到用户激活它。您的应用可以通过调用 onSearchRequested() 来激活搜索对话框。但是,此方法在您为活动启用搜索对话框之前不起作用。

要启用搜索对话框以执行搜索,请向系统指示哪个可搜索活动必须接收来自搜索对话框的搜索查询。例如,在前面关于 创建可搜索活动 的部分中,创建了一个名为 SearchableActivity 的可搜索活动。如果您希望另一个活动(例如名为 OtherActivity 的活动)显示搜索对话框并将搜索传递给 SearchableActivity,则在清单中声明 SearchableActivityOtherActivity 中搜索对话框要使用的可搜索活动。

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

例如,以下是在清单中声明可搜索活动 SearchableActivity 和另一个活动 OtherActivity 的声明,其中 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> 元素来声明要用于搜索的可搜索活动,因此该活动启用了搜索对话框。尽管用户位于此活动中,但 onSearchRequested() 方法会激活搜索对话框。当用户执行搜索时,系统会启动 SearchableActivity 并向其传递 ACTION_SEARCH 意图。

如果您希望应用中的每个活动都提供搜索对话框,请将前面的 <meta-data> 元素作为 <application> 元素的子元素插入,而不是每个 <activity> 元素。这样,每个活动都继承该值,提供搜索对话框,并将搜索传递给同一个可搜索活动。如果您有多个可搜索活动,则可以通过在各个活动中放置不同的 <meta-data> 声明来覆盖默认的可搜索活动。

现在已为您的活动启用了搜索对话框,您的应用已准备好执行搜索。

调用搜索对话框

尽管某些设备提供专用搜索按钮,但按钮的行为可能因设备而异,并且许多设备根本不提供搜索按钮。因此,在使用搜索对话框时,您必须在 UI 中提供一个搜索按钮,该按钮通过调用 onSearchRequested() 来激活搜索对话框。

例如,在您的 选项菜单 或 UI 布局中添加一个调用 onSearchRequested() 的搜索按钮。

您还可以启用“键入以搜索”功能,该功能在用户开始在键盘上键入时激活搜索对话框。键击会被插入到搜索对话框中。您可以在活动中通过调用 setDefaultKeyMode(或 DEFAULT_KEYS_SEARCH_LOCAL)在活动 onCreate() 方法期间启用键入以搜索。

搜索对话框对活动生命周期产生的影响

搜索对话框是一个 Dialog,它浮动在屏幕顶部。它不会导致活动堆栈发生任何更改,因此当搜索对话框出现时,不会调用任何生命周期方法(例如 onPause())。您的活动会失去输入焦点,因为输入焦点会赋予搜索对话框。

如果您希望在激活搜索对话框时收到通知,请覆盖 onSearchRequested() 方法。当系统调用此方法时,表示您的活动将输入焦点丢失给搜索对话框,因此您可以执行适合该事件的任何操作,例如暂停游戏。除非您正在 传递搜索上下文数据(在本文档的另一部分中讨论),否则请通过调用超类实现来结束该方法

Kotlin

override fun onSearchRequested(): Boolean {
    pauseSomeStuff()
    return super.onSearchRequested()
}

Java

@Override
public boolean onSearchRequested() {
    pauseSomeStuff();
    return super.onSearchRequested();
}

如果用户通过点击“返回”按钮取消搜索,则搜索对话框会关闭,并且活动会重新获得输入焦点。您可以使用 setOnDismissListener()setOnCancelListener() 或两者来注册以在搜索对话框关闭时收到通知。您只需要注册 OnDismissListener,因为它在每次搜索对话框关闭时都会被调用。OnCancelListener 仅与用户显式退出搜索对话框的事件相关,因此在执行搜索时不会调用它。当执行搜索时,搜索对话框会自动消失。

如果当前活动不是可搜索活动,则当用户执行搜索时,会触发正常的活动生命周期事件——当前活动会接收 onPause(),如 活动简介 中所述。但是,如果当前活动是可搜索活动,则会发生以下两种情况之一

  • 默认情况下,可搜索活动会通过调用 onCreate() 来接收 ACTION_SEARCH 意图,并且活动的新实例会被置于活动堆栈的顶部。现在活动堆栈中有两个可搜索活动的实例,因此点击“返回”按钮会将您带回可搜索活动的上一个实例,而不是退出可搜索活动。
  • 如果您将 android:launchMode 设置为 "singleTop",则可搜索活动会通过调用 onNewIntent(Intent) 来接收 ACTION_SEARCH 意图,并将新的 ACTION_SEARCH 意图作为参数传递。例如,以下是如何处理此案例的示例,其中可搜索活动的启动模式为 "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);
        }
    }
    

    与关于 执行搜索 的部分中的示例代码相比,处理搜索意图的所有代码现在都在 handleIntent() 方法中,以便 onCreate()onNewIntent() 都可以执行它。

    当系统调用 onNewIntent(Intent) 时,活动不会重新启动,因此 getIntent() 方法会返回与 onCreate() 一起接收的相同意图。这就是为什么您必须在 onNewIntent(Intent) 内部调用 setIntent(Intent) 的原因:以便在将来调用 getIntent() 时更新活动保存的意图。

使用 "singleTop" 启动模式的第二种情况通常更可取,因为在搜索完成后,用户可能会执行其他搜索,并且您不希望应用创建可搜索活动的多个实例。我们建议您在应用清单中将可搜索活动的启动模式设置为 "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>

传递搜索上下文数据

在某些情况下,您可以针对每次搜索,在可搜索活动内部对搜索查询进行必要的细化。但是,如果您想根据用户执行搜索的活动来细化搜索条件,则可以在系统发送到可搜索活动的意图中提供其他数据。您可以将其他数据传递到APP_DATA Bundle中,该Bundle包含在ACTION_SEARCH意图中。

要将此类数据传递到可搜索活动,请覆盖用户可以从中执行搜索的活动的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()来激活搜索对话框。用户提交查询后,它将与您添加的数据一起传递到可搜索活动。您可以从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);
}

使用搜索小部件

An image showing a search view in the app top bar

图 1.SearchView小部件作为应用栏中的操作视图。

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

配置搜索小部件

在创建搜索配置可搜索活动之后,通过调用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;
}

现在搜索小部件已配置完毕,系统会将搜索查询传递到您的可搜索活动。您还可以为搜索小部件启用搜索建议

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

其他搜索小部件功能

SearchView小部件提供了一些您可能需要的其他功能

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

SearchView类中还有其他几个 API 可以让您自定义搜索小部件。但是,大多数 API 仅在您自己处理所有用户输入时使用,而不是使用 Android 系统来传递搜索查询和显示搜索建议。

同时使用小部件和对话框

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

要处理这种情况,您将搜索小部件附加到的菜单项必须在用户从溢出菜单中选择它时激活搜索对话框。为此,请实现onOptionsItemSelected()来处理“搜索”菜单项,并通过调用onSearchRequested()打开搜索对话框。

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

添加语音搜索

您可以通过将android:voiceSearchMode属性添加到可搜索配置中,为您的搜索对话框或小部件添加语音搜索功能。这会添加一个语音搜索按钮,该按钮会启动语音提示。当用户说完话后,转录的搜索查询将发送到您的可搜索活动。

如下例所示:

<?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指定语音搜索按钮必须启动一个识别器,该识别器将转录的文本返回到可搜索活动。

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

添加搜索建议

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

您可以提供两种搜索建议

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