Aplikasi media yang berjalan di TV harus memungkinkan pengguna menjelajahi penawaran kontennya, membuat pilihan, dan mulai memutar konten. Pengalaman penjelajahan konten harus sederhana dan intuitif, serta menyenangkan dan menarik secara visual.
Panduan ini membahas cara menggunakan class yang disediakan oleh library androidx.leanback yang tidak digunakan lagi untuk mengimplementasikan antarmuka pengguna bagi penjelajahan musik atau video dari katalog media aplikasi Anda.
Catatan: Contoh implementasi yang ditampilkan di sini menggunakan class BrowseSupportFragment
, bukan class BrowseFragment
yang tidak digunakan lagi. BrowseSupportFragment
memperluas class Fragment
AndroidX,
membantu memastikan perilaku yang konsisten di seluruh perangkat dan versi Android.

Gambar 1. Fragmen penjelajahan aplikasi contoh Leanback menampilkan data katalog video.
Membuat tata letak penelusuran media
Class BrowseSupportFragment
dalam toolkit UI Leanback
memungkinkan Anda membuat tata letak utama untuk menjelajahi kategori dan baris item media dengan
kode yang minimum. Contoh berikut menunjukkan cara membuat tata letak yang berisi objek
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>
Aktivitas utama aplikasi mengatur tampilan ini, seperti yang ditampilkan dalam contoh berikut:
Kotlin
class MainActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.main) } ...
Java
public class MainActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } ...
Metode BrowseSupportFragment
mengisi tampilan dengan data video dan elemen UI serta menyetel parameter tata letak seperti ikon dan judul serta apakah header kategori diaktifkan.
Subclass aplikasi yang mengimplementasikan metode BrowseSupportFragment
juga menyiapkan pemroses peristiwa untuk tindakan pengguna pada elemen UI dan menyiapkan pengelola latar belakang, seperti yang ditampilkan dalam contoh berikut:
Kotlin
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() } ...
Java
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()); } ...
Menyetel elemen UI
Dalam contoh sebelumnya, metode pribadi setupUIElements()
memanggil beberapa metode
BrowseSupportFragment
untuk mengatur gaya browser katalog media:
setBadgeDrawable()
menempatkan resource drawable yang ditetapkan di pojok kanan atas fragmen jelajah, seperti yang ditampilkan dalam gambar 1 dan 2. Metode ini menggantikan string judul dengan resource drawable, jikasetTitle()
juga dipanggil. Resource yang dapat digambar harus memiliki tinggi 52 dp.setTitle()
menyetel string judul di pojok kanan atas fragmen jelajah, kecuali jikasetBadgeDrawable()
dipanggil.setHeadersState()
dansetHeadersTransitionOnBackEnabled()
menyembunyikan atau menonaktifkan header. Lihat bagian Menyembunyikan atau menonaktifkan header untuk mengetahui informasi selengkapnya.setBrandColor()
menyetel warna latar belakang untuk elemen UI dalam fragmen jelajah, khususnya warna latar belakang bagian header, dengan nilai warna yang telah ditetapkan.setSearchAffordanceColor()
menyetel warna ikon penelusuran dengan nilai warna yang ditetapkan. Ikon penelusuran muncul di pojok kiri atas fragmen jelajah, seperti yang ditunjukkan dalam gambar 1 dan 2.
Menyesuaikan tampilan header
Fragmen penjelajahan yang ditampilkan dalam gambar 1 menampilkan nama kategori video, yang merupakan header baris dalam database video, dalam tampilan teks. Anda juga dapat menyesuaikan header untuk menyertakan tampilan tambahan dalam tata letak yang lebih kompleks. Bagian berikut menunjukkan cara menyertakan tampilan gambar yang menampilkan ikon di samping nama kategori, seperti dalam gambar 2.

