TV'de çalışan bir medya uygulamasının, kullanıcıların sunduğu içeriklere göz atmasına, seçim yapmasına ve içeriği oynatmaya başlamasına izin vermesi gerekir. İçeriğe göz atma 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 amacıyla androidx.leanback kitaplığı tarafından sağlanan sınıfların nasıl kullanılacağı anlatılmaktadır.
Not: Burada gösterilen uygulama örneği, kullanımdan kaldırılan BrowseFragment
sınıfı yerine BrowseSupportFragment
yöntemini kullanmaktadır. BrowseSupportFragment
, AndroidX
Fragment
sınıfını genişleterek cihazlar ve Android sürümleri arasında tutarlı davranış sağlanmasına yardımcı olur.
![Uygulama ana ekranı](https://developer.android.com/static/images/tv/app-browse.png?authuser=5&hl=tr)
Şekil 1. Leanback örnek uygulamasının göz atma parçası, video kataloğu verilerini gösterir.
Medyaya göz atma düzeni oluştur
Leanback kullanıcı arayüzü araç setindeki BrowseSupportFragment
sınıfı, minimum düzeyde 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, aşağıdaki örnekte gösterildiği gibi bu görünümü 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. Ayrıca simge, başlık ve kategori başlıklarının etkinleştirilip etkinleştirilmediği gibi düzen parametrelerini ayarlar.
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ın stilini belirlemek 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, göz atma parçasının sağ üst köşesindeki başlık dizesini 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ğerine ayarlar.setSearchAffordanceColor()
, arama simgesinin rengini, belirtilen renk değeriyle ayarlar. Ş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 bölümü, metin görünümlerinde, video veritabanındaki satır başlıkları olan video kategorisi adlarını görüntüler. 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 görüntüleyen resim görünümünün nasıl ekleneceği gösterilmektedir.
![Uygulama ana ekranı](https://developer.android.com/static/images/tv/custom-head.png?authuser=5&hl=tr)
Şekil 2. Göz atma parçasındaki, hem simge hem de metin etiketiyle birlikte yer alan 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>
Görünüm sahibini oluşturmak, bağlamak ve bağlantısını kaldırmak için Presenter
kullanın ve soyut yöntemleri uygulayın. Aşağıdaki örnekte, iki görünümle (ImageView
ve TextView
) görünüm sahibini nasıl bağlayacağınız 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 kaydırarak aralarından geçiş yapabilmesi için başlıklarınızın odaklanılabilir olması gerekir. Bunu yönetmenin iki yolu vardır:
onBindViewHolder()
ürününde görünümünüzü odaklanılabilir olacak ş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österen 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 göz atın.
Üstbilgileri gizleme veya devre dışı bırakma
Kaydırılabilir bir liste gerektirecek kadar kategori olmaması gibi bazı durumlarda satır başlıklarının görünmesini istemezsiniz. Satır başlıklarını 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, parametre olarak aşağıdaki sabit değerlerden biri verildiğinde göz atma parçasındaki üstbilgilerin ilk durumunu ayarlar:
HEADERS_ENABLED
: Göz atma parçası etkinliği oluşturulduğunda üst bilgiler etkinleştirilir ve varsayılan olarak gösterilir. Üstbilgiler, bu sayfadaki şekil 1 ve 2'de gösterildiği gibidir.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 bir şekilde gösterildiği gibi daraltılmıştı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 üst bilgiler varsayılan olarak devre dışı bırakılır ve hiçbir zaman görüntülenmez.
HEADERS_ENABLED
veya HEADERS_HIDDEN
ayarlanmışsa satırdaki seçili içerik öğesinden satır başlığına geri dönmeyi desteklemek için setHeadersTransitionOnBackEnabled()
numarasını çağırabilirsiniz. Yöntemi çağırmazsanız bu özellik varsayılan olarak etkinleştirilir. Geri hareketi kendiniz yönetmek için false
öğesini setHeadersTransitionOnBackEnabled()
adresine iletin ve kendi sırt yığını işlemenizi uygulayın.
Medya listelerini görüntüle
BrowseSupportFragment
sınıfı, bağdaştırıcıları ve sunucuları kullanarak bir medya kataloğundaki göz atılabilir medya içeriği kategorilerini ve medya öğelerini tanımlamanıza ve 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.
Bağdaştırıcılar, görünüm oluşturmak ve ekranda bir öğe görüntülemek amacıyla 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 bir bağdaştırıcı oluşturabilir ve kullanıcının göz atabilmesi için bu öğeleri ekranda görüntülemek üzere bağdaştırıcıyı BrowseSupportFragment
özelliğine bağlayabilirsiniz. Aşağıdaki örnek kod, önceki kod örneğinde gösterilen StringPresenter
sınıfını kullanarak bu kategorilerdeki kategorileri ve öğeleri görüntülemek için bağdaştırıcının nasıl oluşturulacağını göstermektedir:
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ı, online 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 göz atın.
Arka planı güncelle
TV'deki bir medya tarama uygulamasına görsel ilgi eklemek için kullanıcılar içeriklere göz atarken arka plan resmini güncelleyebilirsiniz. Bu teknik, uygulamanızla etkileşimi daha sinematik ve eğlenceli 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ğ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 o anki 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 nasıl OnItemViewSelectedListener
sınıfı 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. Kullanıcıların öğeler arasında gezinmesine yanıt olarak arka planı güncellemeyi planlıyorsanız, kullanıcı bir öğeye karar verene kadar arka plan resminin güncellenmesini geciktirecek bir süre ekleyin. Bu teknik, arka plan resminin çok fazla güncellenmesini önler.