يستخدم Android TV واجهة بحث Android لاسترداد بيانات المحتوى من التطبيقات المثبَّتة وعرض نتائج البحث للمستخدم. يمكن تضمين بيانات محتوى تطبيقك في هذه النتائج لمنح المستخدم إمكانية الوصول الفوري إلى المحتوى في تطبيقك.
يجب أن يزوّد تطبيقك Android TV بحقول البيانات التي يمكن أن ينشئ منها Android TV نتائج بحث مقترَحة عندما يُدخِل المستخدم أحرفًا في مربّع البحث. لإجراء ذلك، يجب أن ينفّذ تطبيقك
موفّر محتوى يعرض
الاقتراحات بالإضافة إلى ملف إعدادات
searchable.xml يصف موفّر المحتوى والمعلومات الأساسية الأخرى لنظام Android TV. تحتاج أيضًا إلى نشاط يعالج الغرض الذي يتم تشغيله عندما يختار المستخدم نتيجة بحث مقترَحة. لمزيد
من التفاصيل، يُرجى الاطّلاع على مقالة إضافة
اقتراحات بحث مخصّصة. يغطّي هذا الدليل النقاط الرئيسية الخاصة بتطبيقات Android TV.
قبل قراءة هذا الدليل، تأكَّد من الإلمام بالمفاهيم الموضّحة في الـ Search API guide. راجِع أيضًا مقالة إضافة وظيفة البحث.
تأتي عينة التعليمات البرمجية في هذا الدليل من Leanback sample app .
تحديد الأعمدة
يصف 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(): Map<String, 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" حتى يتمكّن "بحث Google" العالمي من استخدام النتائج التي يتم عرضها منه.
<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"
لتحديد إجراء الغرض لتوفير اقتراح مخصّص. يختلف هذا عن إجراء الغرض
لتوفير عبارة بحث، كما هو موضّح في القسم التالي.
للاطّلاع على طرق أخرى للإعلان عن إجراء الغرض للاقتراحات،
يُرجى مراجعة مقالة الإعلان عن
إجراء الغرض.
بالإضافة إلى إجراء الغرض، يجب أن يوفّر تطبيقك بيانات الغرض التي تحدّدها باستخدام السمة
android:searchSuggestIntentData. هذا هو الجزء الأول من URI الذي يشير إلى المحتوى، والذي يصف جزء URI الشائع في جميع صفوف جدول الربط لهذا المحتوى. يتم تحديد جزء URI الفريد لكل صف باستخدام حقل SUGGEST_COLUMN_INTENT_DATA_ID،
كما هو موضّح في قسم تحديد الأعمدة.
للاطّلاع على طرق أخرى للإعلان عن بيانات الغرض للاقتراحات، يُرجى مراجعة مقالة
الإعلان
عن بيانات الغرض.
تحدّد السمة 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>
معالجة عبارات البحث
بمجرد أن يحتوي مربّع البحث على كلمة تطابِق القيمة في أحد أعمدة تطبيقك، كما
هو موضّح في قسم تحديد الأعمدة، يشغّل النظام الغرض
ACTION_SEARCH.
يبحث النشاط في تطبيقك الذي يعالج هذا الغرض في المستودع عن أعمدة تحتوي على الكلمة المحدّدة في قيمها ويعرض قائمة بعناصر المحتوى التي تتضمّن هذه الأعمدة. في ملف AndroidManifest.xml ، يمكنك تحديد النشاط الذي يعالج الغرض ACTION_SEARCH كما هو موضّح في المثال التالي:
... <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، يُرجى قراءة
البحث داخل تطبيقات التلفزيون.