TV'de çalışan bir medya uygulaması, kullanıcıların içerik tekliflerine göz atmasına, seçim yapmasına ve içeriği oynatmaya başlamasına olanak tanımalıdır. İçeriklere göz atma deneyimi basit ve sezgisel olmasının yanı sıra görsel olarak hoş ve ilgi çekici olmalıdır.
Bu kılavuzda, uygulamanızın medya kataloğundaki müziklere veya videolara göz atmak için kullanıcı arayüzü oluşturmak üzere kullanımdan kaldırılan androidx.leanback kitaplığı tarafından sağlanan sınıfların nasıl kullanılacağı açıklanmaktadır.
Not: Burada gösterilen uygulama örneğinde, kullanımdan kaldırılan BrowseFragment
sınıfı yerine BrowseSupportFragment
sınıfı kullanılmaktadır. BrowseSupportFragment
, AndroidX
Fragment
sınıfını genişletir ve cihazlar ile Android sürümleri arasında tutarlı davranışlar sağlanmasına yardımcı olur.

1.şekil Leanback örnek uygulamasının göz atma parçası, video kataloğu verilerini gösterir.
Medya tarama düzeni oluşturma
Leanback kullanıcı arayüzü araç setindeki BrowseSupportFragment
sınıfı, minimum kodla
kategorilere ve medya öğesi satırlarına 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>
Uygulamanın ana etkinliği bu görünümü ayarlar. Aşağıdaki örnekte gösterildiği gibi:
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. Ayrıca simge ve başlık gibi düzen parametrelerini ve kategori başlıklarının etkinleştirilip etkinleştirilmeyeceğini ayarlar.
BrowseSupportFragment
yöntemlerini uygulayan uygulamanın alt sınıfı, aşağıdaki örnekte gösterildiği gibi kullanıcı arayüzü öğelerindeki kullanıcı işlemleri için etkinlik dinleyicileri 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, özel yöntem setupUIElements()
, medya kataloğu tarayıcısını stilize etmek için çeşitli BrowseSupportFragment
yöntemlerini çağırır:
setBadgeDrawable()
belirtilen çizilebilir kaynağı, 1 ve 2 numaralı şekillerde 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 çizilebilir kaynakla değiştirir. Çizilebilir kaynak 52 dp yüksekliğinde olmalıdır.setTitle()
setBadgeDrawable()
çağrılmadığı sürece, göz atma parçasının sağ üst köşesindeki başlık dizesini ayarlar.setHeadersState()
vesetHeadersTransitionOnBackEnabled()
üstbilgileri gizler veya devre dışı bırakır. Daha fazla bilgi için Başlıkları gizleme veya devre dışı bırakma bölümünü inceleyin.setBrandColor()
göz atma parçasındaki kullanıcı arayüzü öğelerinin arka plan rengini, özellikle de başlık bölümünün arka plan rengini belirtilen renk değeriyle ayarlar.setSearchAffordanceColor()
arama simgesinin rengini belirtilen renk değeriyle ayarlar. Arama simgesi, Şekil 1 ve 2'de gösterildiği gibi göz atma parçasının sol üst köşesinde 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österir. Daha karmaşık bir düzende ek görünümler eklemek için başlığı da özelleştirebilirsiniz. Aşağıdaki bölümlerde, Şekil 2'de gösterildiği gibi kategori adının yanında bir simge gösteren resim görünümünün nasıl ekleneceği açıklanmaktadır.

