Cómo crear un navegador de catálogos

Una app de música que se ejecute en una TV debe permitir a los usuarios navegar por los contenidos que ofrece, seleccionarlos y reproducirlos. La experiencia de navegación por el contenido de las apps de este tipo debe ser intuitiva, simple, visualmente agradable y atractiva.

Un navegador de catálogos multimedia suele constar de varias secciones, y cada sección tiene una lista de contenido multimedia. Entre los ejemplos de secciones en un catálogo de contenido multimedia, se incluyen las playlists, el contenido destacado y las categorías recomendadas.

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

Usa las funciones que proporciona Compose para TV para implementar una interfaz de usuario que permita navegar por música o videos desde el catálogo multimedia de tu app.

Crea 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. Comienza por definir una función de componibilidad para el navegador del catálogo de contenido multimedia:

@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álogo de medios. La función toma los siguientes argumentos:

  • Es la lista de contenido destacado.
  • Lista de secciones.
  • Es un objeto Modifier.
  • Es 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 para mostrar una gran cantidad de elementos (o una lista de longitud desconocida). Llama a LazyColumn para colocar secciones de forma vertical. LazyColumn proporciona un bloque LazyListScope.() -> Unit, que ofrece un DSL para definir el contenido del elemento. En el siguiente ejemplo, cada sección se coloca en una lista vertical con un espacio de 16 dp entre las secciones:

@Composable
fun CatalogBrowser(
   featuredContentList: List<Movie>,
   sectionList: List<Section>,
   modifier: Modifier = Modifier,
   onItemSelected: (Movie) -> Unit = {},
) {
  LazyColumn(
    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, LazyRow demuestra cómo esta versión horizontal de LazyColumn se usa de manera similar para definir una lista horizontal con un bloque LazyListScope.() -> Unit llamando al DSL proporcionado:

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

En el elemento Section componible, se usa el componente Text. El texto y otros componentes definidos en Material Design se ofrecen en la biblioteca tv-material . Puedes cambiar el estilo de los textos tal como se define en Material Design haciendo referencia al objeto MaterialTheme. La biblioteca de tv-material también proporciona este objeto. Card forma parte de la biblioteca tv-material. MovieCard define cómo se renderizan los datos de cada película en el catálogo, como se muestra en el siguiente fragmento:

@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 y no hay diferencias visuales entre ellos. Puedes destacar algunos de ellos con Carousel.

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

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

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