Introdução ao Compose para TV

1. Antes de começar

Compose para TV é o framework de interface mais recente para desenvolver apps executados no Android TV. Ele oferece todos os benefícios do Jetpack Compose para apps de TV, o que facilita a criação de interfaces incríveis e funcionais para seu app. Confira alguns benefícios específicos do Compose para TV:

  • Flexibilidade. O Compose pode ser usado para criar qualquer tipo de interface, desde layouts simples a animações complexas. Os componentes funcionam imediatamente, mas também podem ser personalizados e estilizados para atender às necessidades do seu app.
  • Desenvolvimento simplificado e acelerado. O Compose é compatível com códigos já existentes e permite que os desenvolvedores criem apps com menos código.
  • Intuição: o Compose usa uma sintaxe declarativa que torna intuitivas a mudança da interface e a depuração, compreensão e revisão do código.

Um caso de uso comum para apps de TV é o consumo de mídia. Os usuários navegam por catálogos de conteúdo e selecionam aquele que querem assistir. O conteúdo pode ser um filme, um programa de TV ou um podcast. Depois de selecionar um conteúdo, os usuários podem conferir mais informações sobre ele, por exemplo, uma descrição curta, a duração da reprodução e o nome dos criadores. Neste codelab, você vai aprender a implementar uma tela para um navegador de catálogo e uma tela de detalhes com o Compose para TV.

Pré-requisitos

  • Experiência com a sintaxe do Kotlin, incluindo lambdas.
  • Experiência básica com o Compose. Se você não conhece o Compose, conclua o codelab Noções básicas do Jetpack Compose.
  • Conhecimento básico de combináveis e modificadores.

O que você vai criar

  • Um app de player de vídeo com uma tela de navegador de catálogo e uma tela de detalhes.
  • Uma tela de navegador de catálogo que mostra uma lista de vídeos para os usuários escolherem. Ela tem a seguinte aparência:

O navegador em catálogos mostra uma lista de filmes em destaque\ncom um carrossel na parte de cima.\nA tela também mostra uma lista de filmes de cada categoria.

  • Uma tela de detalhes que mostra os metadados de um vídeo selecionado, por exemplo, título, descrição e duração. Ela tem a seguinte aparência:

A tela de detalhes mostra os metadados do filme,\nincluindo título, estúdio em que o filme foi gravado e uma breve descrição.\nOs metadados aparecem na imagem de plano de fundo associada ao filme.

O que é necessário

2. Começar a configuração

Para receber o código que contém a configuração básica e de aplicação de temas para este codelab, siga um destes procedimentos:

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

A ramificação main contém o código inicial, e a ramificação solution contém o código da solução.

  • Faça o download do arquivo main.zip, que contém o código inicial, e do arquivo solution.zip, que contém o código da solução.

Depois de fazer o download do código, abra a pasta do projeto IntroductionToComposeForTV no Android Studio. Está tudo pronto para começar.

3. Implementar a tela do navegador de catálogo

A tela do navegador de catálogo permite que os usuários procurem catálogos de filmes. Implemente a tela do navegador de catálogo como uma função Composable. A função Composable CatalogBrowser está no arquivo CatalogBrowser.kt. Implemente a tela do navegador de catálogo nesta função Composable.

O código inicial tem um ViewModel chamado classe CatalogBrowserViewModel que tem vários atributos e métodos para recuperar objetos Movie que descrevem o conteúdo do filme. Você implementa um navegador de catálogo com 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 = {}
) {
}

Mostrar os nomes das categorias

É possível acessar uma lista de categorias com o atributo catalogBrowserViewModel.categoryList, que é um fluxo de uma lista de categorias (Category). O fluxo é coletado como um objeto Compose State chamando o método collectAsState dele. Um objeto Category tem o atributo name, um valor String que representa o nome da categoria.

Para mostrar os nomes das categorias, siga estas etapas:

  1. No Android Studio, abra o arquivo CatalogBrowser.kt do código inicial e adicione uma função Composable TvLazyColumn à função Composable CatalogBrowser.
  2. Chame o método catalogBrowserViewModel.categoryList.collectAsState() para coletar o fluxo como um objeto State.
  3. Declare categoryList como uma propriedade delegada do objeto State que você criou na etapa anterior.
  4. Chame a função items com a variável categoryList como parâmetro.
  5. Chame a função Composable Text com o nome da categoria como o parâmetro transmitido como um argumento do 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)
        }
    }
}

