Cómo crear un navegador de catálogos

Una app de música que se ejecuta en una TV debe permitir a los usuarios explorar las ofertas de su contenido, elegir una opción y comenzar a reproducir contenido. La experiencia de navegación por el contenido de estas apps debe ser intuitiva y simple, y visualmente agradable y atractiva.

En esta sección, se describe cómo usar las funciones que proporciona Compose para TV a fin de implementar una interfaz de usuario para explorar música o videos desde el catálogo de contenido multimedia de tu app.

Figura 1: Pantalla de catálogo típica. Los usuarios pueden explorar los datos del catálogo de videos.

Un navegador de catálogos de contenido multimedia suele constar de varias secciones, y cada una tiene una lista de contenido multimedia. Ejemplos de secciones en un catálogo de medios son: playlists, contenido destacado, categorías

Cómo crear una función de componibilidad para el catálogo

Todo lo que aparece en una pantalla se implementa como una función de componibilidad en Compose para TV. Comenzarás por definir una función de componibilidad para el navegador del catálogo de contenido multimedia como el siguiente fragmento:

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

CatalogBrowser es la función de componibilidad que implementa tu navegador de catálogos de contenido multimedia. La función toma los siguientes argumentos:

  • Es la lista de contenido destacado.
  • Lista de secciones.
  • Es un objeto modificador.
  • Una función de devolución de llamada, que activa una transición de pantalla

Cómo establecer elementos de IU

Compose para TV ofrece listas diferidas, un componente que muestra una gran cantidad de elementos (o una lista de longitud desconocida). Llama a TvLazyColumn para colocar las secciones verticalmente. TvLazyColumn proporciona un bloque TvLazyListScope.() -> Unit, que ofrece un DSL para definir el contenido de los elementos. En el siguiente ejemplo, cada sección se coloca en una lista vertical con un espacio de 16 dp entre secciones.

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

En el ejemplo, la función de componibilidad Section define cómo mostrar las secciones. En la siguiente función, TvLazyRow demuestra cómo se usa de manera similar esta versión horizontal de TvLazyColumn para definir una lista horizontal con un bloque TvLazyListScope.() -> Unit llamando al DSL proporcionado.

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

En el elemento componible Section, se usa el componente Text. El texto y otros componentes definidos en Material Design se ofrecen en la biblioteca de material de TV . Puedes cambiar el estilo del texto según se define en Material Design consultando el objeto MaterialTheme. La biblioteca tv-material también proporciona este objeto. MovieCard define cómo se renderizan los datos de película en el catálogo, definido como el siguiente fragmento. Card también forma parte de la 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,
     )
   }
}

En el ejemplo descrito anteriormente, todas las películas se muestran por igual. Tienen la misma área, sin diferencia visual entre ellos. Puedes destacar algunos con Carousel.

El carrusel muestra la información en un conjunto de elementos que se pueden deslizar, atenuar o mover a la vista. El componente se usa para destacar contenido destacado, como películas recientemente disponibles o episodios nuevos de programas de TV.

El objeto Carousel espera que, al menos, especifiques la cantidad de elementos que tiene el carrusel y cómo dibujar cada uno. El primero se puede especificar con itemCount. El segundo se puede pasar como lambda. El número de índice del elemento que se muestra se proporciona a la expresión lambda. Puedes determinar el elemento que se muestra con el valor de índice dado.

@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 puede ser un elemento de una lista diferida, como TvLazyColumn. En el siguiente fragmento, se muestra el elemento FeaturedCarousel componible encima de todos los elementos Section componibles.

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