Introducción a Compose para TV

1. Antes de comenzar

Compose para TV es el framework de IU más reciente para desarrollar apps que se ejecutan en Android TV. Desbloquea todos los beneficios de Jetpack Compose para apps para TV, lo que facilita la compilación de IU atractivas y funcionales para tu app. Estos son algunos de los beneficios específicos de Compose para TV:

  • Flexibilidad: Compose se puede usar para crear cualquier tipo de IU, desde diseños simples hasta animaciones complejas. Los componentes funcionan de inmediato, pero también se pueden personalizar y adaptar a las necesidades de tu app.
  • Desarrollo simplificado y acelerado: Compose es compatible con el código existente y permite que los desarrolladores compilen apps con menos código.
  • Intuición: Compose usa una sintaxis declarativa que le permite cambiar tu IU de forma intuitiva, así como depurar, comprender y revisar el código.

Un caso de uso común para las apps para TV es el consumo de contenido multimedia. Los usuarios exploran los catálogos de contenido y seleccionan el contenido que quieren mirar. Ese contenido puede ser una película, un programa de TV o un podcast. Después de que los usuarios seleccionen contenido, es posible que quieran ver más información sobre este, como una descripción breve, la duración de la reproducción y los nombres de los creadores. En este codelab, aprenderás a implementar una pantalla de navegador de catálogos y una pantalla de detalles con Compose para TV.

Requisitos previos

  • Tener experiencia con la sintaxis de Kotlin, incluidas las funciones de lambdas
  • Tener experiencia básica con Compose (si no estás familiarizado con Compose, completa el codelab Conceptos básicos de Jetpack Compose)
  • Tener conocimientos básicos de elementos componibles y modificadores

Qué compilarás

  • Una app de reproducción de video con una pantalla de navegador de catálogos y una pantalla de detalles
  • Una pantalla de navegador de catálogos que muestra una lista de videos para que los usuarios elijan; se ve como la siguiente imagen:

El navegador del catálogo muestra una lista de películas destacadas\ncon un carrusel en la parte superior.\nLa pantalla también muestra una lista de películas para cada categoría.

  • Una pantalla de detalles que muestra los metadatos de un video seleccionado, como el título, la descripción y la duración; se ve como la siguiente imagen:

La pantalla de detalles muestra los metadatos de la película,\nincluidos el título, el estudio y una descripción breve.\nLos metadatos se muestran en la imagen de fondo asociada con la película.

Requisitos

2. Prepárate

Para obtener el código que contiene los temas y la configuración básica de este codelab, realiza una de las siguientes acciones:

$ git clone https://github.com/android/tv-codelabs.git

La rama main contiene el código de partida, y la rama solution incluye el código de la solución.

  • Descarga el archivo main.zip, que contiene el código de partida, y el archivo solution.zip, que contiene el código de la solución.

Ahora que descargaste el código, abre la carpeta del proyecto IntroductionToComposeForTV en Android Studio. Ya está todo listo para comenzar.

3. Implementa la pantalla del navegador de catálogos

La pantalla del navegador de catálogos les permite a los usuarios explorar catálogos de películas. Implementarás el navegador de catálogos como una función Composable. Puedes encontrar la función Composable CatalogBrowser en el archivo CatalogBrowser.kt. Implementarás la pantalla de navegador de catálogos en esta función Composable.

El código de partida tiene un ViewModel llamado clase CatalogBrowserViewModel que tiene varios atributos y métodos para recuperar objetos Movie que describen contenido de películas. Implementarás un navegador de catálogos con objetos Movie recuperados.

CatalogBrowser.kt

import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.tv.material3.ExperimentalTvMaterial3Api
import com.example.tvcomposeintroduction.data.Movie

@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = viewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
}

Cómo mostrar los nombres de las categorías

Puedes acceder a una lista de categorías con el atributo catalogBrowserViewModel.categoryList, que es un flujo de una lista de Category. El flujo se recopila como un objeto State de Compose llamando a su método collectAsState. Un objeto Category tiene el atributo name, que es un valor String que representa el nombre de la categoría.