Mostrar a lista de conteúdo de cada categoria

Um objeto Category tem outro atributo chamado movieList. O atributo é uma lista de objetos Movie que representam os filmes que pertencem à categoria.

Para mostrar a lista de conteúdos de cada categoria, siga estas etapas:

  1. Adicione a função Composable TvLazyRow e transmita um lambda a ela.
  2. No lambda, chame a função items com a category.movieList e, em seguida, transmita um lambda a ela.
  3. No lambda transmitido à função items, chame a função Composable MovieCard com um 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)
                }
            }
        }
    }
}

Opcional: ajustar o layout

  1. Para definir a lacuna entre as categorias, transmita um objeto Arrangement à função Composable TvLazyColumn com o parâmetro verticalArrangement. O objeto Arrangement é criado chamando o método Arrangement#spacedBy.
  2. Para definir a lacuna entre os cartões de filmes, transmita um objeto Arrangement à função Composable TvLazyRow com o parâmetro horizontalArrangement.
  3. Para definir um recuo na coluna, transmita um objeto PaddingValue com o 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. Implementar a tela de detalhes

A tela de detalhes mostra os detalhes do filme selecionado. Há uma função Composable Details no arquivo Details.kt. Adicione o código a essa função para implementar a tela de detalhes.

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) {
}

Mostrar o título, o nome do estúdio e a descrição do filme

Um objeto Movie tem estes três atributos de string como metadados do filme:

  • title: o título do filme.
  • studio: o nome do estúdio que produziu o filme.
  • description: um breve resumo do filme.

Para mostrar esses metadados na tela de detalhes, siga estas etapas:

  1. Adicione uma função Composable Column e defina a liberação horizontal em 32 dp e a horizontal em 48 dp ao redor da coluna com o objeto Modifier criado pelo método Modifier.padding.
  2. Adicione uma função Composable Text para mostrar o título do filme.
  3. Adicione uma função Composable Text para mostrar o nome do estúdio.
  4. Adicione uma função Composable Text para mostrar a descrição do filme.

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)
    }
}

O objeto Modifier especificado no parâmetro da função Composable Details é usado na próxima tarefa.

Mostrar a imagem de plano de fundo associada a um determinado objeto Movie

Um objeto Movie tem um atributo backgroundImageUrl que indica o local da imagem de plano de fundo do filme descrito pelo objeto.

Para mostrar a imagem de plano de fundo de um determinado filme, siga estas etapas:

  1. Adicione uma função Composable Box como um wrapper da função Composable Column com o objeto modifier transmitido pela função Composable Details.
  2. Na função Composable Box, chame o método fillMaxSize do objeto modifier para que a função Composable Box preencha o tamanho máximo que pode ser alocado para a função Composable Details.
  3. Adicione uma função Composable AsyncImage à função Composable Box com os seguintes parâmetros:
  • Define o valor do atributo backgroundImageUrl do objeto Movie especificado como um parâmetro model.
  • Transmita null a um parâmetro contentDescription.
  • Transmita um objeto ContentScale.Crop a um parâmetro contentScale. Para conferir as diferentes opções de ContentScale, consulte Escala de conteúdo.
  • Transmita o valor de retorno do método Modifier.fillMaxSize ao parâmetro modifier.
  • Configure um objeto Modifier criado chamando o método Modifier.padding para definir a liberação da coluna horizontal em 32 dp e a vertical em 48 dp.

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,
            )
        }
    }
}

Consultar o objeto MaterialTheme para obter uma aplicação consistente de temas

O objeto MaterialTheme contém funções para indicar valores de tema atuais, por exemplo, os das classes Typography e [ColorScheme][ColorScheme].

Para consultar o objeto MaterialTheme e ter uma aplicação consistente de temas, siga estas etapas:

  1. Defina a propriedade MaterialTheme.typography.headlineLarge como o estilo de texto do título do filme.
  2. Defina a propriedade MaterialTheme.typography.headlineMedium como o estilo de texto das outras duas funções Composable Text.
  3. Defina a propriedade MaterialTheme.colorScheme.background como a cor de fundo da função Composable Column com o 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. Adicionar navegação entre as telas

