Создать браузер каталога

Медиаприложение для телевизора должно позволять пользователям просматривать предлагаемый контент, выбирать его и начинать воспроизведение. Процесс просмотра контента в подобных приложениях должен быть простым, интуитивно понятным, визуально привлекательным и интересным.

Каталог медиафайлов обычно состоит из нескольких разделов, и каждый раздел содержит список медиаконтента. Примеры разделов в каталоге медиафайлов включают: плейлисты, рекомендуемый контент, рекомендуемые категории.

Рисунок 1. Типичный экран каталога. Пользователи могут просматривать данные видеокаталога.

Используйте функции Compose for TV для реализации пользовательского интерфейса для просмотра музыки или видео из медиакаталога вашего приложения.

Создайте составную функцию для каталога.

В Compose for TV всё, что отображается на экране, реализовано как составная функция. Начните с определения составной функции для браузера каталога медиафайлов:

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

CatalogBrowser — это составная функция, реализующая интерфейс браузера вашего каталога медиафайлов. Функция принимает следующие аргументы:

  • Список рекомендуемого контента.
  • Список разделов.
  • Объект-модификатор.
  • Функция обратного вызова, которая запускает переход между экранами.

Настройка элементов пользовательского интерфейса

Compose for TV предлагает ленивые списки — компонент для отображения большого количества элементов (или списка неизвестной длины). Вызовите LazyColumn , чтобы разместить секции вертикально. LazyColumn предоставляет блок LazyListScope.() -> Unit , который предлагает DSL для определения содержимого элементов. В следующем примере каждая секция размещается в вертикальном списке с интервалом в 16 dp между секциями:

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

В примере составная функция Section определяет способ отображения разделов. В следующей функции LazyRow демонстрируется, как эта горизонтальная версия LazyColumn аналогично используется для определения горизонтального списка с блоком LazyListScope.() -> Unit путем вызова предоставленного DSL:

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

В компоненте Section используется компонент Text . Компоненты Text и другие компоненты, определенные в Material Design, предлагаются в библиотеке tv-material. Вы можете изменить стиль текста, определенный в Material Design, обратившись к объекту MaterialTheme . Этот объект также предоставляется библиотекой tv-material. Card является частью библиотеки tv-material. MovieCard определяет, как отображаются данные каждого фильма в каталоге, определенном следующим фрагментом кода:

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

В описанном ранее примере все фильмы отображаются одинаково. Они занимают одинаковую площадь, визуальных различий между ними нет. Некоторые из них можно выделить с помощью Carousel .

Карусель отображает информацию в виде набора элементов, которые могут сдвигаться, плавно появляться или перемещаться. Вы используете этот компонент для выделения рекомендуемого контента, например, недавно вышедших фильмов или новых эпизодов телепрограмм.

Carousel ожидает, что вы укажете как минимум количество элементов в Carousel и способ отрисовки каждого элемента. Первое можно указать с помощью itemCount . Второе можно передать в виде лямбда-функции. В лямбда-функцию передается индекс отображаемого элемента. Вы можете определить отображаемый элемент по заданному индексу:

@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 может быть частью ленивого списка, например, TvLazyColumn . Следующий фрагмент кода демонстрирует, как элемент FeaturedCarousel располагается поверх всех элементов 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)
    }
  }
}