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
  • Cualquiera de los siguientes dispositivos para ejecutar la app de ejemplo:
    • Un dispositivo Android TV
    • Un dispositivo virtual de Android con un perfil en la categoría de definición de dispositivo de TV

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

  • La versión más reciente de Android Studio
  • Un dispositivo Android TV o un dispositivo virtual en la categoría de dispositivos de TV

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 de componibilidad. Encontrarás la función de componibilidad CatalogBrowser en el archivo CatalogBrowser.kt. Implementarás la pantalla de navegador de catálogos en esta función de componibilidad.

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

@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = hiltViewModel(),
    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 collectAsStateWithLifecycle. 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 de componibilidad LazyColumn a la función de componibilidad CatalogBrowser.
  2. Llama al método catalogBrowserViewModel.categoryList.collectAsStateWithLifeCycle() 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 de componibilidad Text con el nombre de la categoría como el parámetro que se pasa como argumento de la expresión lambda.

CatalogBrowser.kt

@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = hiltViewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
    val categoryList by catalogBrowserViewModel.categoryList.collectAsStateWithLifecycle()
    LazyColumn(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 de componibilidad LazyRow 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 de componibilidad MovieCard con un objeto Movie.

CatalogBrowser.kt

@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = hiltViewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
    val categoryList by
    catalogBrowserViewModel.categoryList.collectAsStateWithLifecycle()
    LazyColumn(modifier = modifier) {
        items(categoryList) { category ->
            Text(text = category.name)
            LazyRow {
                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 de componibilidad LazyColumn 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 de componibilidad LazyRow 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

@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = hiltViewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
    val categoryList by
    catalogBrowserViewModel.categoryList.collectAsStateWithLifeCycle()
    LazyColumn(
        modifier = modifier,
        verticalArrangement = Arrangement.spacedBy(16.dp),
        contentPadding = PaddingValues(horizontal = 48.dp, vertical = 32.dp)
    ) {
        items(categoryList) { category ->
            Text(text = category.name)
            LazyRow(
                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 de componibilidad Details en el archivo Details.kt. Agregarás código a esta función para implementar la pantalla de detalles.

Details.kt

@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 de componibilidad 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 de componibilidad Text para mostrar el título de la película.
  3. Agrega una función de componibilidad Text para mostrar el nombre del estudio.
  4. Agrega una función de componibilidad Text para mostrar la descripción de la película.

Details.kt

@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.description)
    }
}

En la siguiente tarea, usarás el objeto Modifier especificado en el parámetro de la función de componibilidad 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 de componibilidad Box como wrapper de la función de componibilidad Column con el objeto modifier pasado a través de la función de componibilidad Details.
  2. En la función de componibilidad Box, llama al método fillMaxSize del objeto modifier para que la función de componibilidad Box rellene el tamaño máximo que se puede asignar a la función de componibilidad Details.
  3. Agrega una función de componibilidad AsyncImage con los siguientes parámetros a la función de componibilidad 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.

Details.kt

@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 {
            Text(
                text = movie.title,
            )
            Text(
                text = movie.studio,
            )
            Text(text = movie.description)
        }
    }
}

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.

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

  1. Establece la propiedad MaterialTheme.typography.displayMedium en el estilo de texto del título de la película.
  2. Configura la propiedad MaterialTheme.typography.bodySmall en el estilo de texto de las segundas funciones de componibilidad Text.
  3. Establece la propiedad MaterialTheme.colorScheme.background en el color de fondo de la función de componibilidad Column con el método Modifier.background.

Details.kt

@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
                .background(MaterialTheme.colorScheme.background),
        ) {
            Text(
                text = movie.title,
                style = MaterialTheme.typography.displayMedium,
            )
            Text(
                text = movie.studio,
                style = MaterialTheme.typography.bodySmall,
            )
            Text(text = movie.description)
        }
    }
}

Cómo ajustar el diseño (opcional)

Para ajustar el diseño de la función de componibilidad Details, sigue estos pasos:

  1. Configura la función de componibilidad Box para usar todo el espacio disponible con el modificador fillMaxSize.
  2. Configura el fondo de la función de componibilidad Box con el modificador background para rellenar el fondo con un gradiente lineal que se crea llamando a la función Brush.linearGradient con una lista de objetos Color que contienen el valor MaterialTheme.colorScheme.background y Color.Transparent.
  3. Configura el espacio horizontal 48.dp y vertical 24.dp alrededor de la función de componibilidad Column con el modificador padding.
  4. Configura el ancho de la función de componibilidad Column con el modificador width que se crea llamando a la función Modifier.width con el valor 0.5f.
  5. Agrega el espacio 8.dp entre la segunda función de componibilidad Text y el tercer elemento componible Text con Spacer. La altura de la función de componibilidad Spacer se especifica con el modificador height que se crea con la función Modifier.height.

