Katalogbrowser erstellen

Besser lernen mit der Funktion „Schreiben“
Mit Jetpack Compose für Android TV OS lassen sich mit nur minimalem Code ansprechende UIs erstellen.
<ph type="x-smartling-placeholder"></ph> Für TV verfassen →

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

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.

Weitere Informationen zum Einrichten von UI-Elementen finden Sie unter Benutzeroberfläche festlegen Elemente. Weitere Informationen zum Ausblenden der Überschriften finden Sie in der Kopfzeilen ausblenden oder deaktivieren

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, wenn setTitle() 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() Mit setHeadersTransitionOnBackEnabled() 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

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.