Para mostrar los nombres de las categorías, sigue estos pasos:

  1. En Android Studio, abre el archivo CatalogBrowser.kt del código de partida y, luego, agrega una función Composable TvLazyColumn a la función Composable CatalogBrowser.
  2. Llama al método catalogBrowserViewModel.categoryList.collectAsState() para recopilar el flujo como un objeto State.
  3. Declara categoryList como una propiedad delegada del objeto State que creaste en el paso anterior.
  4. Llama a la función items con la variable categoryList como parámetro.
  5. Llama a la función Composable Text con el nombre de la categoría como el parámetro que se pasa como argumento de la lambda.

CatalogBrowser.kt

import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.tv.foundation.lazy.list.TvLazyColumn
import androidx.tv.foundation.lazy.list.items
import androidx.tv.material3.ExperimentalTvMaterial3Api
import androidx.tv.material3.Text
import com.example.tvcomposeintroduction.data.Movie

@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = viewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
    val categoryList by
    catalogBrowserViewModel.categoryList.collectAsState()
    TvLazyColumn(modifier = modifier) {
        items(categoryList) { category ->
            Text(text = category.name)
        }
    }
}

Cómo mostrar la lista de contenido de cada categoría

Un objeto Category tiene otro atributo llamado movieList. El atributo es una lista de objetos Movie que representan películas que pertenecen a la categoría.

Para mostrar la lista de contenido de cada categoría, sigue estos pasos:

  1. Agrega la función Composable TvLazyRow y, luego, pasa una lambda a ella.
  2. En la expresión lambda, llama a la función items con el valor del atributo category.movieList y, luego, pasa una lambda a ella.
  3. En la lambda pasada a la función items, llama a la función Composable MovieCard con un objeto Movie.

CatalogBrowser.kt

import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.tv.foundation.lazy.list.TvLazyColumn
import androidx.tv.foundation.lazy.list.TvLazyRow
import androidx.tv.foundation.lazy.list.items
import androidx.tv.material3.ExperimentalTvMaterial3Api
import androidx.tv.material3.Text
import com.example.tvcomposeintroduction.data.Movie
import com.example.tvcomposeintroduction.ui.components.MovieCard

@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = viewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
    val categoryList by
    catalogBrowserViewModel.categoryList.collectAsState()
    TvLazyColumn(modifier = modifier) {
        items(categoryList) { category ->
            Text(text = category.name)
            TvLazyRow {
                items(category.movieList) {movie ->
                    MovieCard(movie = movie)
                }
            }
        }
    }
}

Cómo ajustar el diseño (opcional)

  1. Para establecer la brecha entre las categorías, pasa un objeto Arrangement a la función Composable TvLazyColumn con el parámetro verticalArrangement. El objeto Arrangement se crea llamando al método Arrangement#spacedBy.
  2. Para establecer la brecha entre las tarjetas de películas, pasa un objeto Arrangement a la función Composable TvLazyRow con el parámetro horizontalArrangement.
  3. Para establecer una sangría en la columna, pasa un objeto PaddingValue con el parámetro contentPadding.

CatalogBrowser.kt

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.tv.foundation.lazy.list.TvLazyColumn
import androidx.tv.foundation.lazy.list.TvLazyRow
import androidx.tv.foundation.lazy.list.items
import androidx.tv.material3.ExperimentalTvMaterial3Api
import androidx.tv.material3.Text
import com.example.tvcomposeintroduction.data.Movie
import com.example.tvcomposeintroduction.ui.components.MovieCard

@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = viewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
    val categoryList by
    catalogBrowserViewModel.categoryList.collectAsState()
    TvLazyColumn(
        modifier = modifier,
        verticalArrangement = Arrangement.spacedBy(16.dp),
        contentPadding = PaddingValues(horizontal = 48.dp, vertical = 32.dp)
    ) {
        items(categoryList) { category ->
            Text(text = category.name)
            TvLazyRow(
                horizontalArrangement = Arrangement.spacedBy(8.dp)
            ) {
                items(category.movieList) { movie ->
                    MovieCard(movie = movie)
                }
            }
        }
    }
}

4. Implementa la pantalla de detalles

La pantalla de detalles muestra los detalles de la película seleccionada. Hay una función Composable Details en el archivo Details.kt. Agregarás código a esta función para implementar la pantalla de detalles.

