ב-Android TV נעשה שימוש בממשק החיפוש של Android כדי לאחזר נתוני תוכן מאפליקציות מותקנות ולהציג תוצאות חיפוש למשתמש. של האפליקציה שלך לכלול נתוני תוכן בתוצאות האלו כדי לתת למשתמש גישה מיידית לתוכן באפליקציה שלך.
האפליקציה צריכה לספק ל-Android TV את שדות הנתונים שמהם מערכת Android TV יכולה ליצור הצעות לחיפוש
תוצאות כשהמשתמש מזין תווים בתיבת הדו-שיח של החיפוש. כדי לעשות את זה, האפליקציה צריכה להטמיע
ספק תוכן שמציג מודעות
להעלות את ההצעות, וגם
קובץ תצורה של searchable.xml
שמתאר את התוכן
ספק מידע חיוני אחר עבור Android TV. נדרשת גם פעילות שמטפלת
Intent שמופעל כשהמשתמש בוחר בתוצאת חיפוש מוצעת. עבור
פרטים נוספים, ראו הוספה
הצעות לחיפוש בהתאמה אישית. המדריך הזה עוסק בנקודות העיקריות שספציפיות לאפליקציות ל-Android TV.
לפני קריאת המדריך הזה, ודאו שאתם מכירים את המושגים שמוסברים מדריך Search API. מומלץ גם לקרוא את המאמר הוספת פונקציונליות חיפוש.
הקוד לדוגמה במדריך הזה מגיע אפליקציה לדוגמה של Leanback הקצר הזה. התשובות שלך יעזרו לנו להשתפר.
זיהוי עמודות
השדה SearchManager
מתאר את שדות הנתונים שהוא מצפה לקבל על ידי ייצוג שלהם
במסד נתונים מקומי. ללא קשר לפורמט של הנתונים, צריך למפות את שדות הנתונים
את העמודות האלה, בדרך כלל במחלקה שניגשת לנתוני התוכן שלכם. מידע על מבנים
כיתה שממפה את הנתונים הקיימים שלכם לשדות החובה.
יצירה של טבלת הצעות
המחלקה 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 |
משך הזמן באלפיות השנייה של מדיה (חובה) |
מסגרת החיפוש מחייבת את העמודות הבאות:
כשהערכים של התוכן בעמודות האלה תואמים לערכים של אותו תוכן בפורמטים אחרים שאותרו על ידי שרתי Google, המערכת מספקת קישור עומק לאפליקציה בפרטים לצפייה בתוכן, יחד עם קישורים לאפליקציות של ספקים אחרים. הנושא הזה דן בנושא בקטע קישור עומק לאפליקציה במסך הפרטים.
סוג מסד הנתונים של האפליקציה עשוי להגדיר את העמודות כך:
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
כדי לתת לכל שורה מזהה ייחודי.
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 שמפנה לתוכן הייחודי לנתונים
השורה – החלק האחרון ב-URI, שמתאר איפה התוכן מאוחסן. החלק הראשון ב-URI,
כשהערך משותף לכל השורות בטבלה, הפונקציה
searchable.xml
בתור
android:searchSuggestIntentData
, כמו שמתואר
הקטע טיפול בהצעות לחיפוש.
אם החלק הראשון של ה-URI שונה בכל שורה
טבלה, למפות את הערך הזה באמצעות השדה SUGGEST_COLUMN_INTENT_DATA
.
כשהמשתמש בוחר בתוכן הזה, הכוונה שמופעלת מספקת את נתוני הכוונה של
שילוב של SUGGEST_COLUMN_INTENT_DATA_ID
ואת המאפיין android:searchSuggestIntentData
או
ערך השדה SUGGEST_COLUMN_INTENT_DATA
.
לספק נתונים של הצעות לחיפוש
להטמיע ספק תוכן
כדי להחזיר הצעות למונחי חיפוש לתיבת הדו-שיח של החיפוש ב-Android TV. המערכת שולחת שאילתה על התוכן שלכם
ספק כדי לקבל הצעות. לשם כך, צריך להפעיל את השיטה query()
בכל פעם
מקלידים אות. בהטמעה של query()
, התוכן שלך
הספק מחפש את נתוני ההצעות שלכם ומחזיר 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); } ...
בקובץ המניפסט, ספק התוכן מקבל טיפול מיוחד. במקום
מתויגת כפעילות, היא מתוארת
<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
, כוללים את
ה
android:searchSuggestAuthority
כדי לציין למערכת את מרחב השמות של
ספק תוכן. הערך הזה חייב להתאים לערך המחרוזת שציינת
android:authorities
של <provider>
בקובץ AndroidManifest.xml
.
כמו כן, צריך לכלול תווית, כלומר השם של האפליקציה. הגדרות החיפוש של המערכת משתמשות בתווית זו לצורך ספירת אפליקציות שזמינות לחיפוש.
הקובץ searchable.xml
חייב לכלול גם את
android:searchSuggestIntentAction
עם הערך "android.intent.action.VIEW"
כדי להגדיר את פעולת Intent לשליחת הצעה בהתאמה אישית. היא שונה מהכוונה
פעולה למילוי מונח חיפוש, כפי שמתואר בקטע הבא.
בדרכים אחרות שבהן אפשר להצהיר על פעולת הכוונה לגבי הצעות:
ראה הצהרה על
פעולת Intent.
בנוסף לפעולת ה-Intent, האפליקציה שלכם צריכה לספק את נתוני ה-Intent, שאותם ציינתם עם
android:searchSuggestIntentData
. זהו החלק הראשון ב-URI שמצביע
לתוכן, שמתאר את החלק מה-URI המשותף לכל השורות בטבלת המיפוי
תוכן. החלק של ה-URI הייחודי לכל שורה נקבע עם השדה SUGGEST_COLUMN_INTENT_DATA_ID
,
כפי שמתואר בקטע זיהוי עמודות.
לדרכים נוספות להצהיר על נתוני Intent בהצעות:
הצהרה
נתוני Intent.
המאפיין android:searchSuggestSelection=" ?"
מציין את הערך שהועבר
בתור הפרמטר selection
של query()
. הערך של סימן השאלה (?
) יוחלף בטקסט של השאילתה.
לסיום, צריך לכלול גם את
android:includeInGlobalSearch
עם הערך "true"
. כאן מוצגת דוגמה
קובץ 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>
לטפל במונחי חיפוש
ברגע שבתיבת הדו-שיח של החיפוש יש מילה שתואמת לערך באחת מהעמודות של האפליקציה, כמו
שמתואר בקטע זיהוי עמודות, המערכת מפעילה את
Intent מסוג ACTION_SEARCH
.
הפעילות באפליקציה שמטפלת בגורמים האלה
Intent מחפש במאגר עמודות שהמילה הנתנה מופיעה בהן בערכים שלהם, ומחזיר רשימה
של פריטי תוכן בעמודות האלה. בקובץ AndroidManifest.xml
, מציינים את
פעילות שמטפלת ב-ACTION_SEARCH
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" /> ...
הפעילות חייבת לתאר גם את ההגדרה שניתנת לחיפוש, כולל אזכור של
searchable.xml
.
כדי להשתמש בתיבת הדו-שיח של החיפוש הגלובלי,
המניפסט חייב לתאר איזו פעילות צריכה לקבל שאילתות חיפוש. כמו כן, המניפסט
תיאור של <provider>
, בדיוק כפי שהוא מתואר בקובץ searchable.xml
.
קישור עומק לאפליקציה במסך הפרטים
אם קבעתם את הגדרות החיפוש כפי שמתואר בקטע חיפוש כינוי
הצעות ומיפית את SUGGEST_COLUMN_TEXT_1
,
SUGGEST_COLUMN_PRODUCTION_YEAR
, וגם
SUGGEST_COLUMN_DURATION
שדות כפי שמתואר ב
בקטע זיהוי עמודות,
קישור עומק לפעולת צפייה בתוכן שלכם מופיע במסך הפרטים שמופעל
המשתמש בוחר תוצאת חיפוש:
כשהמשתמש לוחץ על הקישור לאפליקציה, שמזוהה על ידי הלחצן **זמין ב-**.
במסך הפרטים, המערכת מפעילה את הפעילות שמטפלת ב-ACTION_VIEW
הגדרה בתור
android:searchSuggestIntentAction
עם הערך "android.intent.action.VIEW"
ב-
הקובץ searchable.xml
.
אפשר גם להגדיר קהל בהתאמה אישית עם כוונת רכישה כדי להתחיל את הפעילות. אפשר לראות זאת
אפליקציה לדוגמה של Leanback
הקצר הזה. התשובות שלך יעזרו לנו להשתפר. לתשומת ליבך, האפליקציה לדוגמה מפעילה LeanbackDetailsFragment
משלה במסגרת
הצגת הפרטים של המדיה שנבחרה. באפליקציות שלכם, להפעיל את הפעילות שמפעילה את המדיה
מיד כדי לשמור את המשתמש קליק נוסף או שניים.
התנהגות חיפוש
אפשר לחפש ב-Android TV ממסך הבית ומהאפליקציה שלך. תוצאות חיפוש שונים בשני המקרים.
חיפוש ממסך הבית
כשהמשתמש יבצע חיפוש ממסך הבית, התוצאה הראשונה תופיע בכרטיס ישות. אם יש אפליקציות שיכולות להפעיל את התוכן. קישור לכל אחת מהן מופיע בתחתית הכרטיס:
לא ניתן להוסיף אפליקציה באופן פרוגרמטי לכרטיס הישות. כדי להיכלל אפשרות הפעלה, תוצאות החיפוש של האפליקציה חייבות להתאים לשם, לשנה ולמשך הזמן תוכן שחיפשתם.
יכול להיות שיופיעו תוצאות חיפוש נוספות מתחת לכרטיס. כדי לראות אותן, המשתמש צריך ללחוץ לחיצה ארוכה על מרחוק וגוללים למטה. התוצאות של כל אפליקציה מופיעות בשורה נפרדת. לא ניתן לשלוט לסידור השורות. אפליקציות שתומכות פעולות הצפייה מופיעות ראשונות.
חיפוש מהאפליקציה
המשתמש יכול גם להתחיל חיפוש מתוך האפליקציה על ידי הפעלת המיקרופון מהשלט הרחוק או בקר משחקים. תוצאות החיפוש מוצגות בשורה אחת מעל התוכן של האפליקציה. האפליקציה יוצרת תוצאות חיפוש באמצעות ספק חיפוש גלובלי משלה.
מידע נוסף
למידע נוסף על חיפוש באפליקציה לטלוויזיה, אפשר לקרוא את המאמר שילוב תכונות החיפוש של Android באפליקציה הוספה של פונקציונליות חיפוש.
למידע נוסף על התאמה אישית של חוויית החיפוש בתוך האפליקציה באמצעות SearchFragment
, אפשר לקרוא את המאמר
לחיפוש באפליקציות לטלוויזיה.