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

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

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

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

  1. No Android Studio, abra o arquivo CatalogBrowser.kt do código inicial e adicione uma função combinável TvLazyColumn à função combinável CatalogBrowser.
  2. Chame o método catalogBrowserViewModel.categoryList.collectAsStateWithLifeCycle() 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 combinável Text com 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()
    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 combinável TvLazyRow e transmita uma lambda a ela.
  2. Na lambda, chame a função items com o valor de atributo category.movieList e, em seguida, transmita uma lambda a ela.
  3. Na lambda transmitida à função items, chame a função combinável MovieCard com um 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()
    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 combinável 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 combinável TvLazyRow com o parâmetro horizontalArrangement.
  3. Para definir um recuo na coluna, transmita um objeto PaddingValue com o 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()
    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 combinável Details no arquivo Details.kt. Adicione o código a essa função para implementar a tela de detalhes.

Details.kt

@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 combinável Column e defina a área livre vertical como 32 dp e a horizontal como 48 dp ao redor da coluna com o objeto Modifier criado pelo método Modifier.padding.
  2. Adicione uma função combinável Text para mostrar o título do filme.
  3. Adicione uma função combinável Text para mostrar o nome do estúdio.
  4. Adicione uma função combinável Text para mostrar a descrição do filme.

Details.kt

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

  1. Adicione uma função combinável Box como um wrapper da função combinável Column com o objeto modifier transmitido pela função combinável Details.
  2. Na função combinável Box, chame o método fillMaxSize do objeto modifier para que a função combinável Box preencha o tamanho máximo que pode ser alocado para a função combinável Details.
  3. Adicione uma função combinável AsyncImage à função Box com os parâmetros abaixo:
  • 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.

Details.kt

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

  1. Defina a propriedade MaterialTheme.typography.displayMedium como o estilo de texto do título do filme.
  2. Defina a propriedade MaterialTheme.typography.bodySmall como o estilo de texto da segunda função combinável Text.
  3. Defina a propriedade MaterialTheme.colorScheme.background como a cor de fundo da função Column com o método Modifier.background.

Details.kt

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

  1. Defina a função combinável Box para usar todo o espaço disponível com o modificador fillMaxSize.
  2. Defina o segundo plano da função combinável Box com o modificador background para preencher o segundo plano com um gradiente linear criado ao chamar a função Brush.linearGradient com uma lista de objetos Color contendo o valor MaterialTheme.colorScheme.background e Color.Transparent
  3. Defina a área livre horizontal 48.dp e vertical 24.dp ao redor da função combinável Column com o modificador padding
  4. Defina a largura da função combinável Column com o modificador width, que é criado chamando a função Modifier.width com o valor 0.5f.
  5. Adicione o espaço 8.dp entre a segunda função combinável Text e o terceiro elemento Text combinável usando Spacer. A altura da função combinável Spacer é especificada com o modificador height, que é criado com a função Modifier.height.

Details.kt

@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()
        )
        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.

  1. Abra o arquivo com.example.tvcomposeintroduction.ui.screens.CatalogBrowser.
  2. Transmita uma função lambda à função combinável MovieCard com um parâmetro onClick.
  3. Chame o callback onMovieSelected com o objeto do filme associado à função combinável MovieCard.

CatalogBrowser.kt

@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = hiltViewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
    val categoryList by
    catalogBrowserViewModel.categoryList.collectAsStateWithLifecycle()
    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 combinável TvLazyColumn.
  3. Declare featuredMovieList como uma propriedade delegada na lambda transmitida à função item e defina o objeto State a ser delegado, que é coletado do atributo catalogBrowserViewModel.featuredMovieList.
  4. Chame a função combinável Carousel dentro da função item e transmita estes parâmetros:
  • O tamanho da variável featuredMovieList por 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 combinável Carousel usa 376 dp de altura, transmitindo um valor 376.dp ao método Modifier.height.
  • Uma lambda chamada com um valor inteiro que indica o índice do item do carrossel visível.
  1. Extraia o objeto Movie da variável featuredMovieList e do valor de índice fornecido.
  2. Adicione uma função combinável Box à função combinável Carousel.
  3. Adicione uma função combinável Text à função combinável Box para 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()
    TvLazyColumn(
        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)
            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 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:

  1. Chame a função combinável AsyncImage para carregar a imagem de plano de fundo associada ao objeto Movie antes da função combinável Text.
  2. Atualize a posição e o estilo do texto da função combinável Text para melhorar a visibilidade.
  3. Defina um marcador de posição para a função combinável 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

@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = hiltViewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
    val categoryList by
    catalogBrowserViewModel.categoryList.collectAsStateWithLifecycle()
    TvLazyColumn(
        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)
            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

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

  1. Chame a função combinável Column no elemento combinável Box no combinável Carousel
  2. Mova o elemento combinável Text no Carousel para a função combinável Column.
  3. Chame a função combinável Button depois da função Text na função Column.
  4. Chame a função combinável Text na função Button com o valor de retorno da função stringResource, chamada com R.string.show_details.
  5. Chame a função onMovieSelected com a variável featuredMovie na lambda transmitida ao parâmetro onClick da função combinável Button.

CatalogBrowser.kt

@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = hiltViewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
    val categoryList by
    catalogBrowserViewModel.categoryList.collectAsStateWithLifecycle()
    TvLazyColumn(
        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)
            TvLazyRow(
                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:

  1. Atribua o valor backgroundColor com o valor MaterialTheme.colorScheme.background na função combinável Carousel.
  2. Unir a função combinável Column a um elemento combinável Box
  3. Transmita o valor Alignment.BottomStart ao parâmetro contentAlignment do componente Box.
  4. Transmita o modificador fillMaxSize ao parâmetro modificador da função combinável Box. O modificador fillMaxSize é criado com a função Modifier.fillMaxSize().
  5. Chame o método drawBehind() no modificador fillMaxSize transmitido ao elemento combinável Box.
  6. Na lambda transmitida ao modificador drawBehind, atribua o valor brush com um objeto Brush que é criado ao chamar a função Brush.linearGradient com uma lista de dois objetos Color. A lista é criada chamando a função listOf com os valores backgroundColor e Color.Transparent.
  7. Chame drawRect com o objeto brush na lambda transmitida ao modificador drawBehind para criar uma camada srim sobre a imagem de plano de fundo
  8. Especifique o padding da função combinável Column com o modificador padding, que é criado chamando Modifier.padding com o valor 20.dp.
  9. Adicione uma função combinável Spacer com o valor 20.dp entre os elementos combináveis Text e Button na função combinável Column.

CatalogBrowser.kt

@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = hiltViewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
    val categoryList by catalogBrowserViewModel.categoryList.collectAsStateWithLifecycle()
    TvLazyColumn(
        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)
            TvLazyRow(
                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

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.