Agora você tem a tela do navegador de catálogo e as telas de detalhes. Depois que um usuário seleciona o conteúdo na tela do navegador de catálogo, é necessário mudar para a tela de detalhes. Para que isso seja possível, use o método clickable para adicionar um listener event à função Composable MovieCard. Quando o botão central do botão direcional é pressionado, o método CatalogBrowserViewModel#showDetails é chamado, apresentando o objeto do filme associado à função Composable MovieCard como um argumento.

  1. Abra o arquivo com.example.tvcomposeintroduction.ui.screens.CatalogBrowser.
  2. Transmita uma função lambda à função Composable MovieCard com um parâmetro onClick.
  3. Chame o callback onMovieSelected com o objeto filme associado à função 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. Adicionar um carrossel à tela do navegador de catálogo para realçar o conteúdo em destaque

O carrossel é um componente da interface comumente adaptado que atualiza automaticamente os slides após uma duração específica. É usado normalmente para realçar o conteúdo em destaque.

Para adicionar um carrossel à tela do navegador de catálogo e realçar filmes na lista de conteúdo em destaque, siga estas etapas:

  1. Abra o arquivo com.example.tvcomposeintroduction.ui.screens.CatalogBrowser.
  2. Chame a função item para adicionar um item à função TvLazyColumn Composable.
  3. Declare featuredMovieList como uma propriedade delegada no lambda transmitido à função item e defina o objeto State a ser delegado, que é coletado do atributo catalogBrowserViewModel.featuredMovieList.
  4. Chame a função Composable Carousel dentro da função item e transmita os seguintes parâmetros:
  • O tamanho da variável featuredMovieList por meio de um parâmetro slideCount.
  • Um objeto Modifier para especificar o tamanho do carrossel com os métodos Modifier.fillMaxWidth e Modifier.height. A função Composable Carousel usa 376 dp de altura, transmitindo um valor 376.dp ao método Modifier.height.
  • Um lambda chamado com um valor inteiro que indica o índice do item do carrossel visível.
  1. Recupere o objeto Movie da variável featuredMovieList e do valor de índice fornecido.
  2. Adicione uma função Composable CarouselSlide à função Composable Carousel.
  3. Adicione uma função Composable Text à função Composable CarouselSlide para mostrar o título do filme.

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) })
                }
            }
        }
    }
}

Mostrar imagens de plano de fundo

A função Composable CarouselSlide pode usar outro lambda para especificar como mostrar o plano de fundo da função Composable CarouselSlide.

Para mostrar imagens de plano de fundo, siga estas etapas:

  1. Transmita um lambda à função Composable CarouselSlide com o parâmetro background.
  2. Chame a função Composable AsyncImage para carregar a imagem de plano de fundo associada ao objeto Movie no segundo plano da função Composable CarouselSlide.
  3. Atualize a posição e o estilo do texto da função Composable Text na função Composable CarouselSlide para melhorar a visibilidade.
  4. Defina um marcador de posição para a função Composable AsyncImage com o objetivo de evitar a mudança de layout. O código inicial tem um marcador de posição como um drawable que pode ser referenciado com o 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) })
                }
            }
        }
    }
}

Adicionar uma transição de telas à tela de detalhes

Você pode permitir que os usuários cliquem na função Composable CarouselSlide.

Para que os usuários possam conferir os detalhes do filme no item de carrossel visível na tela de detalhes, siga estas etapas:

  1. Transmita o valor de retorno do método Modifier.clickable para a função CarouselSlide Composable usando o parâmetro modifier.
  2. Chame a função onMovieSelected com o objeto Movie para a função Composable CarouselSlide visível no lambda transmitido ao 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. Acessar o código da solução

Para fazer o download do código da solução para este codelab, realize uma destas ações:

  • Clique no botão a seguir para fazer o download como um arquivo ZIP. Em seguida, descompacte e abra esse arquivo no Android Studio.

  • Recupere-o com o Git:
$ git clone https://github.com/android/tv-codelabs.git
$ cd tv-codelabs
$ git checkout solution
$ cd IntroductionToComposeForTV

8. Parabéns!

Parabéns! Você aprendeu as noções básicas do Compose para TV:

  • Como implementar uma tela para mostrar uma lista de conteúdo combinando TvLazyColumn e TvLazyLow.
  • A implementação básica de tela para mostrar detalhes do conteúdo.
  • Como adicionar transições entre as duas telas.