Android TV 使用 Android 搜索界面 从已安装的应用中检索内容数据,并向用户提供搜索结果。您应用的 内容数据可以包含在这些结果中,以便用户即时访问 。
您的应用必须为 Android TV 提供数据字段,以便 Android TV 可以据此生成搜索建议
当用户在搜索对话框中输入字符时返回的结果。为此,您的应用必须实现
Content Provider,
以及
searchable.xml
配置文件,用于描述
提供商和其他重要信息您还需要一个用于处理
在用户选择建议的搜索结果时触发的 intent。对于
请参阅添加
自定义搜索建议。本指南介绍了针对 Android TV 应用的要点。
在阅读本指南之前,请确保您熟悉 Search API 指南。 此外,请参阅添加搜索功能。
本指南中的示例代码来自 <ph type="x-smartling-placeholder"></ph> Leanback 示例应用 ,了解所有最新动态。
标识列
SearchManager
通过将预期数据字段表示为
本地数据库的列。无论数据格式如何,您都必须将数据字段映射到
这些列通常位于访问内容数据的类中。关于构建
一个将现有数据映射到所需字段的类,请参阅
<ph type="x-smartling-placeholder"></ph>
构建建议表格。
SearchManager
类包含多个 Android TV 列。其中一些
下表介绍了更重要的列。
值 | 说明 |
---|---|
SUGGEST_COLUMN_TEXT_1 |
内容的名称(必填) |
SUGGEST_COLUMN_TEXT_2 |
内容的文本说明 |
SUGGEST_COLUMN_RESULT_CARD_IMAGE |
内容的图片、海报或封面 |
SUGGEST_COLUMN_CONTENT_TYPE |
媒体的 MIME 类型 |
SUGGEST_COLUMN_VIDEO_WIDTH |
媒体的分辨率宽度 |
SUGGEST_COLUMN_VIDEO_HEIGHT |
媒体的分辨率高度 |
SUGGEST_COLUMN_PRODUCTION_YEAR |
内容的制作年份(必需) |
SUGGEST_COLUMN_DURATION |
媒体的时长(以毫秒为单位)(必填) |
搜索框架需要以下列:
当您内容的这些列的值与其他同一内容中的值匹配时 提供商时,系统会提供 深层链接, 查看相应内容,并提供指向其他提供商的应用的链接。本专精课程 详情屏幕中指向应用的深层链接部分。
应用的数据库类可能会按如下所示定义列:
Kotlin
class VideoDatabase { companion object { // The columns we'll include in the video database table val KEY_NAME = SearchManager.SUGGEST_COLUMN_TEXT_1 val KEY_DESCRIPTION = SearchManager.SUGGEST_COLUMN_TEXT_2 val KEY_ICON = SearchManager.SUGGEST_COLUMN_RESULT_CARD_IMAGE val KEY_DATA_TYPE = SearchManager.SUGGEST_COLUMN_CONTENT_TYPE val KEY_IS_LIVE = SearchManager.SUGGEST_COLUMN_IS_LIVE val KEY_VIDEO_WIDTH = SearchManager.SUGGEST_COLUMN_VIDEO_WIDTH val KEY_VIDEO_HEIGHT = SearchManager.SUGGEST_COLUMN_VIDEO_HEIGHT val KEY_AUDIO_CHANNEL_CONFIG = SearchManager.SUGGEST_COLUMN_AUDIO_CHANNEL_CONFIG val KEY_PURCHASE_PRICE = SearchManager.SUGGEST_COLUMN_PURCHASE_PRICE val KEY_RENTAL_PRICE = SearchManager.SUGGEST_COLUMN_RENTAL_PRICE val KEY_RATING_STYLE = SearchManager.SUGGEST_COLUMN_RATING_STYLE val KEY_RATING_SCORE = SearchManager.SUGGEST_COLUMN_RATING_SCORE val KEY_PRODUCTION_YEAR = SearchManager.SUGGEST_COLUMN_PRODUCTION_YEAR val KEY_COLUMN_DURATION = SearchManager.SUGGEST_COLUMN_DURATION val KEY_ACTION = SearchManager.SUGGEST_COLUMN_INTENT_ACTION ... } ... }
Java
public class VideoDatabase { // The columns we'll include in the video database table public static final String KEY_NAME = SearchManager.SUGGEST_COLUMN_TEXT_1; public static final String KEY_DESCRIPTION = SearchManager.SUGGEST_COLUMN_TEXT_2; public static final String KEY_ICON = SearchManager.SUGGEST_COLUMN_RESULT_CARD_IMAGE; public static final String KEY_DATA_TYPE = SearchManager.SUGGEST_COLUMN_CONTENT_TYPE; public static final String KEY_IS_LIVE = SearchManager.SUGGEST_COLUMN_IS_LIVE; public static final String KEY_VIDEO_WIDTH = SearchManager.SUGGEST_COLUMN_VIDEO_WIDTH; public static final String KEY_VIDEO_HEIGHT = SearchManager.SUGGEST_COLUMN_VIDEO_HEIGHT; public static final String KEY_AUDIO_CHANNEL_CONFIG = SearchManager.SUGGEST_COLUMN_AUDIO_CHANNEL_CONFIG; public static final String KEY_PURCHASE_PRICE = SearchManager.SUGGEST_COLUMN_PURCHASE_PRICE; public static final String KEY_RENTAL_PRICE = SearchManager.SUGGEST_COLUMN_RENTAL_PRICE; public static final String KEY_RATING_STYLE = SearchManager.SUGGEST_COLUMN_RATING_STYLE; public static final String KEY_RATING_SCORE = SearchManager.SUGGEST_COLUMN_RATING_SCORE; public static final String KEY_PRODUCTION_YEAR = SearchManager.SUGGEST_COLUMN_PRODUCTION_YEAR; public static final String KEY_COLUMN_DURATION = SearchManager.SUGGEST_COLUMN_DURATION; public static final String KEY_ACTION = SearchManager.SUGGEST_COLUMN_INTENT_ACTION; ...
构建从 SearchManager
列到数据字段的映射时,您需要
还必须指定 _ID
,以便为每一行分配一个唯一 ID。
Kotlin
companion object { .... private fun buildColumnMap(): MapS<tring, String> { return mapOf( KEY_NAME to KEY_NAME, KEY_DESCRIPTION to KEY_DESCRIPTION, KEY_ICON to KEY_ICON, KEY_DATA_TYPE to KEY_DATA_TYPE, KEY_IS_LIVE to KEY_IS_LIVE, KEY_VIDEO_WIDTH to KEY_VIDEO_WIDTH, KEY_VIDEO_HEIGHT to KEY_VIDEO_HEIGHT, KEY_AUDIO_CHANNEL_CONFIG to KEY_AUDIO_CHANNEL_CONFIG, KEY_PURCHASE_PRICE to KEY_PURCHASE_PRICE, KEY_RENTAL_PRICE to KEY_RENTAL_PRICE, KEY_RATING_STYLE to KEY_RATING_STYLE, KEY_RATING_SCORE to KEY_RATING_SCORE, KEY_PRODUCTION_YEAR to KEY_PRODUCTION_YEAR, KEY_COLUMN_DURATION to KEY_COLUMN_DURATION, KEY_ACTION to KEY_ACTION, BaseColumns._ID to ("rowid AS " + BaseColumns._ID), SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID to ("rowid AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID), SearchManager.SUGGEST_COLUMN_SHORTCUT_ID to ("rowid AS " + SearchManager.SUGGEST_COLUMN_SHORTCUT_ID) ) } }
Java
... private static HashMap<String, String> buildColumnMap() { HashMap<String, String> map = new HashMap<String, String>(); map.put(KEY_NAME, KEY_NAME); map.put(KEY_DESCRIPTION, KEY_DESCRIPTION); map.put(KEY_ICON, KEY_ICON); map.put(KEY_DATA_TYPE, KEY_DATA_TYPE); map.put(KEY_IS_LIVE, KEY_IS_LIVE); map.put(KEY_VIDEO_WIDTH, KEY_VIDEO_WIDTH); map.put(KEY_VIDEO_HEIGHT, KEY_VIDEO_HEIGHT); map.put(KEY_AUDIO_CHANNEL_CONFIG, KEY_AUDIO_CHANNEL_CONFIG); map.put(KEY_PURCHASE_PRICE, KEY_PURCHASE_PRICE); map.put(KEY_RENTAL_PRICE, KEY_RENTAL_PRICE); map.put(KEY_RATING_STYLE, KEY_RATING_STYLE); map.put(KEY_RATING_SCORE, KEY_RATING_SCORE); map.put(KEY_PRODUCTION_YEAR, KEY_PRODUCTION_YEAR); map.put(KEY_COLUMN_DURATION, KEY_COLUMN_DURATION); map.put(KEY_ACTION, KEY_ACTION); map.put(BaseColumns._ID, "rowid AS " + BaseColumns._ID); map.put(SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID, "rowid AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID); map.put(SearchManager.SUGGEST_COLUMN_SHORTCUT_ID, "rowid AS " + SearchManager.SUGGEST_COLUMN_SHORTCUT_ID); return map; } ...
在前面的示例中,请注意到 SUGGEST_COLUMN_INTENT_DATA_ID
的映射。
字段。这是 URI 中指向此
row — URI 的最后一部分,用于描述内容的存储位置。URI 的第一部分
表中的所有行都是通用的,在
searchable.xml
文件作为
android:searchSuggestIntentData
属性,如
处理搜索建议部分。
如果 URI 中每一行的 URI 第一部分都不同,
表中,将该值与 SUGGEST_COLUMN_INTENT_DATA
字段映射。
当用户选择此内容时,触发的 intent 会提供来自
SUGGEST_COLUMN_INTENT_DATA_ID
的组合
以及 android:searchSuggestIntentData
属性或
SUGGEST_COLUMN_INTENT_DATA
字段值。
提供搜索建议数据
实现 Content Provider
可将搜索字词建议返回 Android TV 搜索对话框。系统查询您的内容
提供程序,通过每次调用 query()
方法来获取建议
所输入的字母。在query()
的实现代码中,您的内容
provider 搜索您的建议数据,并返回指向Cursor
指定用于提供建议的行
Kotlin
fun query(uri: Uri, projection: Array<String>, selection: String, selectionArgs: Array<String>, sortOrder: String): Cursor { // Use the UriMatcher to see what kind of query we have and format the db query accordingly when (URI_MATCHER.match(uri)) { SEARCH_SUGGEST -> { Log.d(TAG, "search suggest: ${selectionArgs[0]} URI: $uri") if (selectionArgs == null) { throw IllegalArgumentException( "selectionArgs must be provided for the Uri: $uri") } return getSuggestions(selectionArgs[0]) } else -> throw IllegalArgumentException("Unknown Uri: $uri") } } private fun getSuggestions(query: String): Cursor { val columns = arrayOf<String>( BaseColumns._ID, VideoDatabase.KEY_NAME, VideoDatabase.KEY_DESCRIPTION, VideoDatabase.KEY_ICON, VideoDatabase.KEY_DATA_TYPE, VideoDatabase.KEY_IS_LIVE, VideoDatabase.KEY_VIDEO_WIDTH, VideoDatabase.KEY_VIDEO_HEIGHT, VideoDatabase.KEY_AUDIO_CHANNEL_CONFIG, VideoDatabase.KEY_PURCHASE_PRICE, VideoDatabase.KEY_RENTAL_PRICE, VideoDatabase.KEY_RATING_STYLE, VideoDatabase.KEY_RATING_SCORE, VideoDatabase.KEY_PRODUCTION_YEAR, VideoDatabase.KEY_COLUMN_DURATION, VideoDatabase.KEY_ACTION, SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID ) return videoDatabase.getWordMatch(query.toLowerCase(), columns) }
Java
@Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { // Use the UriMatcher to see what kind of query we have and format the db query accordingly switch (URI_MATCHER.match(uri)) { case SEARCH_SUGGEST: Log.d(TAG, "search suggest: " + selectionArgs[0] + " URI: " + uri); if (selectionArgs == null) { throw new IllegalArgumentException( "selectionArgs must be provided for the Uri: " + uri); } return getSuggestions(selectionArgs[0]); default: throw new IllegalArgumentException("Unknown Uri: " + uri); } } private Cursor getSuggestions(String query) { query = query.toLowerCase(); String[] columns = new String[]{ BaseColumns._ID, VideoDatabase.KEY_NAME, VideoDatabase.KEY_DESCRIPTION, VideoDatabase.KEY_ICON, VideoDatabase.KEY_DATA_TYPE, VideoDatabase.KEY_IS_LIVE, VideoDatabase.KEY_VIDEO_WIDTH, VideoDatabase.KEY_VIDEO_HEIGHT, VideoDatabase.KEY_AUDIO_CHANNEL_CONFIG, VideoDatabase.KEY_PURCHASE_PRICE, VideoDatabase.KEY_RENTAL_PRICE, VideoDatabase.KEY_RATING_STYLE, VideoDatabase.KEY_RATING_SCORE, VideoDatabase.KEY_PRODUCTION_YEAR, VideoDatabase.KEY_COLUMN_DURATION, VideoDatabase.KEY_ACTION, SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID }; return videoDatabase.getWordMatch(query, columns); } ...
在您的清单文件中,内容提供程序会受到特殊对待。不像
标记为 activity,则被描述为
<provider>
。通过
provider 包含 android:authorities
属性,告知系统
内容提供程序的命名空间内此外,您还必须将其 android:exported
属性设置为
"true"
,以便 Android 全局搜索可以使用从中返回的结果。
<provider android:name="com.example.android.tvleanback.VideoContentProvider" android:authorities="com.example.android.tvleanback" android:exported="true" />
处理搜索建议
应用程序必须包含
res/xml/searchable.xml
文件,用于配置搜索建议设置。
在 res/xml/searchable.xml
文件中,添加
<ph type="x-smartling-placeholder"></ph>
android:searchSuggestAuthority
属性告知系统
content provider。该值必须与您在
android:authorities
<provider>
的属性
元素。AndroidManifest.xml
此外,还要包含标签, 这是应用的名称系统搜索设置在进行枚举时使用该标签 搜索应用。
searchable.xml
文件
必须包含
android:searchSuggestIntentAction
,值为 "android.intent.action.VIEW"
定义用于提供自定义建议的 intent 操作。这不同于
操作以提供搜索字词,如下一部分所述。
如需了解声明 intent 操作以提供建议的其他方式,
请参阅声明
intent 操作。
除了 intent 操作之外,您的应用还必须提供 intent 数据,您可以使用
<ph type="x-smartling-placeholder"></ph>
android:searchSuggestIntentData
属性。这是 URI 的第一部分
内容,用于说明该内容的映射表中所有行共有的 URI 部分
内容。URI 中每行的唯一部分是通过 SUGGEST_COLUMN_INTENT_DATA_ID
字段建立的,
,具体说明请参阅标识列部分。
如需了解声明 intent 数据以提供建议的其他方式,请参阅
声明
intent 数据。
android:searchSuggestSelection=" ?"
属性指定传递的值
作为 query()
的 selection
参数
方法。问号 (?
) 值会替换为查询文本。
最后,您还必须添加
值为 "true"
的 android:includeInGlobalSearch
属性。下面是一个示例
searchable.xml
文件:
<searchable xmlns:android="http://schemas.android.com/apk/res/android" android:label="@string/search_label" android:hint="@string/search_hint" android:searchSettingsDescription="@string/settings_description" android:searchSuggestAuthority="com.example.android.tvleanback" android:searchSuggestIntentAction="android.intent.action.VIEW" android:searchSuggestIntentData="content://com.example.android.tvleanback/video_database_leanback" android:searchSuggestSelection=" ?" android:searchSuggestThreshold="1" android:includeInGlobalSearch="true"> </searchable>
处理搜索字词
只要搜索对话框中包含的字词与应用某个列中的值匹配,系统就会立即触发
标识列部分中所述,系统会触发
ACTION_SEARCH
intent。
应用中负责处理该操作的 activity
intent 会在代码库中搜索值中包含给定字词的列,并返回列表
与这些列相关联。在 AndroidManifest.xml
文件中,指定
负责处理 ACTION_SEARCH
的 activity
intent,如以下示例所示:
... <activity android:name="com.example.android.tvleanback.DetailsActivity" android:exported="true"> <!-- Receives the search request. --> <intent-filter> <action android:name="android.intent.action.SEARCH" /> <!-- No category needed, because the Intent will specify this class component --> </intent-filter> <!-- Points to searchable meta data. --> <meta-data android:name="android.app.searchable" android:resource="@xml/searchable" /> </activity> ... <!-- Provides search suggestions for keywords against video meta data. --> <provider android:name="com.example.android.tvleanback.VideoContentProvider" android:authorities="com.example.android.tvleanback" android:exported="true" /> ...
该 activity 还必须描述可搜索配置,并引用
searchable.xml
文件。
如需使用全局搜索对话框,请执行以下操作:
清单必须说明哪个 activity 应接收搜索查询。清单还必须
描述<provider>
元素,这与 searchable.xml
文件中的说明完全一样。
详情屏幕中指向应用的深层链接
如果您已按照处理搜索
建议部分,并映射 SUGGEST_COLUMN_TEXT_1
,
SUGGEST_COLUMN_PRODUCTION_YEAR
和
SUGGEST_COLUMN_DURATION
字段,如
标识列部分,
深层链接指向您内容的观看操作,会显示在
当用户选择搜索结果时:
![详情屏幕中的深层链接](https://developer.android.google.cn/static/images/tv/deep-link.png?authuser=0000&hl=bg)
当用户选择您的应用链接时,该链接由
详情屏幕,系统会启动处理 ACTION_VIEW
的 activity
设为
值为 "android.intent.action.VIEW"
的 android:searchSuggestIntentAction
searchable.xml
文件。
您还可以设置自定义 intent 来启动您的 activity。如示例所示,
<ph type="x-smartling-placeholder"></ph>
Leanback 示例应用
,了解所有最新动态。请注意,示例应用会启动自己的 LeanbackDetailsFragment
,以
显示所选媒体的详细信息;启动用于播放媒体内容的 activity
以便让用户再点击一两次
搜索行为
在 Android TV 中,您可以从主屏幕和应用内部使用搜索功能。搜索结果 会有所不同
在主屏幕上搜索
当用户从主屏幕进行搜索时,第一条结果会显示在实体卡片中。如果有 的应用,卡片底部会显示每个应用的链接:
![TV 搜索结果播放](https://developer.android.google.cn/static/images/tv/tv-search-playback.png?authuser=0000&hl=bg)
您无法以编程方式将应用放入实体卡中。以 则应用的搜索结果必须与 搜索内容。
卡片下方可能会显示更多搜索结果。要想看到它们,用户必须按下 然后向下滚动。每个应用的结果会显示在单独的一行中。您无法控制 行排序。支持的应用 观看操作最先列出。
![电视搜索结果](https://developer.android.google.cn/static/images/tv/tv-search-results.gif?authuser=0000&hl=bg)
在应用中搜索
用户也可以通过遥控器或 游戏手柄控制器搜索结果会显示在应用内容顶部的一行中。 您的应用会使用自己的全局搜索提供程序生成搜索结果。
![TV 应用内搜索结果](https://developer.android.google.cn/static/images/tv/tv-search-in-app.png?authuser=0000&hl=bg)
了解详情
要详细了解如何搜索电视应用,请阅读 将 Android 搜索功能集成到您的应用中 添加搜索功能。
如需详细了解如何使用 SearchFragment
自定义应用内搜索体验,请阅读下文
在 TV 应用内搜索。