Şekil 2. Göz atma parçasındaki hem simge hem de metin etiketi içeren satır başlıkları.
Satır başlığının düzeni şu şekilde 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>
Presenter
kullanın ve görünüm tutucuyu oluşturmak, bağlamak ve bağlantısını kaldırmak için soyut yöntemleri uygulayın. Aşağıdaki örnekte, görünüm tutucunun iki görünümle (ImageView
ve TextView
) 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 } }
Başlıklarınız, D-pad ile kaydırılabilmesi için odaklanılabilir olmalıdır. Bunu yönetmenin iki yolu vardır:
- Görünümünüzü
onBindViewHolder()
içinde odaklanılabilir olarak 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 ş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, BrowseSupportFragment
katalog tarayıcısını gösteren uygulamada, aşağıdaki örnekte gösterildiği gibi setHeaderPresenterSelector()
yöntemini kullanarak satır başlığı için sunan kişiyi ayarlayı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.
Başlıkları gizleme veya devre dışı bırakma
Bazen satır başlıklarının görünmesini istemeyebilirsiniz. Örneğin, kaydırılabilir bir liste gerektirecek kadar kategori yoktur. Satır başlıklarını gizlemek veya devre dışı bırakmak için parçanın BrowseSupportFragment.setHeadersState()
yöntemi sırasında onActivityCreated()
yöntemini çağırın. setHeadersState()
yöntemi, parametre olarak aşağıdaki sabitlerden biri 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 varsayılan olarak etkinleştirilir ve gösterilir. Üstbilgiler, 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 üstbilgi bölümü, Kart görünümü sağlama bölümündeki şekilde gösterildiği gibi daraltılı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()
işlevini çağırabilirsiniz. Yöntemi çağırmazsanız bu özellik varsayılan olarak etkindir. Geri hareketini kendiniz yönetmek için false
öğesini setHeadersTransitionOnBackEnabled()
öğesine iletin ve kendi geri yığın işleme mantığınızı uygulayın.
Görüntülü reklam medya listeleri
BrowseSupportFragment
sınıfı, uyarlayıcılar ve sunucular kullanarak bir medya kataloğundaki göz atılabilir medya içeriği kategorilerini ve medya öğelerini tanımlayıp görüntülemenize olanak tanır. Adaptörler, medya kataloğu bilgilerinizi içeren yerel veya online veri kaynaklarına bağlanmanıza olanak tanır.
Adaptörler, görünümler oluşturmak ve ekranda bir öğe göstermek için verileri bu görünümlere bağlamak üzere sunucuları kullanır.
Aşağıdaki örnek kodda, dize verilerini görüntülemek için Presenter
uygulaması gösterilmektedir:
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 bir sunucu sınıfı oluşturduktan sonra, bir bağdaştırıcı oluşturup BrowseSupportFragment
öğesine ekleyerek bu öğeleri ekranda kullanıcı tarafından göz atılacak şekilde görüntüleyebilirsiniz. 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 bir bağdaştırıcı oluşturma işlemi 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 bir uygulaması gösterilmektedir. Tipik bir medya tarama uygulaması, online bir veri tabanından veya web hizmetinden alınan verileri kullanır. Web'den alınan verileri kullanan bir göz atma uygulaması örneği için Leanback örnek uygulamasına bakın.
Arka planı güncelleme
TV'de medya tarama uygulamasına görsel ilgi çekicilik katmak için kullanıcılar içeriklere göz atarken arka plan resmini güncelleyebilirsiniz. Bu teknik, uygulamanızla etkileşimi daha sinematik ve keyifli hale getirebilir.
Leanback kullanıcı arayüzü araç seti, TV uygulaması etkinliğinizin arka planını değiştirmek için BackgroundManager
sınıfı sağlar. Aşağıdaki örnekte, TV uygulaması etkinliğinizdeki 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); }
Medya tarama uygulamalarının çoğu, kullanıcı medya listelerinde gezinirken arka planı otomatik olarak günceller. Bunu yapmak için, kullanıcının mevcut seçimine göre arka planı otomatik olarak güncellemek üzere bir seçim dinleyicisi ayarlayabilirsiniz. Aşağıdaki örnekte, seçim etkinliklerini yakalamak ve arka planı güncellemek için OnItemViewSelectedListener
sınıfının nasıl ayarlanacağı 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, yalnızca örnek 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, kullanıcıların öğeler arasında gezinmesine yanıt olarak arka planı güncellemeyi planlıyorsanız kullanıcı bir öğeyi seçene kadar arka plan resmi güncellemesini geciktirecek bir süre ekleyin. Bu teknik, arka plan resimlerinin aşırı güncellenmesini önler.