Details.kt

import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.tv.material3.ExperimentalTvMaterial3Api
import com.example.tvcomposeintroduction.data.Movie

@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun Details(movie: Movie, modifier: Modifier = Modifier) {
}

Cómo mostrar el título de la película, el nombre del estudio y la descripción

Un objeto Movie tiene los siguientes tres atributos de cadena como metadatos de la película:

  • title es el título de la película.
  • studio es el nombre del estudio que produjo la película.
  • description es un breve resumen de la película.

Para mostrar estos metadatos en la pantalla de detalles, sigue estos pasos:

  1. Agrega una función Composable Column y, luego, establece el espacio horizontal y vertical alrededor de la columna en 48 dp y 32 dp, respectivamente, con el objeto Modifier creado por el método Modifier.padding.
  2. Agrega una función Composable Text para mostrar el título de la película.
  3. Agrega una función Composable Text para mostrar el nombre del estudio.
  4. Agrega una función Composable Text para mostrar la descripción de la película.

Details.kt

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.tv.material3.ExperimentalTvMaterial3Api
import androidx.tv.material3.Text
import com.example.tvcomposeintroduction.data.Movie

@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun Details(movie: Movie, modifier: Modifier = Modifier) {
    Column(
        modifier = Modifier
            .padding(vertical = 32.dp, horizontal = 48.dp)
    ) {
        Text(text = movie.title)
        Text(text = movie.studio)
        Text(text = movie.title)
    }
}

En la siguiente tarea, usarás el objeto Modifier especificado en el parámetro de la función Composable Details.

Cómo mostrar la imagen de fondo asociada a un objeto Movie determinado.

Un objeto Movie tiene un atributo backgroundImageUrl que indica la ubicación de la imagen de fondo de la película que describe el objeto.

Para mostrar la imagen de fondo de una película determinada, sigue estos pasos:

  1. Agrega una función Composable Box como un wrapper de la función Composable Column con el objeto modifier pasado con la función Composable Details.
  2. En la función Composable Box, llama al método fillMaxSize del objeto modifier para que la función Composable Box rellene el tamaño máximo que se puede asignar a la función Composable Details.
  3. Agrega una función Composable AsyncImage con los siguientes parámetros a la función Composable Box:
  • Establece el valor del atributo backgroundImageUrl del objeto Movie determinado en un parámetro model.
  • Pasa null a un parámetro contentDescription.
  • Pasa un objeto ContentScale.Crop a un parámetro contentScale. Para ver las diferentes opciones de ContentScale, consulta Escala de contenido.
  • Pasa el valor que se muestra del método Modifier.fillMaxSize al parámetro modifier.
  • Establece un espacio vertical de 32 dp y un espacio horizontal de 48 dp en la columna a través de la configuración de un objeto Modifier que se creó llamando al método Modifier.padding.

Details.kt

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp
import androidx.tv.material3.ExperimentalTvMaterial3Api
import androidx.tv.material3.Text
import coil.compose.AsyncImage
import com.example.tvcomposeintroduction.data.Movie

@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun Details(movie: Movie, modifier: Modifier = Modifier) {
    Box(modifier = modifier.fillMaxSize()) {
        AsyncImage(
            model = movie.cardImageUrl,
            contentDescription = null,
            contentScale = ContentScale.Crop,
            modifier = Modifier.fillMaxSize()
        )
        Column(
            modifier = Modifier
                .padding(vertical = 32.dp, horizontal = 48.dp)
        ) {
            Text(
                text = movie.title,
            )
            Text(
                text = movie.studio,
            )
            Text(
                text = movie.title,
            )
        }
    }
}

Cómo hacer referencia al objeto MaterialTheme para obtener temas coherentes

El objeto MaterialTheme contiene funciones que hacen referencia a los valores de tema actuales, como los de las clases Typography y [ColorScheme][ColorScheme].

Para hacer referencia al objeto MaterialTheme y así obtener temas coherentes, sigue estos pasos:

  1. Establece la propiedad MaterialTheme.typography.headlineLarge en el estilo de texto del título de la película.
  2. Establece la propiedad MaterialTheme.typography.headlineMedium en el estilo de texto de las otras dos funciones Text Composable.
  3. Establece la propiedad MaterialTheme.colorScheme.background en el color de fondo de la función Composable Column con el método Modifier.background.