Details.kt

@Composable
fun Details(movie: Movie, modifier: Modifier = Modifier) {
    Box(modifier = modifier.fillMaxSize()) {
        AsyncImage(
            model = movie.cardImageUrl,
            contentDescription = null,
            contentScale = ContentScale.Crop,
            modifier = Modifier.fillMaxSize()
        )
        Box(
            modifier = Modifier
                .background(
                    Brush.linearGradient(
                        listOf(
                            MaterialTheme.colorScheme.background,
                            Color.Transparent
                        )
                    )
                )
                .fillMaxSize()
        ) {
            Column(
                modifier = Modifier
                    .padding(horizontal = 48.dp, vertical = 24.dp)
                    .fillMaxWidth(0.5f)
            ) {
                Text(
                    text = movie.title,
                    style = MaterialTheme.typography.displayMedium,
                )
                Text(
                    text = movie.studio,
                    style = MaterialTheme.typography.bodySmall,
                )
                Spacer(modifier = Modifier.height(8.dp))
                Text(
                    text = movie.description,
                )
            }
        }
    }
}

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 de componibilidad 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 de componibilidad MovieCard como argumento.

  1. Abre el archivo com.example.tvcomposeintroduction.ui.screens.CatalogBrowser.
  2. Pasa una función lambda a la función de componibilidad 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 de componibilidad MovieCard.

CatalogBrowser.kt