Gambar 2. Header baris dalam fragmen jelajah dengan ikon dan label teks.
Tata letak untuk header baris ditentukan sebagai berikut:
<?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>
Gunakan Presenter
dan implementasikan
metode abstrak untuk membuat, mengikat, dan melepaskan ikatan view holder. Contoh
berikut menunjukkan cara mengikat viewholder dengan dua tampilan, ImageView
dan TextView
.
Kotlin
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 } }
Java
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 } }
Header harus bisa difokuskan agar D-pad bisa digunakan untuk men-scroll-nya. Ada dua cara untuk mengelola hal ini:
- Menyetel tampilan agar dapat difokuskan di
onBindViewHolder()
:Kotlin
override fun onBindViewHolder(viewHolder: Presenter.ViewHolder, o: Any) { val headerItem = (o as ListRow).headerItem val rootView = viewHolder.view rootView.focusable = View.FOCUSABLE // ... }
Java
@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 // ... }
- Menyetel tata letak agar dapat difokuskan:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" ... android:focusable="true">
Terakhir, dalam implementasi BrowseSupportFragment
yang menampilkan browser katalog, gunakan metode setHeaderPresenterSelector()
untuk menyetel presenter bagi header baris, seperti yang ditampilkan dalam contoh berikut.
Kotlin
setHeaderPresenterSelector(object : PresenterSelector() { override fun getPresenter(o: Any): Presenter { return IconHeaderItemPresenter() } })
Java
setHeaderPresenterSelector(new PresenterSelector() { @Override public Presenter getPresenter(Object o) { return new IconHeaderItemPresenter(); } });
Untuk contoh lengkapnya, lihat aplikasi contoh Leanback .
Menyembunyikan atau menonaktifkan header
Terkadang Anda tidak ingin menampilkan header baris, seperti ketika kategori yang ada tidak mencukupi
untuk mengharuskan daftar yang dapat di-scroll. Panggil metode BrowseSupportFragment.setHeadersState()
selama metode onActivityCreated()
fragmen untuk menyembunyikan atau menonaktifkan header baris. Metode setHeadersState()
menyetel status awal header dalam fragmen penjelajahan, yang diberi salah satu konstanta berikut sebagai parameter:
HEADERS_ENABLED
: saat aktivitas fragmen penjelajahan dibuat, header akan diaktifkan dan ditampilkan secara default. Header muncul seperti yang ditunjukkan dalam gambar 1 dan 2 di halaman ini.HEADERS_HIDDEN
: saat aktivitas fragmen penjelajahan dibuat, header akan diaktifkan dan disembunyikan secara default. Bagian header layar akan diciutkan, seperti yang ditunjukkan dalam gambar di Menyediakan tampilan kartu. Pengguna dapat memilih bagian header yang diciutkan untuk meluaskannya.HEADERS_DISABLED
: saat aktivitas fragmen penjelajahan dibuat, header akan dinonaktifkan secara default dan tidak pernah ditampilkan.
Jika HEADERS_ENABLED
atau HEADERS_HIDDEN
disetel, Anda dapat memanggil
setHeadersTransitionOnBackEnabled()
untuk mendukung pemindahan kembali ke header baris dari item konten yang dipilih dalam baris. Fitur ini diaktifkan secara
default jika Anda tidak memanggil metode. Untuk menangani gerakan kembali sendiri,
terus false
ke setHeadersTransitionOnBackEnabled()
dan terapkan penanganan back stack Anda sendiri.
Menampilkan daftar media
Class BrowseSupportFragment
memungkinkan Anda
menentukan dan menampilkan kategori konten media dan item media yang dapat dijelajahi dari
katalog media menggunakan adaptor dan presenter. Adaptor memungkinkan Anda terhubung
ke sumber data lokal atau online yang berisi informasi katalog media Anda.
Adaptor menggunakan presenter untuk membuat tampilan dan mengikat data ke tampilan
tersebut untuk menampilkan item di layar.
Kode contoh berikut menunjukkan implementasi Presenter
untuk menampilkan data string:
Kotlin
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 } }
Java
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 } }
Setelah membuat class presenter untuk item media, Anda dapat membuat
adaptor dan melampirkannya ke BrowseSupportFragment
untuk menampilkan item tersebut di layar agar dijelajahi pengguna. Contoh kode berikut menunjukkan cara membuat adaptor untuk menampilkan kategori dan item dalam kategori tersebut menggunakan class StringPresenter
yang ditampilkan dalam contoh kode sebelumnya:
Kotlin
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 }
Java
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); }
Contoh berikut ini menampilkan implementasi statik adaptor. Aplikasi penjelajahan media biasanya menggunakan data dari database online atau layanan web. Untuk contoh aplikasi penjelajahan yang menggunakan data yang diambil dari web, lihat aplikasi contoh Leanback .
Memperbarui latar belakang
Untuk menambahkan daya tarik visual ke aplikasi penjelajahan media di TV, Anda bisa memperbarui gambar latar belakang saat pengguna menjelajahi konten. Teknik ini dapat menjadikan interaksi dengan aplikasi Anda lebih sinematik dan menyenangkan.
Toolkit UI Leanback menyediakan class BackgroundManager
untuk mengubah latar belakang aktivitas aplikasi TV Anda. Contoh berikut menunjukkan cara
membuat metode sederhana untuk memperbarui latar belakang dalam aktivitas aplikasi TV Anda:
Kotlin
protected fun updateBackground(drawable: Drawable) { BackgroundManager.getInstance(this).drawable = drawable }
Java
protected void updateBackground(Drawable drawable) { BackgroundManager.getInstance(this).setDrawable(drawable); }
Banyak aplikasi penjelajahan media otomatis memperbarui latar belakang saat pengguna menelusuri daftar media. Untuk melakukannya, Anda dapat membuat pemroses pilihan agar memperbarui
latar belakang secara otomatis berdasarkan pilihan pengguna. Contoh berikut menunjukkan cara
menyiapkan class OnItemViewSelectedListener
untuk
merekam peristiwa pilihan dan memperbarui latar belakang:
Kotlin
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() } }
Java
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(); } } }; }
Catatan: Penerapan sebelumnya adalah contoh sederhana untuk tujuan ilustrasi. Saat membuat fungsi ini dalam aplikasi sendiri, jalankan tindakan pembaruan latar belakang di thread terpisah agar kinerja menjadi lebih baik. Selain itu, jika Anda berencana memperbarui latar belakang untuk merespons scroll pengguna yang menelusuri item, tambahkan waktu tunda bagi pembaruan gambar latar belakang hingga pengguna memilih sebuah item. Teknik ini akan menghindari pembaruan gambar latar belakang yang berlebihan.