[ColorScheme]: /reference/kotlin/androidx/tv/material3/ColorScheme)

Details.kt

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp
import androidx.tv.material3.ExperimentalTvMaterial3Api
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text
import coil.compose.AsyncImage
import com.example.tvcomposeintroduction.data.Movie

@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun Details(movie: Movie, modifier: Modifier = Modifier) {
    Box(modifier = modifier.fillMaxSize()) {
        AsyncImage(
            model = movie.cardImageUrl,
            contentDescription = null,
            contentScale = ContentScale.Crop,
            modifier = Modifier.fillMaxSize()
        )
        Column(
            modifier = Modifier
                .padding(vertical = 32.dp, horizontal = 48.dp)
        ) {
            Text(
                text = movie.title,
                style = MaterialTheme.typography.headlineLarge,
            )
            Text(
                text = movie.studio,
                style = MaterialTheme.typography.headlineMedium,
            )
            Text(
                text = movie.title,
                style = MaterialTheme.typography.headlineMedium,
            )
        }
    }
}

5. Agrega navegación entre las pantallas

Ya cuentas con las pantallas de detalles y del navegador de catálogos. Después de que un usuario selecciona contenido en la pantalla del navegador de catálogos, la pantalla debe pasar a la de detalles. Para que esto sea posible, usa el modificador clickable para agregar un objeto de escucha event a la función Composable MovieCard. Cuando se presiona el botón central del mando de dirección, se llama al método CatalogBrowserViewModel#showDetails con el objeto de película asociado con la función Composable MovieCard como argumento.

  1. Abre el archivo com.example.tvcomposeintroduction.ui.screens.CatalogBrowser.
  2. Pasa una función lambda a la función Composable MovieCard con un parámetro onClick.
  3. Llama a la devolución de llamada onMovieSelected con el objeto de película asociado con la función Composable MovieCard.

CatalogBrowser.kt

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.tv.foundation.lazy.list.TvLazyColumn
import androidx.tv.foundation.lazy.list.TvLazyRow
import androidx.tv.foundation.lazy.list.items
import androidx.tv.material3.ExperimentalTvMaterial3Api
import androidx.tv.material3.Text
import com.example.tvcomposeintroduction.data.Movie
import com.example.tvcomposeintroduction.ui.components.MovieCard

@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = viewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
    val categoryList by
    catalogBrowserViewModel.categoryList.collectAsState()
    TvLazyColumn(
        modifier = modifier,
        verticalArrangement = Arrangement.spacedBy(16.dp),
        contentPadding = PaddingValues(horizontal = 48.dp, vertical = 32.dp)
    ) {
        items(categoryList) { category ->
            Text(text = category.name)
            TvLazyRow(
                horizontalArrangement = Arrangement.spacedBy(8.dp)
            ) {
                items(category.movieList) { movie ->
                    MovieCard(movie = movie, onClick = { onMovieSelected(movie) })
                }
            }
        }
    }
}

6. Agrega un carrusel a la pantalla del navegador de catálogos para mostrar contenido destacado

El carrusel es un componente de IU de adaptación común que actualiza automáticamente sus diapositivas al cabo de una duración específica. Por lo general, se usa para mostrar contenido destacado.

Si quieres agregar un carrusel a la pantalla del navegador de catálogos para mostrar películas en la lista de contenido destacado, sigue estos pasos:

  1. Abre el archivo com.example.tvcomposeintroduction.ui.screens.CatalogBrowser.
  2. Llama a la función item para agregar un elemento a la función Composable TvLazyColumn.
  3. Declara featuredMovieList como una propiedad delegada en la expresión lambda pasada a la función item y, luego, configura el objeto State que se delegará, que se recopilará del atributo catalogBrowserViewModel.featuredMovieList.
  4. Llama a la función Composable Carousel dentro de la función item y, luego, pasa los siguientes parámetros:
  • El tamaño de la variable featuredMovieList mediante un parámetro slideCount
  • Un objeto Modifier para especificar el tamaño del carrusel con los métodos Modifier.fillMaxWidth y Modifier.height (la función Composable Carousel usa 376 dp de altura pasando un valor 376.dp al método Modifier.height)
  • Una lambda llamada con un valor de número entero que indica el índice del elemento visible del carrusel
  1. Recupera el objeto Movie de la variable featuredMovieList y el valor del índice determinado.
  2. Agrega una función Composable CarouselSlide a la función Composable Carousel.
  3. Agrega una función Composable Text a la función Composable CarouselSlide para mostrar el título de la película.