@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = hiltViewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
    val categoryList by
    catalogBrowserViewModel.categoryList.collectAsStateWithLifecycle()
    LazyColumn(
        modifier = modifier,
        verticalArrangement = Arrangement.spacedBy(16.dp),
        contentPadding = PaddingValues(horizontal = 48.dp, vertical = 32.dp)
    ) {
        items(categoryList) { category ->
            Text(text = category.name)
            LazyRow(
                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 de componibilidad LazyColumn.
  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 de componibilidad Carousel dentro de la función item y, luego, pasa los siguientes parámetros:
  • El tamaño de la variable featuredMovieList a través de 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 de componibilidad 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 de componibilidad Box a la función de componibilidad Carousel.
  3. Agrega una función de componibilidad Text a la función de componibilidad Box para mostrar el título de la película.

CatalogBrowser.kt

@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = hiltViewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
    val categoryList by
    catalogBrowserViewModel.categoryList.collectAsStateWithLifecycle()
    LazyColumn(
        modifier = modifier,
        verticalArrangement = Arrangement.spacedBy(16.dp),
        contentPadding = PaddingValues(horizontal = 48.dp, vertical = 32.dp)
    ) {
        item {
            val featuredMovieList by catalogBrowserViewModel.featuredMovieList.collectAsStateWithLifecycle()
            Carousel(
                slideCount = featuredMovieList.size,
                modifier = Modifier
                    .fillMaxWidth()
                    .height(376.dp)
            ) { indexOfCarouselSlide ->
                val featuredMovie =
                    featuredMovieList[indexOfCarouselSlide]
                Box {
                    Text(text = featuredMovie.title)
                }
            }
        }
        items(categoryList) { category ->
            Text(text = category.name)
            LazyRow(
                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 de componibilidad Box coloca un componente sobre otro. Consulta los Conceptos básicos de diseño para obtener más detalles.

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

  1. Llama a la función de componibilidad AsyncImage para cargar la imagen de fondo asociada con el objeto Movie antes de la función de componibilidad Text.
  2. Actualiza la posición y el estilo de texto de la función de componibilidad Text para mejorar la visibilidad.
  3. Configura un marcador de posición en la función de componibilidad 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

@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = hiltViewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
    val categoryList by
    catalogBrowserViewModel.categoryList.collectAsStateWithLifecycle()
    LazyColumn(
        modifier = modifier,
        verticalArrangement = Arrangement.spacedBy(16.dp),
        contentPadding = PaddingValues(horizontal = 48.dp, vertical = 32.dp)
    ) {
        item {
            val featuredMovieList by catalogBrowserViewModel.featuredMovieList.collectAsStateWithLifecycle()
            Carousel(
                slideCount = featuredMovieList.size,
                modifier = Modifier
                    .fillMaxWidth()
                    .height(376.dp),
            ) { indexOfCarouselItem ->
                val featuredMovie = featuredMovieList[indexOfCarouselItem]
                Box{
                    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)
            LazyRow(
                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 agregar un objeto Button al carrusel para que los usuarios puedan hacer clic en el botón para activar una transición de pantalla a la pantalla de detalles.

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

  1. Llama a la función de componibilidad Column en el elemento Box componible, en el elemento componible Carousel.
  2. Mueve el elemento componible Text en el elemento Carousel a la función de componibilidad Column.
  3. Llama a la función de componibilidad Button después de la función de componibilidad Text en la función de componibilidad Column.
  4. Llama a la función de componibilidad Text en la función de componibilidad Button con el valor que se muestra de la función stringResource a la que se llamó con R.string.show_details.
  5. Llama a la función onMovieSelected con la variable featuredMovie en la expresión lambda pasada al parámetro onClick de la función de componibilidad Button.

CatalogBrowser.kt

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

Cómo ajustar el diseño (opcional)

Para ajustar el diseño del carrusel, sigue estos pasos:

  1. Asigna el valor backgroundColor con el valor MaterialTheme.colorScheme.background en la función de componibilidad Carousel.
  2. Une la función de componibilidad Column con un elemento componible Box.
  3. Pasa el valor Alignment.BottomStart al parámetro contentAlignment del componente Box.
  4. Pasa el modificador fillMaxSize al parámetro modificador de la función de componibilidad Box. El modificador fillMaxSize se crea con la función Modifier.fillMaxSize().
  5. Llama al método drawBehind() mediante el modificador fillMaxSize que se pasa al elemento componible Box.
  6. En la lambda pasada al modificador drawBehind, asigna el valor brush con un objeto Brush que se crea llamando a la función Brush.linearGradient con una lista de dos objetos Color. La lista se crea llamando a la función listOf con el valor backgroundColor y el valor Color.Transparent.
  7. Llama a drawRect con el objeto brush de la expresión lambda pasada al modificador drawBehind para crear una capa srim sobre la imagen de fondo.
  8. Especifica el relleno de la función de componibilidad Column con el modificador padding que se crea llamando a Modifier.padding con el valor 20.dp.
  9. Agrega una función de componibilidad Spacer con el valor 20.dp entre el elemento componible Text y el elemento componible Button en la función de componibilidad Column.

CatalogBrowser.kt

@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = hiltViewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
    val categoryList by catalogBrowserViewModel.categoryList.collectAsStateWithLifecycle()
    LazyColumn(
        modifier = modifier,
        verticalArrangement = Arrangement.spacedBy(32.dp),
        contentPadding = PaddingValues(horizontal = 58.dp, vertical = 36.dp)
    ) {
        item {
            val featuredMovieList by
            catalogBrowserViewModel.featuredMovieList.collectAsStateWithLifecycle()

            Carousel(
                itemCount = featuredMovieList.size,
                modifier = Modifier
                    .fillMaxWidth()
                    .height(376.dp),
            ) { indexOfCarouselItem ->
                val featuredMovie = featuredMovieList[indexOfCarouselItem]
                val backgroundColor = MaterialTheme.colorScheme.background

                Box {
                    AsyncImage(
                        model = featuredMovie.backgroundImageUrl,
                        contentDescription = null,
                        placeholder = painterResource(
                            id = R.drawable.placeholder
                        ),
                        contentScale = ContentScale.Crop,
                        modifier = Modifier.fillMaxSize(),
                    )
                    Box(
                        contentAlignment = Alignment.BottomStart,
                        modifier = Modifier
                            .fillMaxSize()
                            .drawBehind {
                                val brush = Brush.horizontalGradient(
                                    listOf(backgroundColor, Color.Transparent)
                                )
                                drawRect(brush)
                            }
                    ) {
                        Column(
                            modifier = Modifier.padding(20.dp)
                        ) {
                            Text(
                                text = featuredMovie.title,
                                style = MaterialTheme.typography.displaySmall
                            )
                            Spacer(modifier = Modifier.height(28.dp))
                            Button(onClick = { onMovieSelected(featuredMovie) }) {
                                Text(text = stringResource(id = R.string.show_details))
                            }
                        }
                    }
                }
            }
        }
        items(categoryList) { category ->
            Text(text = category.name)
            LazyRow(
                horizontalArrangement = Arrangement.spacedBy(16.dp),
                modifier = Modifier.height(200.dp)
            ) {
                items(category.movieList) { movie ->
                    MovieCard(
                        movie,
                        onClick = {
                            onMovieSelected(it)
                        }
                    )
                }
            }
        }
    }
}

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 LazyColumn y LazyLow
  • Cómo implementar una pantalla básica para mostrar detalles del contenido
  • Cómo agregar transiciones de pantalla entre las dos pantallas