يستخدم Android TV واجهة البحث في Android لاسترداد بيانات المحتوى من التطبيقات المثبّتة وعرض نتائج البحث للمستخدم. يمكن تضمين بيانات محتوى تطبيقك مع هذه النتائج لمنح المستخدم الوصول الفوري إلى محتوى تطبيقك.
يجب أن يقدّم تطبيقك إلى Android TV حقول البيانات التي يمكن من خلالها أن ينشئ Android TV نتائج بحث مقترَحة عند إدخال المستخدم أحرفًا في مربّع حوار البحث. لإجراء ذلك، يجب أن يستخدم تطبيقك خيار موفّر محتوى يعرض الاقتراحات بالإضافة إلى ملف إعداد
searchable.xml
يصف مقدّم المحتوى ومعلومات مهمة أخرى حول Android TV. تحتاج أيضًا إلى نشاط يعالج القصد الذي
يتم تنشيطه عندما يختار المستخدم نتيجة بحث مقترحة. لمزيد من التفاصيل، راجِع إضافة اقتراحات بحث مخصّصة. يتناول هذا الدليل النقاط الرئيسية المتعلّقة بتطبيقات 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(): 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"
لكي يتمكن البحث العالمي من 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
.
يجب أيضًا تضمين label، وهو اسم التطبيق. تستخدم إعدادات بحث النظام هذا التصنيف عند تعداد التطبيقات القابلة للبحث.
يجب أن يتضمّن ملف 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
كما هو موضّح في قسم تحديد الأعمدة، سيظهر
رابط لصفحة في التطبيق يؤدي إلى إجراء مشاهدة للمحتوى الخاص بك في شاشة التفاصيل التي يتم تشغيلها عندما يختار المستخدم إحدى نتائج البحث:
![رابط لصفحة في التطبيق في شاشة التفاصيل](https://developer.android.com/static/images/tv/deep-link.png?authuser=6&hl=ar)
عندما يختار المستخدم رابط تطبيقك، الذي حدّده الزر **متاح مفعَّل** في شاشة
التفاصيل، يبدأ النظام النشاط الذي يعالج الحالة ACTION_VIEW
التي تم ضبطها على
android:searchSuggestIntentAction
بالقيمة "android.intent.action.VIEW"
في
ملف searchable.xml
.
يمكنك أيضًا إعداد هدف مخصّص حسب النية بالشراء لبدء نشاطك. يتضِّح ذلك في
نموذج تطبيق Leanback
. يُرجى العِلم أنّ نموذج التطبيق يشغِّل LeanbackDetailsFragment
الخاص به لعرض
تفاصيل الوسائط المحدَّدة. وفي تطبيقاتك، يمكنك إطلاق النشاط الذي يشغِّل الوسائط
فورًا لحفظ المستخدم بنقرة أخرى أو نقرتَين.
سلوك البحث
تتوفّر ميزة "البحث" على Android TV من الشاشة الرئيسية ومن داخل التطبيق، وتختلف نتائج البحث عن هاتين الحالتين.
البحث من الشاشة الرئيسية
عندما يُجري المستخدم بحثًا من الشاشة الرئيسية، تظهر النتيجة الأولى في بطاقة الكيان. إذا كانت هناك تطبيقات يمكنها تشغيل المحتوى، سيظهر رابط لكل تطبيق منها في أسفل البطاقة:
![تشغيل نتيجة بحث التلفزيون](https://developer.android.com/static/images/tv/tv-search-playback.png?authuser=6&hl=ar)
لا يمكنك وضع تطبيق آليًا في بطاقة الكيان. لتضمينه كخيار تشغيل، يجب أن تتطابق نتائج البحث عن التطبيق مع العنوان والسنة والمدة للمحتوى الذي يتم البحث عنه.
قد يتوفّر المزيد من نتائج البحث أسفل البطاقة. لرؤيتها، يجب على المستخدم الضغط على جهاز التحكم عن بُعد والانتقال لأسفل. تظهر نتائج كل تطبيق في صف منفصل. لا يمكنك التحكم في ترتيب الصف. يتم أولاً إدراج التطبيقات التي تتيح إجراءات الساعة.
![نتائج البحث عن البرامج التلفزيونية](https://developer.android.com/static/images/tv/tv-search-results.gif?authuser=6&hl=ar)
البحث من تطبيقك
يمكن للمستخدم أيضًا بدء البحث من داخل التطبيق عن طريق تشغيل الميكروفون من وحدة التحكم عن بُعد أو وحدة التحكم في لوحة الألعاب. يتمّ عرض نتائج البحث في صف واحد فوق محتوى التطبيق. ينشئ تطبيقك نتائج بحث باستخدام مزود البحث العالمي الخاص به.
![نتائج البحث داخل التطبيق التلفزيوني](https://developer.android.com/static/images/tv/tv-search-in-app.png?authuser=6&hl=ar)
مزيد من المعلومات
للمزيد من المعلومات حول البحث في تطبيقات التلفزيون، يُرجى الاطّلاع على المقالة دمج ميزات البحث على Android في تطبيقك وإضافة وظيفة بحث.
للحصول على مزيد من المعلومات عن طريقة تخصيص تجربة البحث داخل التطبيق باستخدام SearchFragment
، يمكنك الاطّلاع على
البحث في تطبيقات التلفزيون.