Criar um navegador de catálogo

Um app de mídia executado em uma TV precisa permitir que os usuários procurem ofertas de conteúdo, façam uma seleção e comecem a reproduzir conteúdo. A experiência de navegação de conteúdo para apps desse tipo precisa ser simples, intuitiva, visualmente agradável e envolvente.

Esta seção descreve como usar as funções fornecidas pelo Compose para TV para implementar uma interface do usuário para navegar por músicas ou vídeos do catálogo de mídia do seu app.

Figura 1. Tela de catálogo típica. Os usuários podem navegar pelos dados do catálogo de vídeos.

Um navegador de catálogo de mídia tende a consistir em várias seções, e cada seção tem uma lista de conteúdo de mídia. Exemplos de seções em um catálogo de mídia incluem: playlists, conteúdo em destaque, categorias recomendadas

Criar uma função combinável para catálogo

Tudo que aparece em uma tela é implementado como uma função combinável no Compose para TV. Começaremos definindo uma função combinável para o navegador de catálogo de mídia, conforme o seguinte snippet:

@Composable
fun CatalogBrowser(
   featuredContentList: List<Movie>,
   sectionList: List<Section>,
   modifier: Modifier = Modifier,
   onItemSelected: (Movie) -> Unit = {},
) {
// ToDo: add implementation
}

CatalogBrowser é a função combinável que implementa o navegador de catálogo de mídia. A função usa os seguintes argumentos:

  • Lista de conteúdo em destaque.
  • Lista de seções.
  • Um objeto modificador.
  • Uma função de callback, que aciona uma transição de tela.

Definir elementos da IU

O Compose para TV oferece listas lentas, um componente para mostrar um grande número de itens (ou uma lista de tamanho desconhecido). Você vai chamar TvLazyColumn para colocar seções verticalmente. O TvLazyColumn fornece um bloco TvLazyListScope.() -> Unit, que oferece uma DSL para definir o conteúdo do item. No exemplo abaixo, cada seção é colocada em uma lista vertical com um intervalo de 16 dp entre as seções.

@Composable
fun CatalogBrowser(
   featuredContentList: List<Movie>,
   sectionList: List<Section>,
   modifier: Modifier = Modifier,
   onItemSelected: (Movie) -> Unit = {},
) {
  TvLazyColumn(
    modifier = modifier.fillMaxSize(),
    verticalArrangement = Arrangement.spacedBy(16.dp)
  ) {
    items(sectionList) { section ->
      Section(section, onItemSelected = onItemSelected)
    }
  }
}

No exemplo, a função combinável Section define como mostrar seções. Na função a seguir, TvLazyRow demonstra como essa versão horizontal de TvLazyColumn é usada de maneira semelhante para definir uma lista horizontal com um bloco TvLazyListScope.() -> Unit chamando a DSL fornecida.

@Composable
fun Section(
  section: Section,
  modifier: Modifier = Modifier,
  onItemSelected: (Movie) -> Unit = {},
) {
  Text(
    text = section.title,
    style = MaterialTheme.typography.headlineSmall,
  )
  TvLazyRow(
     modifier = modifier,
     horizontalArrangement = Arrangement.spacedBy(8.dp)
  ) {
    items(section.movieList){ movie ->
    MovieCard(
         movie = movie,
         onClick = { onItemSelected(movie) }
       )
    }
  }
}

No elemento combinável Section, o componente Text é usado. Texto e outros componentes definidos no Material Design são oferecidos na biblioteca tv-material . Para mudar o estilo dos textos, conforme definido no Material Design, consulte o objeto MaterialTheme. Esse objeto também é fornecido pela biblioteca tv-material. MovieCard define como cada dado de filme é renderizado no catálogo definido como o snippet a seguir. Card também faz parte da biblioteca de material de TV.

@Composable
fun MovieCard(
   movie: Movie,
   modifier: Modifier = Modifier,
   onClick: () -> Unit = {}
) {
   Card(modifier = modifier, onClick = onClick){
    AsyncImage(
       model = movie.thumbnailUrl,
       contentDescription = movie.title,
     )
   }
}

No exemplo descrito anteriormente, todos os filmes são exibidos igualmente. Eles têm a mesma área, sem diferença visual entre eles. É possível destacar algumas delas com Carousel.

O carrossel mostra as informações em um conjunto de itens que podem deslizar, esmaecer ou mover para a visualização. Você usa o componente para destacar conteúdo em destaque, como filmes recém-disponibilizados ou novos episódios de programas de TV.

Carousel espera que você especifique pelo menos o número de itens que o carrossel tem e como desenhar cada item. O primeiro pode ser especificado com itemCount. A segunda pode ser transmitida como um lambda. O número de índice do item exibido é fornecido à lambda. É possível determinar o item exibido com o valor de índice fornecido.

@Composable
function FeaturedCarousel(
  featuredContentList: List<Movie>,
  modifier: Modifier = Modifier,
) {
  Carousel(
    itemCount = featuredContentList.size,
    modifier = modifier,
  ) { index ->
    val content = featuredContentList[index]
    Box {
      AsyncImage(
        model = content.backgroundImageUrl,
        contentDescription = content.description,
        placeholder = painterResource(
          id = R.drawable.placeholder
        ),
        contentScale = ContentScale.Crop,
        modifier = Modifier.fillMaxSize()
      )
      Text(text = content.title)
    }
  }
}

Carousel pode ser um item de uma lista lenta, como TvLazyColumn. O snippet abaixo mostra o elemento combinável FeaturedCarousel sobre todos os elementos Section.

@Composable
fun CatalogBrowser(
   featuredContentList: List<Movie>,
   sectionList: List<Section>,
   modifier: Modifier = Modifier,
   onItemSelected: (Movie) -> Unit = {},
) {
  TvLazyColumn(
    modifier = modifier.fillMaxSize(),
    verticalArrangement = Arrangement.spacedBy(16.dp)
  ) {

    item {
      FeaturedCarousel(featuredContentList)
    }

    items(sectionList) { section ->
      Section(section, onItemSelected = onItemSelected)
    }
  }
}