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.
- Qualquer um destes dispositivos para executar o app de exemplo:- Um dispositivo Android TV
- Um dispositivo virtual Android com um perfil na categoria de definição de dispositivo de TV
 
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:

- 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:

O que é necessário
- A versão mais recente do Android Studio
- Um dispositivo Android TV ou virtual na categoria de dispositivo de TV
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:
- Clone o código deste repositório do GitHub:
$ 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 arquivosolution.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 combinável. A função combinável CatalogBrowser está no arquivo CatalogBrowser.kt. Implemente a tela do navegador de catálogo nesta função combinável.
O código inicial tem um ViewModel conhecido como a classe CatalogBrowserViewModel que tem vários atributos e métodos para extrair objetos Movie que descrevem o conteúdo do filme. Você implementa um navegador de catálogo com objetos Movie recuperados.
CatalogBrowser.kt
@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = hiltViewModel(),
    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 collectAsStateWithLifecycle dele. Um objeto Category tem o atributo name, que é um valor String que representa o nome da categoria.
Para mostrar os nomes das categorias, siga estas etapas:
- No Android Studio, abra o arquivo CatalogBrowser.ktdo código inicial e adicione uma função combinávelLazyColumnà função combinávelCatalogBrowser.
- Chame o método catalogBrowserViewModel.categoryList.collectAsStateWithLifeCycle()para coletar o fluxo como um objetoState.
- Declare categoryListcomo uma propriedade delegada do objetoStateque você criou na etapa anterior.
- Chame a função itemscom a variávelcategoryListcomo parâmetro.
- Chame a função combinável Textcom o nome da categoria como o parâmetro transmitido como um argumento da 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)
        }
    }
}
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:
- Adicione a função combinável LazyRowe transmita uma lambda a ela.
- Na lambda, chame a função itemscom o valor de atributocategory.movieListe, em seguida, transmita uma lambda a ela.
- Na lambda transmitida à função items, chame a função combinávelMovieCardcom um objetoMovie.
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)
                }
            }
        }
    }
}
Opcional: ajustar o layout
- Para definir a lacuna entre as categorias, transmita um objeto Arrangementà função combinávelLazyColumncom o parâmetroverticalArrangement. O objetoArrangementé criado chamando o métodoArrangement#spacedBy.
- Para definir a lacuna entre os cartões de filmes, transmita um objeto Arrangementà função combinávelLazyRowcom o parâmetrohorizontalArrangement.
- Para definir um recuo na coluna, transmita um objeto PaddingValuecom o parâmetrocontentPadding.
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. Implementar a tela de detalhes
A tela de detalhes mostra os detalhes do filme selecionado. Há uma função combinável Details no arquivo Details.kt. Adicione o código a essa função para implementar a tela de detalhes.
Details.kt
@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:
- Adicione uma função combinável Columne defina a área livre vertical como 32 dp e a horizontal como 48 dp ao redor da coluna com o objetoModifiercriado pelo métodoModifier.padding.
- Adicione uma função combinável Textpara mostrar o título do filme.
- Adicione uma função combinável Textpara mostrar o nome do estúdio.
- Adicione uma função combinável Textpara mostrar a descrição do filme.
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)
    }
}
O objeto Modifier especificado no parâmetro da função combinável 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:
- Adicione uma função combinável Boxcomo um wrapper da função combinávelColumncom o objetomodifiertransmitido pela função combinávelDetails.
- Na função combinável Box, chame o métodofillMaxSizedo objetomodifierpara que a função combinávelBoxpreencha o tamanho máximo que pode ser alocado para a função combinávelDetails.
- Adicione uma função combinável AsyncImageà funçãoBoxcom os parâmetros abaixo:
- Define o valor do atributo backgroundImageUrldo objetoMovieespecificado como um parâmetromodel.
- Transmita nulla um parâmetrocontentDescription.
- Transmita um objeto ContentScale.Cropa um parâmetrocontentScale. Para conferir as diferentes opções deContentScale, consulte Escala de conteúdo.
- Transmita o valor de retorno do método Modifier.fillMaxSizeao parâmetromodifier.
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)
        }
    }
}
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.
Para consultar o objeto MaterialTheme e ter uma aplicação consistente de temas, siga estas etapas:
- Defina a propriedade MaterialTheme.typography.displayMediumcomo o estilo de texto do título do filme.
- Defina a propriedade MaterialTheme.typography.bodySmallcomo o estilo de texto da segunda função combinávelText.
- Defina a propriedade MaterialTheme.colorScheme.backgroundcomo a cor de fundo da funçãoColumncom o métodoModifier.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)
        }
    }
}
Opcional: ajustar o layout
Para ajustar o layout da função combinável Details, siga estas etapas:
- Defina a função combinável Boxpara usar todo o espaço disponível com o modificadorfillMaxSize.
- Defina o segundo plano da função combinável Boxcom o modificadorbackgroundpara preencher o segundo plano com um gradiente linear criado ao chamar a funçãoBrush.linearGradientcom uma lista de objetosColorcontendo o valorMaterialTheme.colorScheme.backgroundeColor.Transparent
- Defina a área livre horizontal 48.dpe vertical24.dpao redor da função combinávelColumncom o modificadorpadding
- Defina a largura da função combinável Columncom o modificadorwidth, que é criado chamando a funçãoModifier.widthcom o valor0.5f.
- Adicione o espaçamento 8.dpentre a segunda função combinávelTexte o terceiro combinávelTextusandoSpacer. A altura da função combinávelSpaceré especificada com o modificadorheight, que é criado com a funçãoModifier.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. 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 modificador clickable para adicionar um listener event à função combinável 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 combinável MovieCard como um argumento.
- Abra o arquivo com.example.tvcomposeintroduction.ui.screens.CatalogBrowser.
- Transmita uma função lambda à função combinável MovieCardcom um parâmetroonClick.
- Chame o callback onMovieSelectedcom o objeto do filme associado à função combinávelMovieCard.
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. 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:
- Abra o arquivo com.example.tvcomposeintroduction.ui.screens.CatalogBrowser.
- Chame a função itempara adicionar um item à função combinávelLazyColumn.
- Declare featuredMovieListcomo uma propriedade delegada na lambda transmitida à funçãoiteme defina o objetoStatea ser delegado, que é coletado do atributocatalogBrowserViewModel.featuredMovieList.
- Chame a função combinável Carouseldentro da funçãoiteme transmita estes parâmetros:
- O tamanho da variável featuredMovieListpor um parâmetroslideCount.
- Um objeto Modifierpara especificar o tamanho do carrossel com os métodosModifier.fillMaxWidtheModifier.height. A função combinávelCarouselusa 376 dp de altura, transmitindo um valor376.dpao métodoModifier.height.
- Uma lambda chamada com um valor inteiro que indica o índice do item do carrossel visível.
- Extraia o objeto Movieda variávelfeaturedMovieListe do valor de índice fornecido.
- Adicione uma função combinável Boxà função combinávelCarousel.
- Adicione uma função combinável Textà função combinávelBoxpara mostrar o título do filme.
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) })
                }
            }
        }
    }
}
Mostrar imagens de plano de fundo
A função combinável Box coloca um componente em cima de outro. Consulte Conceitos básicos de layout para mais detalhes.
Para mostrar imagens de plano de fundo, siga estas etapas:
- Chame a função combinável AsyncImagepara carregar a imagem de plano de fundo associada ao objetoMovieantes da função combinávelText.
- Atualize a posição e o estilo do texto da função combinável Textpara melhorar a visibilidade.
- Defina um marcador de posição para a função combinável AsyncImagecom 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 oR.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) })
                }
            }
        }
    }
}
Adicionar uma transição de telas à tela de detalhes
É possível adicionar um Button ao carrossel para que os usuários possam acionar uma transição para a tela de detalhes clicando no botão.
Para que os usuários possam conferir os detalhes do filme no carrossel visível na tela de detalhes, siga estas etapas:
- Chame a função combinável Columnno elemento combinávelBoxno combinávelCarousel
- Mova o elemento combinável TextnoCarouselpara a função combinávelColumn.
- Chame a função combinável Buttondepois da funçãoTextna funçãoColumn.
- Chame a função combinável Textna funçãoButtoncom o valor de retorno da funçãostringResource, chamada comR.string.show_details.
- Chame a função onMovieSelectedcom a variávelfeaturedMoviena lambda transmitida ao parâmetroonClickda função combinávelButton.
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) })
                }
            }
        }
    }
}
Opcional: ajustar o layout
Para ajustar o layout do carrossel, siga estas etapas:
- Atribua o valor backgroundColorcom o valorMaterialTheme.colorScheme.backgroundna função combinávelCarousel.
- Unir a função combinável Columna um elemento combinávelBox
- Transmita o valor Alignment.BottomStartao parâmetrocontentAlignmentdo componenteBox.
- Transmita o modificador fillMaxSizeao parâmetro modificador da função combinávelBox. O modificadorfillMaxSizeé criado com a funçãoModifier.fillMaxSize().
- Chame o método drawBehind()no modificadorfillMaxSizetransmitido ao elemento combinávelBox.
- Na lambda transmitida ao modificador drawBehind, atribua o valorbrushcom um objetoBrushque é criado ao chamar a funçãoBrush.linearGradientcom uma lista de dois objetosColor. A lista é criada chamando a funçãolistOfcom os valoresbackgroundColoreColor.Transparent.
- Chame drawRectcom o objetobrushna lambda transmitida ao modificadordrawBehindpara criar uma camada srim sobre a imagem de plano de fundo
- Especifique o padding da função combinável Columncom o modificadorpadding, que é criado chamandoModifier.paddingcom o valor20.dp.
- Adicione uma função combinável Spacercom o valor20.dpentre os elementos combináveisTexteButtonna função combinávelColumn.
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. 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
- Confira no GitHub (link em inglês).
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 LazyColumn e LazyLow.
- A implementação básica de tela para mostrar detalhes do conteúdo.
- Como adicionar transições entre as duas telas.