CatalogBrowser.kt

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.tv.foundation.lazy.list.TvLazyColumn
import androidx.tv.foundation.lazy.list.TvLazyRow
import androidx.tv.foundation.lazy.list.items
import androidx.tv.material3.Carousel
import androidx.tv.material3.ExperimentalTvMaterial3Api
import androidx.tv.material3.Text
import com.example.tvcomposeintroduction.data.Movie
import com.example.tvcomposeintroduction.ui.components.MovieCard

@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = viewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
    val categoryList by
    catalogBrowserViewModel.categoryList.collectAsState()
    TvLazyColumn(
        modifier = modifier,
        verticalArrangement = Arrangement.spacedBy(16.dp),
        contentPadding = PaddingValues(horizontal = 48.dp, vertical = 32.dp)
    ) {
        item {
            val featuredMovieList by catalogBrowserViewModel.featuredMovieList.collectAsState()
            Carousel(
                slideCount = featuredMovieList.size,
                modifier = Modifier
                    .fillMaxWidth()
                    .height(376.dp)
            ) { indexOfCarouselSlide ->
                val featuredMovie =
                    featuredMovieList[indexOfCarouselSlide]
                CarouselSlide {
                    Text(text = featuredMovie.title)
                }
            }
        }
        items(categoryList) { category ->
            Text(text = category.name)
            TvLazyRow(
                horizontalArrangement = Arrangement.spacedBy(8.dp)
            ) {
                items(category.movieList) { movie ->
                    MovieCard(movie = movie, onClick = { onMovieSelected(movie) })
                }
            }
        }
    }
}

Cómo mostrar imágenes de fondo

La función Composable CarouselSlide puede tomar otra lambda para especificar cómo se muestra el fondo de la función Composable CarouselSlide.

Para mostrar imágenes de fondo, sigue estos pasos:

  1. Pasa una lambda a la función Composable CarouselSlide con el parámetro background.
  2. Llamar a la función Composable AsyncImage para cargar la imagen de fondo asociada con el objeto Movie en el fondo de la función Composable CarouselSlide.
  3. Actualiza la posición y el estilo de texto de la función Composable Text en la función Composable CarouselSlide para mejorar la visibilidad.
  4. Establece un marcador de posición en la función Composable AsyncImage para evitar el cambio de diseño. El código de partida tiene un marcador de posición como elemento de diseño al que puedes hacer referencia con R.drawable.placeholder.

CatalogBrowser.kt

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.tv.foundation.lazy.list.TvLazyColumn
import androidx.tv.foundation.lazy.list.TvLazyRow
import androidx.tv.foundation.lazy.list.items
import androidx.tv.material3.Carousel
import androidx.tv.material3.ExperimentalTvMaterial3Api
import androidx.tv.material3.Text
import coil.compose.AsyncImage
import com.example.tvcomposeintroduction.R
import com.example.tvcomposeintroduction.data.Movie
import com.example.tvcomposeintroduction.ui.components.MovieCard

