1. 始める前に
Compose for TV は、Android TV で動作するアプリを開発するための最新の UI フレームワークです。TV アプリ向け Jetpack Compose のすべてのメリットを活用できるため、アプリの美しく機能性の高い UI の作成が容易になります。Compose for TV の具体的なメリットは次のとおりです。
- 柔軟性: Compose を使用すると、シンプルなレイアウトから複雑なアニメーションまで、あらゆる種類の UI を作成できます。コンポーネントはすぐに使用できますが、アプリのニーズに合わせたカスタマイズやスタイル設定も可能です。
 - 開発の簡素化と加速: Compose は既存のコードと互換性があるため、デベロッパーは少ないコードでアプリを構築できます。
 - 直感的: Compose では、UI の変更とコードのデバッグ、理解、レビューを直感的に行える宣言型構文が使用されています。
 
TV アプリの一般的なユースケースはメディア消費です。ユーザーはコンテンツ カタログを参照して、視聴するコンテンツを選択します。コンテンツには、ムービー、テレビ番組、ポッドキャストなどがあります。ユーザーがコンテンツの選択を終えると、簡単な説明、再生時間、クリエイターの名前などの詳細情報が表示される可能性があります。この Codelab では、Compose for TV を使用してカタログ ブラウザ画面と詳細画面を実装する方法を学びます。
前提条件
- ラムダを含む Kotlin 構文の使用経験。
 - Compose に関する基本的な経験。Compose に慣れていない場合は、Jetpack Compose の基本の Codelab を修了してください。
 - コンポーザブルと修飾子に関する基本的な知識。
 - 次のいずれかのデバイス(サンプルアプリの実行用):
- Android TV デバイス
 - プロファイルが TV デバイス定義カテゴリに属する Android 仮想デバイス
 
 
構築内容
- カタログ ブラウザ画面と詳細画面を備えた動画プレーヤー アプリ。
 - ユーザーが選択できる動画の一覧が表示されるカタログ ブラウザ画面。次の画像のように表示されます。
 

- タイトル、説明、長さなど、選択した動画のメタデータを表示する詳細画面。次の画像のように表示されます。
 

必要なもの
- Android Studio の最新バージョン
 - TV デバイス カテゴリに属する Android TV デバイスまたは仮想デバイス
 
2. セットアップする
この Codelab のテーマ設定と基本設定を含むコードを取得するには、次のいずれかを行います。
- この GitHub リポジトリからコードのクローンを作成します。
 
