Eine Medien-App, die auf einem Fernseher ausgeführt wird, muss es den Nutzern ermöglichen, und beginne mit der Wiedergabe von Inhalten. Das Durchsuchen von Inhalten müssen einfach und intuitiv sowie visuell ansprechend und ansprechend sein.
In diesem Leitfaden wird erläutert, wie die Klassen verwendet werden, die von der androidx.leanback-Bibliothek bereitgestellt werden. , um eine Benutzeroberfläche zum Durchsuchen von Musik oder Videos aus dem Medienkatalog deiner App zu implementieren.
Hinweis:Im hier gezeigten Implementierungsbeispiel wird
BrowseSupportFragment
anstelle der eingestellten BrowseFragment
. BrowseSupportFragment
erweitert AndroidX
Fragment
Klasse,
für ein einheitliches Verhalten auf allen Geräten und Android-Versionen.
![Hauptbildschirm der App](https://developer.android.com/static/images/tv/app-browse.png?hl=de)
Abbildung 1: Das Suchfragment der Leanback-Beispielanwendung zeigt Videokatalogdaten an.
Layout zur Mediensuche erstellen
Das BrowseSupportFragment
im Leanback-UI-Toolkit
können Sie ein primäres Layout zum Durchsuchen von Kategorien und Zeilen von Medienelementen mit einem
mindestens so viel Code wie möglich. Das folgende Beispiel zeigt, wie Sie ein Layout erstellen, das ein
Objekt 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>
Diese Ansicht wird durch die Hauptaktivität der Anwendung festgelegt, wie im folgenden Beispiel gezeigt:
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); } ...
Die BrowseSupportFragment
-Methoden füllen die Ansicht mit dem
und UI-Elementen hinzufügen und Layoutparameter wie Symbol, Titel und
ob Kategorieüberschriften aktiviert sind.
Die Unterklasse der Anwendung, die die BrowseSupportFragment
implementiert
-Methoden auch Ereignis-Listener für Nutzeraktionen in den UI-Elementen eingerichtet und eine Vorbereitung
Hintergrundmanager verwenden, wie im folgenden Beispiel gezeigt:
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()); } ...
UI-Elemente festlegen
Im vorherigen Beispiel ruft die private Methode setupUIElements()
mehrere
BrowseSupportFragment
Methoden zum Gestalten des Medienkatalogbrowsers:
setBadgeDrawable()
platziert die angegebene Drawable-Ressource oben rechts im Suchfragment, wie in den Abbildungen 1 und 2 dargestellt. Bei dieser Methode wird der Titel-String durch den Drawable-Ressource, wennsetTitle()
ebenfalls aufgerufen wird. Die Drawable-Ressource muss 52 dp groß sein hoch.setTitle()
legt den Titel-String in der oberen rechten Ecke des Suchfragments fest, es sei denn,setBadgeDrawable()
wird aufgerufen.setHeadersState()
MitsetHeadersTransitionOnBackEnabled()
werden die Header ausgeblendet oder deaktiviert. Weitere Informationen finden Sie im Abschnitt Header ausblenden oder deaktivieren.setBrandColor()
Legt die Hintergrundfarbe für UI-Elemente im Suchfragment fest, insbesondere die Kopfzeile Hintergrundfarbe des Abschnitts mit dem angegebenen Farbwert.setSearchAffordanceColor()
legt die Farbe des Suchsymbols mit dem angegebenen Farbwert fest. Das Suchsymbol erscheint in der oberen linken Ecke des Fragments, wie in den Abbildungen 1 und 2 dargestellt.
Headeransichten anpassen
Das in Abbildung 1 gezeigte Suchfragment enthält die Namen der Videokategorien, Dies sind die Zeilenüberschriften in der Videodatenbank, in Textansichten. Sie können auch die um zusätzliche Ansichten in einem komplexeren Layout hinzuzufügen. In den folgenden Abschnitten wird beschrieben, wie Sie enthalten eine Bildansicht, die ein Symbol neben dem Kategorienamen anzeigt, wie in Abbildung 2 dargestellt.
![Hauptbildschirm der App](https://developer.android.com/static/images/tv/custom-head.png?hl=de)
Abbildung 2: Die Zeilenüberschriften im Suchfragment enthalten sowohl ein Symbol als auch und einer Textbeschriftung.
Das Layout für den Zeilentitel ist wie folgt definiert:
<?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
verwenden und das Tag
abstrakte Methoden zum Erstellen, Binden und Aufheben der Bindung des Ansichtsinhabers. Die folgenden
Das Beispiel zeigt, wie Sie den Ansichtsinhaber mit zwei Ansichten binden, eine
ImageView
und 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 } }
Ihre Überschriften müssen fokussierbar sein, damit das Steuerkreuz durch sie scrollen. Dafür gibt es zwei Möglichkeiten:
- Stellen Sie Ihre Ansicht in
onBindViewHolder()
so ein, dass sie fokussierbar ist: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 // ... }
- Legen Sie das Layout so fest, dass es fokussierbar ist:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" ... android:focusable="true">
Schließlich müssen Sie in der BrowseSupportFragment
-Implementierung, die das Objekt
Katalogbrowser ist, verwenden Sie den setHeaderPresenterSelector()
-Methode zum Festlegen des Vortragenden für den Zeilenkopf, wie im folgenden Beispiel gezeigt.
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(); } });
Ein vollständiges Beispiel finden Sie in der <ph type="x-smartling-placeholder"></ph> Leanback-Beispiel-App .
Header ausblenden oder deaktivieren
Manchmal möchten Sie nicht, dass die Zeilenüberschriften angezeigt werden, z. B. wenn nicht genügend
die eine scrollbare Liste erfordern. BrowseSupportFragment.setHeadersState()
aufrufen
während des onActivityCreated()
-Vorgangs des Fragments
, um die Zeilenüberschriften auszublenden oder zu deaktivieren. Das setHeadersState()
-Methode legt den Anfangszustand der Header im Suchfragment fest, sofern eines der folgenden Elemente vorhanden ist:
als Parameter verwenden:
HEADERS_ENABLED
: Wenn die Aktivität zum Durchsuchen des Fragments erstellt wird, werden Header aktiviert und durch Standardeinstellung. Die Überschriften werden in den Abbildungen 1 und 2 auf dieser Seite dargestellt.HEADERS_HIDDEN
: Wenn die Aktivität zum Durchsuchen des Fragments erstellt wird, sind Header standardmäßig aktiviert und ausgeblendet. Der Header-Bereich des Bildschirms ist minimiert, wie in gezeigt. eine Zahl in Kartenansicht bereitstellen. Die Nutzer können den minimierten Kopfzeilenbereich auswählen, um ihn zu maximieren.HEADERS_DISABLED
: Beim Erstellen der Aktivität zum Durchsuchen des Fragments sind Header standardmäßig deaktiviert und werden nie angezeigt.
Wenn entweder HEADERS_ENABLED
oder HEADERS_HIDDEN
festgelegt ist, können Sie
setHeadersTransitionOnBackEnabled()
, damit Sie von einem ausgewählten Inhaltselement in der Zeile zurück zum Zeilentitel wechseln können. Dies wird durch
wenn Sie die Methode nicht aufrufen. Um die Rückwärtsbewegung selbst zu steuern,
false
an setHeadersTransitionOnBackEnabled()
übergeben
und implementieren Sie Ihren eigenen Back-Stack-Umgang.
Medienlisten anzeigen
Das BrowseSupportFragment
Class können Sie
Durchsuchbare Medieninhaltskategorien und Medienelemente von definieren und anzeigen
mit Adaptern und Vortragenden
einen Medienkatalog erstellen. Mit Adaptern verbinden
auf lokale oder Online-Datenquellen verweisen, die Informationen aus Ihrem Medienkatalog enthalten.
Adapter verwenden Presenter zum Erstellen von Ansichten und Binden von Daten an diese Ansichten für
ein Element auf dem Bildschirm anzuzeigen.
Der folgende Beispielcode zeigt eine Implementierung einer Presenter
zum Anzeigen von Stringdaten:
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 } }
Sobald Sie eine Vortragenden-Klasse für Ihre Medienelemente erstellt haben, können Sie
und verbinden Sie es mit dem BrowseSupportFragment
um dem Nutzer diese Elemente auf dem Bildschirm anzuzeigen. Im folgenden Beispiel
Code, der zeigt, wie ein Adapter zum Anzeigen von Kategorien und Artikeln erstellt wird
in diesen Kategorien mithilfe der Klasse StringPresenter
aus der
vorheriges Codebeispiel:
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); }
Dieses Beispiel zeigt eine statische Implementierung der Adapter. Eine typische Anwendung zum Durchsuchen von Medien Daten aus einer Online-Datenbank oder einem Webdienst verwendet. Ein Beispiel für eine Browseranwendung, die aus dem Web abgerufene Daten verwendet, siehe <ph type="x-smartling-placeholder"></ph> Leanback-Beispiel-App .
Hintergrund aktualisieren
Wenn Sie eine App zum Surfen in Medien auf dem Fernseher visuell ansprechender gestalten möchten, können Sie den Hintergrund aktualisieren wenn Nutzende Inhalte durchstöbern. Mit dieser Technik können Sie die Interaktion mit Ihrer App wie im Kino und angenehm zu nutzen.
Das Leanback-UI-Toolkit bietet ein BackgroundManager
zum Ändern des Hintergrunds Ihrer TV-App-Aktivitäten. Das folgende Beispiel zeigt, wie Sie
eine einfache Methode zum Aktualisieren des Hintergrunds innerhalb Ihrer TV-App-Aktivitäten erstellen:
Kotlin
protected fun updateBackground(drawable: Drawable) { BackgroundManager.getInstance(this).drawable = drawable }
Java
protected void updateBackground(Drawable drawable) { BackgroundManager.getInstance(this).setDrawable(drawable); }
Viele Apps zum Suchen von Medien aktualisieren den Hintergrund automatisch, während der Nutzer navigiert
über Medieneinträge. Dazu können Sie einen Auswahl-Listener einrichten,
aktualisiert den Hintergrund basierend auf der aktuellen Auswahl des Nutzers. Das folgende Beispiel zeigt,
um einen OnItemViewSelectedListener
-Kurs einzurichten, um
Auswahlereignisse erfassen und Hintergrund aktualisieren:
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(); } } }; }
Hinweis:Die vorherige Implementierung ist ein einfaches Beispiel für Illustration. Wenn Sie diese Funktion in Ihrer eigenen App erstellen, führen Sie den für eine bessere Leistung in einem separaten Thread. Wenn Sie den Hintergrund aktualisieren, wenn Nutzende durch die Artikel scrollen, fügen Sie Eine Zeit, die die Aktualisierung eines Hintergrundbilds verzögert, bis der Nutzer sich für einen Artikel entschieden hat. Mit dieser Technik wird verhindert, übermäßig viele Updates von Hintergrundbildern.