カタログ ブラウザを作成する

テレビで動作するメディアアプリでは、ユーザーがコンテンツ サービスをブラウジングし、選択を行い、コンテンツの再生を開始できるようにする必要があります。このタイプのアプリのコンテンツ ブラウジング エクスペリエンスは、シンプルで直感的、視覚的に魅力的で魅力的なものである必要があります。

このセクションでは、Compose for TV が提供する機能を使用して、アプリのメディア カタログから音楽や動画を閲覧するためのユーザー インターフェースを実装する方法について説明します。

図 1. 典型的なカタログ画面ユーザーが動画カタログデータをブラウジングできる。

メディア カタログ ブラウザは複数のセクションで構成される傾向があり、各セクションにはメディア コンテンツのリストがあります。メディアカタログのセクションの例としては 再生リスト、注目のコンテンツ、おすすめのカテゴリ

カタログ用にコンポーズ可能な関数を作成する

ディスプレイに表示されるものはすべて、Compose for TV でコンポーズ可能な関数として実装されます。まず、メディア カタログ ブラウザ用にコンポーズ可能な関数を次のスニペットとして定義します。

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

CatalogBrowser は、メディア カタログ ブラウザを実装するコンポーズ可能な関数です。この関数は次の引数を取ります。

  • 注目のコンテンツのリスト。
  • セクションのリスト。
  • Modifier オブジェクト。
  • 画面の遷移をトリガーするコールバック関数。

UI 要素を設定する

Compose for TV には、多数のアイテム(または長さが不明のリスト)を表示するコンポーネントである遅延リストが用意されています。TvLazyColumn を呼び出してセクションを縦方向に配置します。TvLazyColumn には、アイテム コンテンツを定義する DSL を提供する TvLazyListScope.() -> Unit ブロックが用意されています。次の例では、各セクションを縦方向のリストに配置し、セクション間の間隔を 16 dp にしています。

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

この例では、コンポーズ可能な関数 Section がセクションの表示方法を定義しています。次の関数の TvLazyRow は、この水平バージョンの TvLazyColumn を使用して、提供された DSL を呼び出して TvLazyListScope.() -> Unit ブロックを含む水平リストを定義する方法を示しています。

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

Section コンポーザブルでは、Text コンポーネントが使用されています。マテリアル デザインで定義されたテキストやその他のコンポーネントは、tv-material ライブラリで提供されています。マテリアル デザインで定義されているテキストのスタイルは、MaterialTheme オブジェクトを参照して変更できます。このオブジェクトは tv-material ライブラリからも提供されます。 MovieCard は、次のスニペットで定義されたカタログで各映画データのレンダリング方法を定義します。Card も tv-material ライブラリの一部です。

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

前述の例では、すべての映画が同じように表示されます。表示面積は同じで、見た目の違いはありません。その一部を Carousel でハイライト表示できます。

カルーセルでは、スライド、フェード、ビュー内への移動が可能な一連のアイテムとして情報が表示されます。このコンポーネントを使用して、新たに視聴可能な映画やテレビ番組の新しいエピソードなど、注目のコンテンツをハイライト表示します。

Carousel では、少なくとも、カルーセルに含まれるアイテムの数と、各アイテムの描画方法を指定する必要があります。最初の URL は itemCount で指定できます。2 つ目の引数はラムダとして渡すことができます。表示されるアイテムのインデックス番号がラムダに渡されます。表示されるアイテムは、指定されたインデックス値で判断できます。

@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 などの遅延リストの項目にできます。次のスニペットは、すべての Section コンポーザブルの上にある FeaturedCarousel コンポーザブルを示しています。

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