$ git clone https://github.com/android/tv-codelabs.git
main ブランチにはスターター コードが含まれ、solution ブランチには解答コードが含まれます。
- スターター コードを含む 
main.zipファイルと、解答コードを含むsolution.zipファイルをダウンロードします。 
コードがダウンロードされたところで、Android Studio で IntroductionToComposeForTV プロジェクト フォルダを開きます。これで準備が整いました。
3. カタログ ブラウザ画面を実装する
カタログ ブラウザ画面では、映画のカタログをブラウジングできます。カタログ ブラウザをコンポーズ可能な関数として実装します。コンポーズ可能な関数 CatalogBrowser は CatalogBrowser.kt ファイルにあります。このコンポーズ可能な関数で、カタログ ブラウザ画面を実装します。
スターター コードには、CatalogBrowserViewModel クラスと呼ばれる ViewModel があります。このクラスには、映画のコンテンツを記述する Movie オブジェクトを取得するための属性とメソッドがいくつかあります。取得した Movie オブジェクトを使用してカタログ ブラウザを実装します。
CatalogBrowser.kt
@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = hiltViewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
}
カテゴリ名を表示する
Category リストのフローである catalogBrowserViewModel.categoryList 属性を使用して、カテゴリのリストにアクセスできます。フローは、collectAsStateWithLifecycle メソッドを呼び出すことにより Compose State オブジェクトとして収集されます。Category オブジェクトには name 属性があります。この属性は、カテゴリ名を表す String 値です。
カテゴリ名を表示する手順は次のとおりです。
- Android Studio でスターター コードの 
CatalogBrowser.ktファイルを開き、コンポーズ可能な関数LazyColumnをコンポーズ可能な関数CatalogBrowserに追加します。 catalogBrowserViewModel.categoryList.collectAsStateWithLifeCycle()メソッドを呼び出して、フローをStateオブジェクトとして収集します。categoryListは、前の手順で作成したStateオブジェクトの委任プロパティとして宣言します。categoryList変数をパラメータとしてitems関数を呼び出します。- ラムダの引数として渡されるパラメータとしてカテゴリ名を指定し、コンポーズ可能な関数 
Textを呼び出します。 
CatalogBrowser.kt
@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = hiltViewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
    val categoryList by catalogBrowserViewModel.categoryList.collectAsStateWithLifecycle()
    LazyColumn(modifier = modifier) {
        items(categoryList) { category ->
            Text(text = category.name)
        }
    }
}
各カテゴリのコンテンツ リストを表示する
Category オブジェクトには、movieList という属性があります。この属性は、カテゴリに属する映画を表す Movie オブジェクトのリストです。
各カテゴリのコンテンツ リストを表示する手順は次のとおりです。
- コンポーズ可能な関数 
LazyRowを追加してラムダを渡します。 - ラムダ内で、
categoryを使用してitems関数を呼び出します。movieList属性値を取得してラムダを渡します。 items関数に渡されたラムダ内で、Movieオブジェクトを指定してコンポーズ可能な関数MovieCardを呼び出します。
CatalogBrowser.kt
@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = hiltViewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
    val categoryList by
    catalogBrowserViewModel.categoryList.collectAsStateWithLifecycle()
    LazyColumn(modifier = modifier) {
        items(categoryList) { category ->
            Text(text = category.name)
            LazyRow {
                items(category.movieList) {movie ->
                    MovieCard(movie = movie)
                }
            }
        }
    }
}
省略可: レイアウトを調整する
- カテゴリ間の間隔を設定するには、
verticalArrangementパラメータを使用してArrangementオブジェクトをコンポーズ可能な関数LazyColumnに渡します。Arrangementオブジェクトは、Arrangement#spacedByメソッドを呼び出すことにより作成されます。 - 映画カードの間隔を設定するには、
horizontalArrangementパラメータを使用してArrangementオブジェクトをコンポーズ可能な関数LazyRowに渡します。 - 列にインデントを設定するには、
contentPaddingパラメータでPaddingValueオブジェクトを渡します。 
CatalogBrowser.kt
@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = hiltViewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
    val categoryList by
    catalogBrowserViewModel.categoryList.collectAsStateWithLifeCycle()
    LazyColumn(
        modifier = modifier,
        verticalArrangement = Arrangement.spacedBy(16.dp),
        contentPadding = PaddingValues(horizontal = 48.dp, vertical = 32.dp)
    ) {
        items(categoryList) { category ->
            Text(text = category.name)
            LazyRow(
                horizontalArrangement = Arrangement.spacedBy(8.dp)
            ) {
                items(category.movieList) { movie ->
                    MovieCard(movie = movie)
                }
            }
        }
    }
}
4. 詳細画面を実装する
詳細画面には、選択した映画の詳細が表示されます。Details.kt ファイル内に、コンポーズ可能な関数 Details があります。この関数にコードを追加して、詳細画面を実装します。
Details.kt
@Composable
fun Details(movie: Movie, modifier: Modifier = Modifier) {
}
ムービーのタイトル、スタジオ名、説明を表示する
Movie オブジェクトには、映画のメタデータとして次の 3 つの文字列属性があります。
title。映画のタイトル。studio。映画を制作したスタジオの名前。description。映画の簡単な要約。
このメタデータを詳細画面に表示する手順は次のとおりです。
- コンポーズ可能な関数 
Columnを追加し、Modifier.paddingメソッドで作成したModifierオブジェクトを使用して、縦方向 32 dp と横方向 48 dp のクリアランスを列に設定します。 - 映画のタイトルを表示するには、コンポーズ可能な関数 
Textを追加します。 - スタジオ名を表示するには、コンポーズ可能な関数 
Textを追加します。 - 映画の説明を表示するには、コンポーズ可能な関数 
Textを追加します。 
Details.kt
@Composable
fun Details(movie: Movie, modifier: Modifier = Modifier) {
    Column(
        modifier = Modifier
            .padding(vertical = 32.dp, horizontal = 48.dp)
    ) {
        Text(text = movie.title)
        Text(text = movie.studio)
        Text(text = movie.description)
    }
}
コンポーズ可能な関数 Details のパラメータで指定された Modifier オブジェクトは、次のタスクで使用します。
特定の Movie オブジェクトに関連付けられている背景画像を表示する
Movie オブジェクトには backgroundImageUrl 属性があり、そのオブジェクトで記述されている映画の背景画像の場所が示されます。
特定の映画の背景画像を表示する手順は次のとおりです。
- コンポーズ可能な関数 
Boxをコンポーズ可能な関数Columnのラッパーとして追加し、modifierオブジェクトをコンポーズ可能な関数Detailsを通じて渡します。 - コンポーズ可能な関数 
Box内で、modifierオブジェクトのfillMaxSizeメソッドを呼び出して、コンポーズ可能な関数Boxがコンポーズ可能な関数Detailsに割り当てることのできる最大サイズを満たすようにします。 - 次のパラメータを含むコンポーズ可能な関数 
AsyncImageをコンポーズ可能な関数Boxに追加します。 
- 指定された 
MovieオブジェクトのbackgroundImageUrl属性の値をmodelパラメータに設定します。 nullをcontentDescriptionパラメータに渡します。
ContentScale.CropオブジェクトをcontentScaleパラメータに渡します。さまざまなContentScaleオプションについては、コンテンツ スケールをご覧ください。Modifier.fillMaxSizeメソッドの戻り値をmodifierパラメータに渡します。
Details.kt
@Composable
fun Details(movie: Movie, modifier: Modifier = Modifier) {
    Box(modifier = modifier.fillMaxSize()) {
        AsyncImage(
            model = movie.cardImageUrl,
            contentDescription = null,
            contentScale = ContentScale.Crop,
            modifier = Modifier.fillMaxSize()
        )
        Column {
            Text(
                text = movie.title,
            )
            Text(
                text = movie.studio,
            )
            Text(text = movie.description)
        }
    }
}
テーマ設定の一貫性を保つために MaterialTheme オブジェクトを参照する
MaterialTheme オブジェクトには、Typography クラスや ColorScheme クラスなどにある現在のテーマ値を参照する関数が含まれています。
テーマ設定の一貫性を保つために MaterialTheme オブジェクトを参照する手順は次のとおりです。
MaterialTheme.typography.displayMediumプロパティに、映画のタイトルのテキスト スタイルを設定します。MaterialTheme.typography.bodySmallプロパティに、2 番目のコンポーズ可能な関数Textのテキスト スタイルを設定します。Modifier.backgroundメソッドを使用して、MaterialTheme.colorScheme.backgroundプロパティに、コンポーズ可能な関数Columnの背景色を設定します。
Details.kt
@Composable
fun Details(movie: Movie, modifier: Modifier = Modifier) {
    Box(modifier = modifier.fillMaxSize()) {
        AsyncImage(
            model = movie.cardImageUrl,
            contentDescription = null,
            contentScale = ContentScale.Crop,
            modifier = Modifier.fillMaxSize()
        )
        Column(
            modifier = Modifier
                .background(MaterialTheme.colorScheme.background),
        ) {
            Text(
                text = movie.title,
                style = MaterialTheme.typography.displayMedium,
            )
            Text(
                text = movie.studio,
                style = MaterialTheme.typography.bodySmall,
            )
            Text(text = movie.description)
        }
    }
}
省略可: レイアウトを調整する
コンポーズ可能な関数 Details のレイアウトを調整する手順は次のとおりです。
fillMaxSize修飾子により、使用可能なスペース全体を使用するようにコンポーズ可能な関数Boxを設定します。background修飾子でコンポーズ可能な関数Boxの背景を設定し、MaterialTheme.colorScheme.background値とColor.Transparentを含むColorオブジェクトのリストでBrush.linearGradient関数を呼び出すことにより作成される線形グラデーションで背景を塗りつぶします。padding修飾子を使用して、コンポーズ可能な関数Columnの周囲に横方向48.dpと縦方向24.dpのクリアランスを設定します。0.5f値を指定してModifier.width関数を呼び出すことにより作成されるwidth修飾子を使用して、コンポーズ可能な関数Columnの幅を設定します。Spacerを使用して、2 番目のコンポーズ可能な関数Textと 3 番目のコンポーズ可能な関数Textの間に8.dpのスペースを追加します。コンポーズ可能な関数Spacerの高さは、Modifier.height関数で作成されたheight修飾子で指定します。
Details.kt
@Composable
fun Details(movie: Movie, modifier: Modifier = Modifier) {
    Box(modifier = modifier.fillMaxSize()) {
        AsyncImage(
            model = movie.cardImageUrl,
            contentDescription = null,
            contentScale = ContentScale.Crop,
            modifier = Modifier.fillMaxSize()
        )
        Box(
            modifier = Modifier
                .background(
                    Brush.linearGradient(
                        listOf(
                            MaterialTheme.colorScheme.background,
                            Color.Transparent
                        )
                    )
                )
                .fillMaxSize()
        ) {
            Column(
                modifier = Modifier
                    .padding(horizontal = 48.dp, vertical = 24.dp)
                    .fillMaxWidth(0.5f)
            ) {
                Text(
                    text = movie.title,
                    style = MaterialTheme.typography.displayMedium,
                )
                Text(
                    text = movie.studio,
                    style = MaterialTheme.typography.bodySmall,
                )
                Spacer(modifier = Modifier.height(8.dp))
                Text(
                    text = movie.description,
                )
            }
        }
    }
}
5. 画面間のナビゲーションを追加する
これで、カタログ ブラウザと詳細画面が表示されます。ユーザーがカタログ ブラウザ画面でコンテンツを選択した後は、詳細画面に遷移する必要があります。そのためには、clickable 修飾子を使用して、event リスナーをコンポーズ可能な関数 MovieCard に追加します。十字キーの中央ボタンが押されると、コンポーズ可能な関数 MovieCard に関連付けられた映画オブジェクトを引数として CatalogBrowserViewModel#showDetails メソッドが呼び出されます。
com.example.tvcomposeintroduction.ui.screens.CatalogBrowserファイルを開きます。onClickパラメータを指定して、ラムダ関数をコンポーズ可能な関数MovieCardに渡します。- コンポーズ可能な関数 
MovieCardに関連付けられた映画オブジェクトを指定して、onMovieSelectedコールバックを呼び出します。 
CatalogBrowser.kt
@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = hiltViewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
    val categoryList by
    catalogBrowserViewModel.categoryList.collectAsStateWithLifecycle()
    LazyColumn(
        modifier = modifier,
        verticalArrangement = Arrangement.spacedBy(16.dp),
        contentPadding = PaddingValues(horizontal = 48.dp, vertical = 32.dp)
    ) {
        items(categoryList) { category ->
            Text(text = category.name)
            LazyRow(
                horizontalArrangement = Arrangement.spacedBy(8.dp)
            ) {
                items(category.movieList) { movie ->
                    MovieCard(movie = movie, onClick = { onMovieSelected(movie) })
                }
            }
        }
    }
}
6. カタログ ブラウザの画面にカルーセルを追加して、注目のコンテンツをハイライト表示する
カルーセルは、指定された時間が経過するとスライドを自動的に更新する、適応性の高い UI コンポーネントです。通常は、注目のコンテンツをハイライト表示するために使用します。
カルーセルをカタログ ブラウザ画面に追加して、注目のコンテンツ リストで映画をハイライトする手順は次のとおりです。
com.example.tvcomposeintroduction.ui.screens.CatalogBrowserファイルを開きます。item関数を呼び出して、アイテムをコンポーズ可能な関数LazyColumnに追加します。item関数に渡されたラムダ内の委任プロパティとしてfeaturedMovieListを宣言し、catalogBrowserViewModel.featuredMovieList属性から収集されるStateオブジェクトを委任に設定します。item関数内でコンポーズ可能な関数Carouselを呼び出し、次のパラメータを渡します。
slideCountパラメータを介したfeaturedMovieList変数のサイズ。Modifier.fillMaxWidthメソッドとModifier.heightメソッドでカルーセル サイズを指定するためのModifierオブジェクト。コンポーズ可能な関数Carouselは、376.dp値をModifier.heightメソッドに渡して、376 dp の高さを使用します。- 表示されるカルーセル アイテムのインデックスを示す整数値で呼び出されるラムダ。
 