@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = viewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
    val categoryList by
    catalogBrowserViewModel.categoryList.collectAsState()
    TvLazyColumn(
        modifier = modifier,
        verticalArrangement = Arrangement.spacedBy(16.dp),
        contentPadding = PaddingValues(horizontal = 48.dp, vertical = 32.dp)
    ) {
        item {
            val featuredMovieList by catalogBrowserViewModel.featuredMovieList.collectAsState()
            Carousel(
                slideCount = featuredMovieList.size,
                modifier = Modifier
                    .fillMaxWidth()
                    .height(376.dp),
            ) { indexOfCarouselItem ->
                val featuredMovie = featuredMovieList[indexOfCarouselItem]
                CarouselSlide(
                    background = {
                        AsyncImage(
                            model = featuredMovie.backgroundImageUrl,
                            contentDescription = null,
                            placeholder = painterResource(
                                id = R.drawable.placeholder
                            ),
                            contentScale = ContentScale.Crop,
                            modifier = Modifier.fillMaxSize(),
                        )
                    },
                ) {
                    Text(text = featuredMovie.title)
                }
            }
        }
        items(categoryList) { category ->
            Text(text = category.name)
            TvLazyRow(
                horizontalArrangement = Arrangement.spacedBy(8.dp)
            ) {
                items(category.movieList) { movie ->
                    MovieCard(movie = movie, onClick = { onMovieSelected(movie) })
                }
            }
        }
    }
}

Cómo agregar una transición de pantallas a la pantalla de detalles

Puedes permitir que los usuarios hagan clic en la función Composable CarouselSlide.

Para permitir que los usuarios vean los detalles de la película en el elemento visible del carrusel en la pantalla de detalles, sigue estos pasos:

  1. Pasa el valor que se muestra del método Modifier.clickable a la función Composable CarouselSlide a través del parámetro modifier.
  2. Llama a la función onMovieSelected con el objeto Movie para la función Composable CarouselSlide visible en la expresión lambda pasada al método Modifier.clickable.

CatalogBrowser.kt

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.tv.foundation.lazy.list.TvLazyColumn
import androidx.tv.foundation.lazy.list.TvLazyRow
import androidx.tv.foundation.lazy.list.items
import androidx.tv.material3.Carousel
import androidx.tv.material3.ExperimentalTvMaterial3Api
import androidx.tv.material3.Text
import coil.compose.AsyncImage
import com.example.tvcomposeintroduction.R
import com.example.tvcomposeintroduction.data.Movie
import com.example.tvcomposeintroduction.ui.components.MovieCard

@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = viewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
    val categoryList by
    catalogBrowserViewModel.categoryList.collectAsState()
    TvLazyColumn(
        modifier = modifier,
        verticalArrangement = Arrangement.spacedBy(16.dp),
        contentPadding = PaddingValues(horizontal = 48.dp, vertical = 32.dp)
    ) {
        item {
            val featuredMovieList by catalogBrowserViewModel.featuredMovieList.collectAsState()
            Carousel(
                slideCount = featuredMovieList.size,
                modifier = Modifier
                    .fillMaxWidth()
                    .height(376.dp),
            ) { indexOfCarouselItem ->
                val featuredMovie = featuredMovieList[indexOfCarouselItem]
                CarouselSlide(
                    background = {
                        AsyncImage(
                            model = featuredMovie.backgroundImageUrl,
                            contentDescription = null,
                            placeholder = painterResource(
                                id = R.drawable.placeholder
                            ),
                            contentScale = ContentScale.Crop,
                            modifier = Modifier.fillMaxSize(),
                        )
                    },
                    modifier = Modifier.clickable { onMovieSelected(featuredMovie) }
                ) {
                    Text(text = featuredMovie.title)
                }
            }
        }
        items(categoryList) { category ->
            Text(text = category.name)
            TvLazyRow(
                horizontalArrangement = Arrangement.spacedBy(8.dp)
            ) {
                items(category.movieList) { movie ->
                    MovieCard(movie = movie, onClick = { onMovieSelected(movie) })
                }
            }
        }
    }
}

7. Obtén el código de la solución

Para descargar el código de la solución de este codelab, realiza una de las siguientes acciones:

  • Haz clic en el siguiente botón para descargarlo como un archivo ZIP, luego descomprímelo y ábrelo en Android Studio.

Descargar ZIP

  • Recupéralo con Git:
$ git clone https://github.com/android/tv-codelabs.git
$ cd tv-codelabs
$ git checkout solution
$ cd IntroductionToComposeForTV

8. Felicitaciones.

¡Felicitaciones! Aprendiste los conceptos básicos de Compose para TV:

  • Cómo implementar una pantalla para mostrar una lista de contenido combinando TvLazyColumn y TvLazyRow
  • Cómo implementar una pantalla básica para mostrar detalles del contenido
  • Cómo agregar transiciones de pantalla entre las dos pantallas