TV'de çalışan bir medya uygulamasının, kullanıcıların içerik tekliflerine göz atmasına, seçim yapmasına ve içerik oynatmaya başlamasına izin vermesi gerekir. İçerik tarama deneyimi basit ve sezgisel olmanın yanı sıra görsel açıdan ilgi çekici ve ilgi çekici olmalıdır.
Bu kılavuzda, uygulamanızın medya kataloğundaki müziklere veya videolara göz atmak için bir kullanıcı arayüzü uygulamak üzere Leanback androidx kitaplığı tarafından sağlanan sınıfların nasıl kullanılacağı anlatılmaktadır.
Not: Burada gösterilen uygulama örneğinde, kullanımdan kaldırılan BrowseFragment
sınıfı yerine BrowseSupportFragment
kullanılmaktadır. BrowseSupportFragment
, AndroidX
Fragment
sınıfını genişleterek farklı cihazlarda ve Android sürümlerinde tutarlı davranış sağlamaya yardımcı olur.
![Uygulama ana ekranı](https://developer.android.google.cn/static/images/tv/app-browse.png?authuser=5&hl=tr)
Şekil 1. Leanback örnek uygulamasının göz atma parçası, video katalog verilerini görüntüler.
Medyaya göz atma düzeni oluşturma
Leanback kitaplığındaki BrowseSupportFragment
sınıfı, minimum kod kullanarak medya öğesi satırlarına ve kategorilerine göz atmak için birincil düzen oluşturmanıza olanak tanır. Aşağıdaki örnekte, BrowseSupportFragment
nesnesi içeren bir düzenin nasıl oluşturulacağı gösterilmektedir:
<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>
Bu görünümü, aşağıdaki örnekte gösterildiği gibi, uygulamanın ana etkinliği ayarlar:
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); } ...
BrowseSupportFragment
yöntemleri, görünümü video verileri ve kullanıcı arayüzü öğeleriyle doldurur ve simge, başlık gibi düzen parametrelerini ve kategori başlıklarının etkin olup olmadığını belirler.
Uygulamanın BrowseSupportFragment
yöntemlerini uygulayan alt sınıfı, aşağıdaki örnekte gösterildiği gibi, kullanıcı arayüzü öğelerindeki kullanıcı işlemleri için etkinlik işleyiciler de ayarlar ve arka plan yöneticisini hazırlar:
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()); } ...
Kullanıcı arayüzü öğelerini ayarlama
Önceki örnekte, setupUIElements()
gizli yöntemi medya kataloğu tarayıcısını biçimlendirmek için birkaç BrowseSupportFragment
yöntemini çağırır:
setBadgeDrawable()
, belirtilen çekilebilir kaynağı şekil 1 ve 2'de gösterildiği gibi göz atma parçasının sağ üst köşesine yerleştirir. Bu yöntem,setTitle()
de çağrılırsa başlık dizesini çekilebilir kaynakla değiştirir. Çekilebilir kaynağın yüksekliği 52 dp olmalıdır.setTitle()
,setBadgeDrawable()
çağrılmadığı sürece başlık dizesini göz atma parçasının sağ üst köşesinde ayarlar.setHeadersState()
vesetHeadersTransitionOnBackEnabled()
, üst bilgileri gizler veya devre dışı bırakır. Daha fazla bilgi için Üstbilgileri gizleme veya devre dışı bırakma bölümüne bakın.setBrandColor()
, göz atma parçasındaki kullanıcı arayüzü öğelerinin arka plan rengini, özellikle de başlık bölümü arka plan rengini belirtilen renk değeriyle ayarlar.setSearchAffordanceColor()
, arama simgesinin rengini belirtilen renk değeriyle belirler. Şekil 1 ve 2'de gösterildiği gibi, göz atma parçasının sol üst köşesinde arama simgesi görünür.
Başlık görünümlerini özelleştirme
Şekil 1'de gösterilen göz atma parçası, video veritabanındaki satır başlıkları olan video kategorisi adlarını metin görünümlerinde göstermektedir. Ayrıca başlığı, daha karmaşık bir düzende ek görünümler içerecek şekilde özelleştirebilirsiniz. Aşağıdaki bölümlerde, Şekil 2'de gösterildiği gibi, kategori adının yanında bir simge bulunan bir resim görünümünün nasıl ekleneceği gösterilmektedir.
![Uygulama ana ekranı](https://developer.android.google.cn/static/images/tv/custom-head.png?authuser=5&hl=tr)
2. Şekil. Göz atma parçasındaki hem bir simge hem de metin etiketine sahip satır başlıkları.
Satır başlığının düzeni aşağıdaki gibi tanımlanır:
<?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>
Görünüm sahibini oluşturmak, bağlamak ve bağlantısını kaldırmak için bir Presenter
kullanın ve soyut yöntemleri uygulayın. Aşağıdaki örnekte, görüntüleme sahibinin ImageView
ve TextView
olmak üzere iki görünümle nasıl bağlanacağı gösterilmektedir.
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 } }
D-pad'in gezinmek için kullanılabilmesi için başlıklarınıza odaklanabilirsiniz. Bunu yönetmenin iki yolu vardır:
onBindViewHolder()
ürününde görünümünüzü odaklanılabilir şekilde ayarlayın: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 // ... }
- Düzeninizi odaklanılabilir olacak şekilde ayarlayın:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" ... android:focusable="true">
Son olarak, katalog tarayıcısını görüntüleyen BrowseSupportFragment
uygulamasında, aşağıdaki örnekte gösterildiği gibi, satır başlığı için sunucuyu ayarlamak üzere setHeaderPresenterSelector()
yöntemini kullanın.
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(); } });
Tam bir örnek için Leanback örnek uygulamasına bakın.
Üstbilgileri gizleme veya devre dışı bırakma
Kaydırılabilir bir liste gerektirecek kadar kategorinin olmaması gibi bazı durumlarda satır başlıklarının görünmesini istemezsiniz. Satır üst bilgilerini gizlemek veya devre dışı bırakmak için parçanın onActivityCreated()
yöntemi sırasında BrowseSupportFragment.setHeadersState()
yöntemini çağırın. setHeadersState()
yöntemi, aşağıdaki sabitlerden biri parametre olarak verildiğinde göz atma parçasındaki başlıkların başlangıç durumunu ayarlar:
HEADERS_ENABLED
: Göz atma parçası etkinliği oluşturulduğunda başlıklar etkinleştirilir ve varsayılan olarak gösterilir. Başlıklar, bu sayfadaki şekil 1 ve 2'de gösterildiği gibi görünür.HEADERS_HIDDEN
: Göz atma parçası etkinliği oluşturulduğunda başlıklar varsayılan olarak etkinleştirilir ve gizlenir. Ekranın başlık bölümü Kart görünümü sağlama bölümündeki şekilde gösterildiği gibi daraltılmış durumdadır. Kullanıcı, daraltılmış başlık bölümünü seçerek genişletebilir.HEADERS_DISABLED
: Göz atma parçası etkinliği oluşturulduğunda başlıklar varsayılan olarak devre dışı bırakılır ve hiçbir zaman gösterilmez.
HEADERS_ENABLED
veya HEADERS_HIDDEN
ayarlanmışsa satırdaki seçili bir içerik öğesinden satır başlığına geri dönmeyi desteklemek için
setHeadersTransitionOnBackEnabled()
çağrısını çağırabilirsiniz. Yöntemi çağırmazsanız bu özellik varsayılan olarak etkinleştirilir. Geri hareketini kendiniz yönetmek için false
öğesini setHeadersTransitionOnBackEnabled()
bölümüne iletin ve kendi arka yığın işlemenizi uygulayın.
Görüntülü reklam medya listeleri
BrowseSupportFragment
sınıfı, bağdaştırıcıları ve sunucuları kullanarak bir medya kataloğundan göz atılabilir medya içeriği kategorilerini ve medya öğelerini tanımlayıp görüntülemenize olanak tanır. Bağdaştırıcılar, medya katalog bilgilerinizi içeren yerel veya çevrimiçi veri kaynaklarına bağlanabilmenizi sağlar.
Bağdaştırıcılar, görünümler oluşturmak ve bir öğeyi ekranda görüntülemek üzere verileri bu görünümlere bağlamak için sunucuları kullanır.
Aşağıdaki örnek kod, dize verilerini görüntülemek için bir Presenter
uygulamasını göstermektedir:
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 } }
Medya öğeleriniz için sunucu sınıfı oluşturduktan sonra bağdaştırıcı oluşturup bu öğeleri ekranda kullanıcının göz atması için görüntülemek üzere BrowseSupportFragment
öğesine ekleyebilirsiniz. Aşağıdaki örnek kodda, önceki kod örneğinde gösterilen StringPresenter
sınıfını kullanarak kategorileri ve bu kategorilerdeki öğeleri görüntülemek için nasıl bağdaştırıcı oluşturulacağı gösterilmektedir:
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); }
Bu örnekte, bağdaştırıcıların statik uygulaması gösterilmektedir. Tipik bir medya tarama uygulaması, çevrimiçi bir veritabanından veya web hizmetinden alınan verileri kullanır. Web'den alınan verileri kullanan bir tarama uygulaması örneği için Leanback örnek uygulamasına bakın.
Arka planı güncelle
TV'deki bir medya tarama uygulamasına görsel açıdan ilgi eklemek için kullanıcılar içeriğe göz atarken arka plan resmini güncelleyebilirsiniz. Bu teknik, uygulamanızla etkileşimi daha sinematik ve keyifli hale getirebilir.
Leanback destek kitaplığı, TV uygulaması etkinliğinizin arka planını değiştirmek için bir BackgroundManager
sınıfı sağlar. Aşağıdaki örnekte, TV uygulama etkinliğinizde arka planı güncellemek için nasıl basit bir yöntem oluşturulacağı gösterilmektedir:
Kotlin
protected fun updateBackground(drawable: Drawable) { BackgroundManager.getInstance(this).drawable = drawable }
Java
protected void updateBackground(Drawable drawable) { BackgroundManager.getInstance(this).setDrawable(drawable); }
Birçok medya tarama uygulaması, kullanıcı medya listelerinde gezinirken arka planı otomatik olarak günceller. Bunu yapmak için arka planı kullanıcının geçerli seçimine göre otomatik olarak güncelleyecek bir seçim işleyici ayarlayabilirsiniz. Aşağıdaki örnekte, seçim etkinliklerini yakalamak ve arka planı güncellemek için OnItemViewSelectedListener
sınıfının nasıl oluşturulacağı gösterilmektedir:
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(); } } }; }
Not: Önceki uygulama, açıklama amaçlı basit bir örnektir. Bu işlevi kendi uygulamanızda oluştururken daha iyi performans için arka plan güncelleme işlemini ayrı bir iş parçacığında çalıştırın. Ayrıca, öğeler arasında gezinen kullanıcılara yanıt olarak arka planı güncellemeyi planlıyorsanız arka plan resmi güncellemesini kullanıcı bir öğe üzerinde çalışmaya başlayana kadar ertelemek için bir süre ekleyin. Bu teknik, aşırı sayıda arka plan resmi güncellemesinin önüne geçer.