featuredMovieList変数と指定されたインデックス値からMovieオブジェクトを取得します。- コンポーズ可能な関数 
Boxをコンポーズ可能な関数Carouselに追加します。 - 映画のタイトルを表示するには、コンポーズ可能な関数 
Textをコンポーズ可能な関数Boxに追加します。 
CatalogBrowser.kt
@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = hiltViewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
    val categoryList by
    catalogBrowserViewModel.categoryList.collectAsStateWithLifecycle()
    LazyColumn(
        modifier = modifier,
        verticalArrangement = Arrangement.spacedBy(16.dp),
        contentPadding = PaddingValues(horizontal = 48.dp, vertical = 32.dp)
    ) {
        item {
            val featuredMovieList by catalogBrowserViewModel.featuredMovieList.collectAsStateWithLifecycle()
            Carousel(
                slideCount = featuredMovieList.size,
                modifier = Modifier
                    .fillMaxWidth()
                    .height(376.dp)
            ) { indexOfCarouselSlide ->
                val featuredMovie =
                    featuredMovieList[indexOfCarouselSlide]
                Box {
                    Text(text = featuredMovie.title)
                }
            }
        }
        items(categoryList) { category ->
            Text(text = category.name)
            LazyRow(
                horizontalArrangement = Arrangement.spacedBy(8.dp)
            ) {
                items(category.movieList) { movie ->
                    MovieCard(movie = movie, onClick = { onMovieSelected(movie) })
                }
            }
        }
    }
}
背景画像を表示する
コンポーズ可能な関数 Box は、あるコンポーネントを別のコンポーネントの上に配置します。詳しくは、レイアウトの基本をご覧ください。
背景画像を表示する手順は次のとおりです。
- コンポーズ可能な関数 
AsyncImageを呼び出して、コンポーズ可能な関数Textの前で、Movieオブジェクトに関連付けられた背景画像を読み込みます。 - 視認性を高めるため、コンポーズ可能な関数 
Textの位置とテキスト スタイルを更新します。 - レイアウト シフトを避けるため、プレースホルダにコンポーズ可能な関数 
AsyncImageを設定します。スターター コードには、R.drawable.placeholderで参照できるドローアブルとしてプレースホルダがあります。 
CatalogBrowser.kt
@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = hiltViewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
    val categoryList by
    catalogBrowserViewModel.categoryList.collectAsStateWithLifecycle()
    LazyColumn(
        modifier = modifier,
        verticalArrangement = Arrangement.spacedBy(16.dp),
        contentPadding = PaddingValues(horizontal = 48.dp, vertical = 32.dp)
    ) {
        item {
            val featuredMovieList by catalogBrowserViewModel.featuredMovieList.collectAsStateWithLifecycle()
            Carousel(
                slideCount = featuredMovieList.size,
                modifier = Modifier
                    .fillMaxWidth()
                    .height(376.dp),
            ) { indexOfCarouselItem ->
                val featuredMovie = featuredMovieList[indexOfCarouselItem]
                Box{
                    AsyncImage(
                        model = featuredMovie.backgroundImageUrl,
                        contentDescription = null,
                        placeholder = painterResource(
                            id = R.drawable.placeholder
                        ),
                        contentScale = ContentScale.Crop,
                        modifier = Modifier.fillMaxSize(),
                    )
                    Text(text = featuredMovie.title)
                }
            }
        }
        items(categoryList) { category ->
            Text(text = category.name)
            LazyRow(
                horizontalArrangement = Arrangement.spacedBy(8.dp)
            ) {
                items(category.movieList) { movie ->
                    MovieCard(movie = movie, onClick = { onMovieSelected(movie) })
                }
            }
        }
    }
}
詳細画面への画面遷移を追加する
Button をカルーセルに追加すると、ユーザーがボタンをクリックして詳細画面への画面遷移をトリガーできるようになります。
詳細画面に表示されるカルーセルでユーザーが映画の詳細を確認できるようにする手順は次のとおりです。
Carouselコンポーザブル内のBoxコンポーザブルで、コンポーズ可能な関数Columnを呼び出します。CarouselのTextコンポーザブルをコンポーズ可能な関数Columnに移動します。- コンポーズ可能な関数 
Column内のコンポーズ可能な関数Textの後で、コンポーズ可能な関数Buttonを呼び出します。 R.string.show_detailsで呼び出されたstringResource関数の戻り値を使用して、コンポーズ可能な関数Button内でコンポーズ可能な関数Textを呼び出します。- コンポーズ可能な関数 
ButtonのonClickパラメータに渡されたラムダ内でfeaturedMovie変数を指定して、onMovieSelected関数を呼び出します。 
CatalogBrowser.kt
@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = hiltViewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
    val categoryList by
    catalogBrowserViewModel.categoryList.collectAsStateWithLifecycle()
    LazyColumn(
        modifier = modifier,
        verticalArrangement = Arrangement.spacedBy(16.dp),
        contentPadding = PaddingValues(horizontal = 48.dp, vertical = 32.dp)
    ) {
        item {
            val featuredMovieList by catalogBrowserViewModel.featuredMovieList.collectAsStateWithLifecycle()
            Carousel(
                slideCount = featuredMovieList.size,
                modifier = Modifier
                    .fillMaxWidth()
                    .height(376.dp),
            ) { indexOfCarouselItem ->
                val featuredMovie = featuredMovieList[indexOfCarouselItem]
                Box {
                    AsyncImage(
                        model = featuredMovie.backgroundImageUrl,
                        contentDescription = null,
                        placeholder = painterResource(
                            id = R.drawable.placeholder
                        ),
                        contentScale = ContentScale.Crop,
                        modifier = Modifier.fillMaxSize(),
                    )
                    Column {
                        Text(text = featuredMovie.title)
                        Button(onClick = { onMovieSelected(featuredMovie) }) {
                            Text(text = stringResource(id = R.string.show_details))
                        }
                    }
                }
            }
        }
        items(categoryList) { category ->
            Text(text = category.name)
            LazyRow(
                horizontalArrangement = Arrangement.spacedBy(8.dp)
            ) {
                items(category.movieList) { movie ->
                    MovieCard(movie = movie, onClick = { onMovieSelected(movie) })
                }
            }
        }
    }
}
省略可: レイアウトを調整する
カルーセルのレイアウトを調整する手順は次のとおりです。
- コンポーズ可能な関数 
Carousel内で、backgroundColor値にMaterialTheme.colorScheme.background値を代入します。 - コンポーズ可能な関数 
ColumnをBoxコンポーザブルでラップします。 Alignment.BottomStart値をBoxコンポーネントのcontentAlignmentパラメータに渡します。fillMaxSize修飾子をコンポーズ可能な関数Boxの修飾子パラメータに渡します。fillMaxSize修飾子はModifier.fillMaxSize()関数で作成されます。Boxコンポーザブルに渡されたfillMaxSize修飾子に対してdrawBehind()メソッドを呼び出します。drawBehind修飾子に渡されたラムダ内で、Brushオブジェクトを使用してbrush値を代入します。このオブジェクトは、2 つのColorオブジェクトのリストを指定してBrush.linearGradient関数を呼び出すことにより作成されます。このリストは、backgroundColor値とColor.Transparent値を指定してlistOf関数を呼び出すことにより作成されます。drawBehind修飾子に渡されたラムダ内のbrushオブジェクトを指定してdrawRectを呼び出し、背景画像の上にスクリムレイヤを作成します。padding修飾子を使用して、コンポーズ可能な関数Columnのパディングを指定します。この修飾子は、20.dp値を指定してModifier.paddingを呼び出すことにより作成されます。- コンポーズ可能な関数 
Column内で、TextコンポーザブルとButtonコンポーザブルの間に、20.dpの値を含むコンポーズ可能な関数Spacerを追加します。 
CatalogBrowser.kt
@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = hiltViewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
    val categoryList by catalogBrowserViewModel.categoryList.collectAsStateWithLifecycle()
    LazyColumn(
        modifier = modifier,
        verticalArrangement = Arrangement.spacedBy(32.dp),
        contentPadding = PaddingValues(horizontal = 58.dp, vertical = 36.dp)
    ) {
        item {
            val featuredMovieList by
            catalogBrowserViewModel.featuredMovieList.collectAsStateWithLifecycle()
            Carousel(
                itemCount = featuredMovieList.size,
                modifier = Modifier
                    .fillMaxWidth()
                    .height(376.dp),
            ) { indexOfCarouselItem ->
                val featuredMovie = featuredMovieList[indexOfCarouselItem]
                val backgroundColor = MaterialTheme.colorScheme.background
                
                Box {
                    AsyncImage(
                        model = featuredMovie.backgroundImageUrl,
                        contentDescription = null,
                        placeholder = painterResource(
                            id = R.drawable.placeholder
                        ),
                        contentScale = ContentScale.Crop,
                        modifier = Modifier.fillMaxSize(),
                    )
                    Box(
                        contentAlignment = Alignment.BottomStart,
                        modifier = Modifier
                            .fillMaxSize()
                            .drawBehind {
                                val brush = Brush.horizontalGradient(
                                    listOf(backgroundColor, Color.Transparent)
                                )
                                drawRect(brush)
                            }
                    ) {
                        Column(
                            modifier = Modifier.padding(20.dp)
                        ) {
                            Text(
                                text = featuredMovie.title,
                                style = MaterialTheme.typography.displaySmall
                            )
                            Spacer(modifier = Modifier.height(28.dp))
                            Button(onClick = { onMovieSelected(featuredMovie) }) {
                                Text(text = stringResource(id = R.string.show_details))
                            }
                        }
                    }
                }
            }
        }
        items(categoryList) { category ->
            Text(text = category.name)
            LazyRow(
                horizontalArrangement = Arrangement.spacedBy(16.dp),
                modifier = Modifier.height(200.dp)
            ) {
                items(category.movieList) { movie ->
                    MovieCard(
                        movie,
                        onClick = {
                            onMovieSelected(it)
                        }
                    )
                }
            }
        }
    }
}
7. 解答コードを取得する
この Codelab の解答コードをダウンロードするには、次のいずれかを行います。
- 次のボタンをクリックして ZIP ファイルとしてダウンロードし、Android Studio で開きます。
 
- Git を使用して取得します。
 
$ git clone https://github.com/android/tv-codelabs.git $ cd tv-codelabs $ git checkout solution $ cd IntroductionToComposeForTV
8. これで完了です。
お疲れさまでした。Compose for TV の基本を習得しました。
- LazyColumn と LazyLow を組み合わせてコンテンツ リストを表示する画面の実装方法。
 - コンテンツの詳細を表示する基本的な画面の実装。
 - 2 つの画面間の画面遷移を追加する方法。