یک برنامه رسانه ای که روی تلویزیون اجرا می شود باید به کاربران اجازه دهد محتوای ارائه شده آن را مرور کنند، انتخاب کنند و شروع به پخش محتوا کنند. تجربه مرور محتوا باید ساده و شهودی و همچنین از نظر بصری دلپذیر و جذاب باشد.
این راهنما نحوه استفاده از کلاسهای ارائهشده توسط کتابخانه androidx.leanback را برای پیادهسازی یک رابط کاربری برای مرور موسیقی یا ویدیوها از کاتالوگ رسانه برنامهتان مورد بحث قرار میدهد.
توجه: مثال پیاده سازی نشان داده شده در اینجا از BrowseSupportFragment
به جای کلاس BrowseFragment
منسوخ استفاده می کند. BrowseSupportFragment
کلاس AndroidX Fragment
را گسترش میدهد و به اطمینان از رفتار ثابت در دستگاهها و نسخههای Android کمک میکند.
![صفحه اصلی برنامه](https://developer.android.com/static/images/tv/app-browse.png?hl=fa)
شکل 1. بخش مرور برنامه نمونه Leanback داده های کاتالوگ ویدیو را نمایش می دهد.
یک طرح بندی مرور رسانه ای ایجاد کنید
کلاس BrowseSupportFragment
در جعبه ابزار Leanback UI به شما امکان می دهد یک طرح اولیه برای مرور دسته ها و ردیف های آیتم های رسانه با حداقل کد ایجاد کنید. مثال زیر نشان می دهد که چگونه می توان یک طرح بندی حاوی یک شی BrowseSupportFragment
ایجاد کرد:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/main_frame" android:layout_width="match_parent" android:layout_height="match_parent"> <fragment android:name="com.example.android.tvleanback.ui.MainFragment" android:id="@+id/main_browse_fragment" android:layout_width="match_parent" android:layout_height="match_parent" /> </FrameLayout>
فعالیت اصلی برنامه این نمای را تنظیم می کند، همانطور که در مثال زیر نشان داده شده است:
کاتلین
class MainActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.main) } ...
جاوا
public class MainActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } ...
روشهای BrowseSupportFragment
نما را با دادههای ویدئویی و عناصر رابط کاربری پر میکنند و پارامترهای طرحبندی مانند نماد و عنوان و فعال بودن سرصفحههای دسته را تنظیم میکنند.
زیر کلاس برنامه که متدهای BrowseSupportFragment
پیادهسازی میکند، شنوندههای رویداد را برای اقدامات کاربر روی عناصر UI تنظیم میکند و مدیر پسزمینه را آماده میکند، همانطور که در مثال زیر نشان داده شده است:
کاتلین
class MainFragment : BrowseSupportFragment(), LoaderManager.LoaderCallbacks<HashMap<String, List<Movie>>> { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) loadVideoData() } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) prepareBackgroundManager() setupUIElements() setupEventListeners() } ... private fun prepareBackgroundManager() { backgroundManager = BackgroundManager.getInstance(activity).apply { attach(activity?.window) } defaultBackground = resources.getDrawable(R.drawable.default_background) metrics = DisplayMetrics() activity?.windowManager?.defaultDisplay?.getMetrics(metrics) } private fun setupUIElements() { badgeDrawable = resources.getDrawable(R.drawable.videos_by_google_banner) // Badge, when set, takes precedent over title title = getString(R.string.browse_title) headersState = BrowseSupportFragment.HEADERS_ENABLED isHeadersTransitionOnBackEnabled = true // Set header background color brandColor = ContextCompat.getColor(requireContext(), R.color.fastlane_background) // Set search icon color searchAffordanceColor = ContextCompat.getColor(requireContext(), R.color.search_opaque) } private fun loadVideoData() { VideoProvider.setContext(activity) videosUrl = getString(R.string.catalog_url) loaderManager.initLoader(0, null, this) } private fun setupEventListeners() { setOnSearchClickedListener { Intent(activity, SearchActivity::class.java).also { intent -> startActivity(intent) } } onItemViewClickedListener = ItemViewClickedListener() onItemViewSelectedListener = ItemViewSelectedListener() } ...
جاوا
public class MainFragment extends BrowseSupportFragment implements LoaderManager.LoaderCallbacks<HashMap<String, List<Movie>>> { } ... @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); loadVideoData(); } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); prepareBackgroundManager(); setupUIElements(); setupEventListeners(); } ... private void prepareBackgroundManager() { backgroundManager = BackgroundManager.getInstance(getActivity()); backgroundManager.attach(getActivity().getWindow()); defaultBackground = getResources() .getDrawable(R.drawable.default_background); metrics = new DisplayMetrics(); getActivity().getWindowManager().getDefaultDisplay().getMetrics(metrics); } private void setupUIElements() { setBadgeDrawable(getActivity().getResources() .getDrawable(R.drawable.videos_by_google_banner)); // Badge, when set, takes precedent over title setTitle(getString(R.string.browse_title)); setHeadersState(HEADERS_ENABLED); setHeadersTransitionOnBackEnabled(true); // Set header background color setBrandColor(ContextCompat.getColor(requireContext(), R.color.fastlane_background)); // Set search icon color setSearchAffordanceColor(ContextCompat.getColor(requireContext(), R.color.search_opaque)); } private void loadVideoData() { VideoProvider.setContext(getActivity()); videosUrl = getString(R.string.catalog_url); getLoaderManager().initLoader(0, null, this); } private void setupEventListeners() { setOnSearchClickedListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(getActivity(), SearchActivity.class); startActivity(intent); } }); setOnItemViewClickedListener(new ItemViewClickedListener()); setOnItemViewSelectedListener(new ItemViewSelectedListener()); } ...
عناصر UI را تنظیم کنید
در نمونه قبلی، متد خصوصی setupUIElements()
چندین متد BrowseSupportFragment
را برای استایل دادن به مرورگر کاتالوگ رسانه فراخوانی می کند:
-
setBadgeDrawable()
منبع قابل ترسیم مشخص شده را در گوشه سمت راست بالای قطعه مرور قرار می دهد، همانطور که در شکل های 1 و 2 نشان داده شده است. اگرsetTitle()
نیز فراخوانی شود، این روش رشته عنوان را با منبع drawable جایگزین می کند. منبع قابل کشیدن باید 52 dp ارتفاع داشته باشد. -
setTitle()
رشته عنوان را در گوشه سمت راست بالای قطعه مرور تنظیم می کند، مگر اینکهsetBadgeDrawable()
فراخوانی شود. -
setHeadersState()
وsetHeadersTransitionOnBackEnabled()
هدرها را مخفی یا غیرفعال می کنند. برای اطلاعات بیشتر به بخش پنهان کردن یا غیرفعال کردن هدرها مراجعه کنید. -
setBrandColor()
رنگ پسزمینه عناصر UI را در قطعه مرور، بهویژه رنگ پسزمینه بخش هدر، با مقدار رنگ مشخص شده تنظیم میکند. -
setSearchAffordanceColor()
رنگ نماد جستجو را با مقدار رنگ مشخص شده تنظیم می کند. نماد جستجو همانطور که در شکل های 1 و 2 نشان داده شده است در گوشه سمت چپ بالای قطعه مرور ظاهر می شود.
نماهای هدر را سفارشی کنید
قطعه مرور که در شکل 1 نشان داده شده است، نام دسته های ویدیو را که سرصفحه های ردیف در پایگاه داده ویدیو هستند، در نمای متنی نمایش می دهد. همچنین میتوانید هدر را سفارشی کنید تا نماهای اضافی را در یک طرحبندی پیچیدهتر اضافه کنید. بخشهای زیر نحوه اضافه کردن نمای تصویر را نشان میدهد که نمادی را در کنار نام دسته نمایش میدهد، همانطور که در شکل 2 نشان داده شده است.
![صفحه اصلی برنامه](https://developer.android.com/static/images/tv/custom-head.png?hl=fa)
شکل 2. سرصفحه های ردیف در قطعه مرور با نماد و برچسب متنی.
طرح هدر ردیف به صورت زیر تعریف می شود:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:id="@+id/header_icon" android:layout_width="32dp" android:layout_height="32dp" /> <TextView android:id="@+id/header_label" android:layout_marginTop="6dp" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout>
از یک Presenter
استفاده کنید و متدهای انتزاعی را برای ایجاد، پیوند و جداسازی دارنده view اجرا کنید. مثال زیر نشان می دهد که چگونه می توان viewholder را با دو نما، ImageView
و TextView
متصل کرد.
کاتلین
class IconHeaderItemPresenter : Presenter() { override fun onCreateViewHolder(viewGroup: ViewGroup): Presenter.ViewHolder { val view = LayoutInflater.from(viewGroup.context).run { inflate(R.layout.icon_header_item, null) } return Presenter.ViewHolder(view) } override fun onBindViewHolder(viewHolder: Presenter.ViewHolder, o: Any) { val headerItem = (o as ListRow).headerItem val rootView = viewHolder.view rootView.findViewById<ImageView>(R.id.header_icon).apply { rootView.resources.getDrawable(R.drawable.ic_action_video, null).also { icon -> setImageDrawable(icon) } } rootView.findViewById<TextView>(R.id.header_label).apply { text = headerItem.name } } override fun onUnbindViewHolder(viewHolder: Presenter.ViewHolder) { // no-op } }
جاوا
public class IconHeaderItemPresenter extends Presenter { @Override public ViewHolder onCreateViewHolder(ViewGroup viewGroup) { LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext()); View view = inflater.inflate(R.layout.icon_header_item, null); return new ViewHolder(view); } @Override public void onBindViewHolder(ViewHolder viewHolder, Object o) { HeaderItem headerItem = ((ListRow) o).getHeaderItem(); View rootView = viewHolder.view; ImageView iconView = (ImageView) rootView.findViewById(R.id.header_icon); Drawable icon = rootView.getResources().getDrawable(R.drawable.ic_action_video, null); iconView.setImageDrawable(icon); TextView label = (TextView) rootView.findViewById(R.id.header_label); label.setText(headerItem.getName()); } @Override public void onUnbindViewHolder(ViewHolder viewHolder) { // no-op } }
هدرهای شما باید قابل فوکوس باشند تا بتوان از D-pad برای پیمایش در آنها استفاده کرد. دو راه برای مدیریت این موضوع وجود دارد:
- نمای خود را طوری تنظیم کنید که در
onBindViewHolder()
قابل فوکوس باشد:کاتلین
override fun onBindViewHolder(viewHolder: Presenter.ViewHolder, o: Any) { val headerItem = (o as ListRow).headerItem val rootView = viewHolder.view rootView.focusable = View.FOCUSABLE // ... }
جاوا
@Override public void onBindViewHolder(ViewHolder viewHolder, Object o) { HeaderItem headerItem = ((ListRow) o).getHeaderItem(); View rootView = viewHolder.view; rootView.setFocusable(View.FOCUSABLE) // Allows the D-Pad to navigate to this header item // ... }
- چیدمان خود را طوری تنظیم کنید که قابل تمرکز باشد:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" ... android:focusable="true">
در نهایت، در پیاده سازی BrowseSupportFragment
که مرورگر کاتالوگ را نمایش می دهد، از متد setHeaderPresenterSelector()
برای تنظیم ارائه کننده برای سربرگ سطر استفاده کنید، همانطور که در مثال زیر نشان داده شده است.
کاتلین
setHeaderPresenterSelector(object : PresenterSelector() { override fun getPresenter(o: Any): Presenter { return IconHeaderItemPresenter() } })
جاوا
setHeaderPresenterSelector(new PresenterSelector() { @Override public Presenter getPresenter(Object o) { return new IconHeaderItemPresenter(); } });
برای مثال کامل، به برنامه نمونه Leanback مراجعه کنید.
هدرها را پنهان یا غیرفعال کنید
گاهی اوقات نمیخواهید سرصفحههای ردیف ظاهر شوند، مانند زمانی که دستهبندی کافی برای نیاز به فهرست قابل پیمایش وجود ندارد. متد BrowseSupportFragment.setHeadersState()
را در طول متد onActivityCreated()
فرگمنت فراخوانی کنید تا هدرهای سطر را پنهان یا غیرفعال کنید. متد setHeadersState()
وضعیت اولیه هدرها را در قطعه مرور با توجه به یکی از ثابت های زیر به عنوان پارامتر تنظیم می کند:
-
HEADERS_ENABLED
: هنگامی که فعالیت قطعه مرور ایجاد می شود، سرصفحه ها به طور پیش فرض فعال و نشان داده می شوند. سرصفحه ها مطابق شکل های 1 و 2 در این صفحه ظاهر می شوند. -
HEADERS_HIDDEN
: هنگامی که فعالیت قطعه مرور ایجاد می شود، سرصفحه ها به طور پیش فرض فعال و پنهان می شوند. همانطور که در شکل در ارائه نمای کارت نشان داده شده است، قسمت سرصفحه صفحه جمع شده است. کاربر می تواند قسمت سرصفحه جمع شده را برای گسترش آن انتخاب کند. -
HEADERS_DISABLED
: هنگامی که فعالیت قطعه مرور ایجاد می شود، سرصفحه ها به طور پیش فرض غیرفعال می شوند و هرگز نمایش داده نمی شوند.
اگر HEADERS_ENABLED
یا HEADERS_HIDDEN
تنظیم شده است، می توانید setHeadersTransitionOnBackEnabled()
را فراخوانی کنید تا از بازگشت به سرصفحه ردیف از یک آیتم محتوای انتخاب شده در ردیف پشتیبانی کنید. اگر روش را فراخوانی نکنید، این به طور پیش فرض فعال است. برای مدیریت حرکت برگشتی، false
به setHeadersTransitionOnBackEnabled()
ارسال کنید و مدیریت پشته پشته خود را پیاده سازی کنید.
نمایش لیست رسانه ها
کلاس BrowseSupportFragment
به شما امکان می دهد با استفاده از آداپتورها و ارائه کننده ها، دسته بندی های محتوای رسانه قابل مرور و موارد رسانه ای را از یک کاتالوگ رسانه تعریف و نمایش دهید. آداپتورها به شما امکان می دهند به منابع داده محلی یا آنلاین که حاوی اطلاعات کاتالوگ رسانه شما هستند متصل شوید. آداپتورها از ارائه کننده ها برای ایجاد نماها و پیوند داده ها به آن نماها برای نمایش یک آیتم روی صفحه استفاده می کنند.
کد مثال زیر اجرای یک Presenter
را برای نمایش داده های رشته نشان می دهد:
کاتلین
private const val TAG = "StringPresenter" class StringPresenter : Presenter() { override fun onCreateViewHolder(parent: ViewGroup): Presenter.ViewHolder { val textView = TextView(parent.context).apply { isFocusable = true isFocusableInTouchMode = true background = parent.resources.getDrawable(R.drawable.text_bg) } return Presenter.ViewHolder(textView) } override fun onBindViewHolder(viewHolder: Presenter.ViewHolder, item: Any) { (viewHolder.view as TextView).text = item.toString() } override fun onUnbindViewHolder(viewHolder: Presenter.ViewHolder) { // no op } }
جاوا
public class StringPresenter extends Presenter { private static final String TAG = "StringPresenter"; public ViewHolder onCreateViewHolder(ViewGroup parent) { TextView textView = new TextView(parent.getContext()); textView.setFocusable(true); textView.setFocusableInTouchMode(true); textView.setBackground( parent.getResources().getDrawable(R.drawable.text_bg)); return new ViewHolder(textView); } public void onBindViewHolder(ViewHolder viewHolder, Object item) { ((TextView) viewHolder.view).setText(item.toString()); } public void onUnbindViewHolder(ViewHolder viewHolder) { // no op } }
هنگامی که یک کلاس ارائه کننده برای آیتم های رسانه خود ایجاد کردید، می توانید یک آداپتور بسازید و آن را به BrowseSupportFragment
متصل کنید تا آن موارد را روی صفحه نمایش برای مرور توسط کاربر نمایش دهد. کد مثال زیر نشان می دهد که چگونه می توان یک آداپتور برای نمایش دسته ها و موارد در آن دسته ها با استفاده از کلاس StringPresenter
که در مثال کد قبلی نشان داده شده است، ساخت:
کاتلین
private const val NUM_ROWS = 4 ... private lateinit var rowsAdapter: ArrayObjectAdapter override fun onCreate(savedInstanceState: Bundle?) { ... buildRowsAdapter() } private fun buildRowsAdapter() { rowsAdapter = ArrayObjectAdapter(ListRowPresenter()) for (i in 0 until NUM_ROWS) { val listRowAdapter = ArrayObjectAdapter(StringPresenter()).apply { add("Media Item 1") add("Media Item 2") add("Media Item 3") } HeaderItem(i.toLong(), "Category $i").also { header -> rowsAdapter.add(ListRow(header, listRowAdapter)) } } browseSupportFragment.adapter = rowsAdapter }
جاوا
private ArrayObjectAdapter rowsAdapter; private static final int NUM_ROWS = 4; @Override protected void onCreate(Bundle savedInstanceState) { ... buildRowsAdapter(); } private void buildRowsAdapter() { rowsAdapter = new ArrayObjectAdapter(new ListRowPresenter()); for (int i = 0; i < NUM_ROWS; ++i) { ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter( new StringPresenter()); listRowAdapter.add("Media Item 1"); listRowAdapter.add("Media Item 2"); listRowAdapter.add("Media Item 3"); HeaderItem header = new HeaderItem(i, "Category " + i); rowsAdapter.add(new ListRow(header, listRowAdapter)); } browseSupportFragment.setAdapter(rowsAdapter); }
این مثال پیاده سازی ایستا از آداپتورها را نشان می دهد. یک برنامه معمولی مرورگر رسانه از داده های یک پایگاه داده آنلاین یا سرویس وب استفاده می کند. برای مثالی از یک برنامه مرور که از داده های بازیابی شده از وب استفاده می کند، به برنامه نمونه Leanback مراجعه کنید.
پس زمینه را به روز کنید
برای افزودن جذابیت بصری به برنامه مرور رسانه در تلویزیون، میتوانید تصویر پسزمینه را در حالی که کاربران در محتوا مرور میکنند، بهروزرسانی کنید. این تکنیک می تواند تعامل با برنامه شما را سینمایی و لذت بخش تر کند.
جعبه ابزار Leanback UI یک کلاس BackgroundManager
برای تغییر پسزمینه فعالیت برنامه تلویزیون شما ارائه میکند. مثال زیر نحوه ایجاد یک روش ساده برای بهروزرسانی پسزمینه در فعالیت برنامه تلویزیونی خود را نشان میدهد:
کاتلین
protected fun updateBackground(drawable: Drawable) { BackgroundManager.getInstance(this).drawable = drawable }
جاوا
protected void updateBackground(Drawable drawable) { BackgroundManager.getInstance(this).setDrawable(drawable); }
بسیاری از برنامههای مرورگر رسانه بهطور خودکار پسزمینه را بهروزرسانی میکنند که کاربر در فهرستهای رسانه پیمایش میکند. برای انجام این کار، می توانید یک شنونده انتخاب راه اندازی کنید تا به طور خودکار پس زمینه را بر اساس انتخاب فعلی کاربر به روز کند. مثال زیر نحوه راهاندازی کلاس OnItemViewSelectedListener
را برای گرفتن رویدادهای انتخابی و بهروزرسانی پسزمینه نشان میدهد:
کاتلین
protected fun clearBackground() { BackgroundManager.getInstance(this).drawable = defaultBackground } protected fun getDefaultItemViewSelectedListener(): OnItemViewSelectedListener = OnItemViewSelectedListener { _, item, _, _ -> if (item is Movie) { item.getBackdropDrawable().also { background -> updateBackground(background) } } else { clearBackground() } }
جاوا
protected void clearBackground() { BackgroundManager.getInstance(this).setDrawable(defaultBackground); } protected OnItemViewSelectedListener getDefaultItemViewSelectedListener() { return new OnItemViewSelectedListener() { @Override public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item, RowPresenter.ViewHolder rowViewHolder, Row row) { if (item instanceof Movie ) { Drawable background = ((Movie)item).getBackdropDrawable(); updateBackground(background); } else { clearBackground(); } } }; }
توجه: اجرای قبلی یک مثال ساده برای مصورسازی است. هنگام ایجاد این عملکرد در برنامه خود، برای عملکرد بهتر، اقدام به روز رسانی پس زمینه را در یک رشته جداگانه اجرا کنید. همچنین، اگر قصد دارید پسزمینه را در پاسخ به کاربرانی که در آیتمها پیمایش میکنند، بهروزرسانی کنید، زمانی را اضافه کنید تا بهروزرسانی تصویر پسزمینه را تا زمانی که کاربر روی یک مورد ثابت کند به تأخیر بیاندازید. این تکنیک از به روز رسانی بیش از حد تصاویر پس زمینه جلوگیری می کند.