Compose for TV 简介

1. 开始之前

Compose for TV 是用于开发在 Android TV 上运行的应用程序的最新 UI 框架。它解锁了 Jetpack Compose for TV 应用程序的所有优势,这使得为您的应用程序构建美观且功能强大的 UI 变得更加容易。Compose for TV 的一些具体优势包括以下内容

  • 灵活性。Compose 可用于创建任何类型的 UI,从简单的布局到复杂的动画。组件开箱即用,但也可以根据您的应用程序需求进行自定义和样式化。
  • 简化和加速开发。Compose 与现有代码兼容,让开发者可以编写更少的代码来构建应用程序。
  • 直观性:Compose 使用声明式语法,使更改 UI 以及调试、理解和审查代码变得直观。

TV 应用程序的常见用例是媒体消费。用户浏览内容目录并选择他们想要观看的内容。内容可以是电影、电视剧或播客。用户选择内容后,他们可能想要查看有关该内容的更多信息,例如简短说明、播放时长和创作者姓名。在本 Codelab 中,您将学习如何使用 Compose for TV 实现一个目录浏览器屏幕和一个详细信息屏幕。

先决条件

  • 熟悉 Kotlin 语法,包括 lambda 表达式。
  • 熟悉 Compose。如果您不熟悉 Compose,请完成Jetpack Compose 基础知识 Codelab。
  • 了解可组合函数和修饰符。
  • 以下任何设备均可运行示例应用程序
    • Android TV 设备
    • 使用 TV 设备定义类别中的配置文件的 Android 虚拟设备

您将构建的内容

  • 一个具有目录浏览器屏幕和详细信息屏幕的视频播放器应用程序。
  • 一个目录浏览器屏幕,显示用户可以选择的一系列视频。它看起来像下面的图像

The catalog browser displays a list of featured movies\nwith a carousel on top.\nThe screen also displays a list of movies for each category.

  • 一个详细信息屏幕,显示所选视频的元数据,例如标题、描述和时长。它看起来像下面的图像

The details screen displays the movie's metadata,\nincluding its title, studio, and short description.\nThe metadata is displayed on the background image associated with the movie.

您需要的内容

  • Android Studio 的最新版本
  • Android TV 设备或 TV 设备类别中的虚拟设备

2. 设置

要获取包含本 Codelab 的主题和基本设置的代码,请执行以下操作之一

$ git clone https://github.com/android/tv-codelabs.git

main 分支包含启动代码,solution 分支包含解决方案代码。

  • 下载包含启动代码的 main.zip 文件,以及包含解决方案代码的 solution.zip 文件。

现在您已经下载了代码,请在 Android Studio 中打开 IntroductionToComposeForTV 项目文件夹。现在您已经准备好开始。

3. 实现目录浏览器屏幕

目录浏览器屏幕允许用户浏览电影目录。您将目录浏览器实现为一个可组合函数。您可以在 CatalogBrowser.kt 文件中找到 CatalogBrowser 可组合函数。您将在该可组合函数中实现目录浏览器屏幕。

启动代码有一个名为 CatalogBrowserViewModel 类的 ViewModel,它具有多个属性和方法来检索描述电影内容的 Movie 对象。您将使用检索到的 Movie 对象实现目录浏览器。

CatalogBrowser.kt

@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = hiltViewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
}

显示类别名称

您可以使用 catalogBrowserViewModel.categoryList 属性访问类别列表,该属性是 Category 列表的 。流通过调用其 Compose State 对象的 collectAsStateWithLifecycle 方法被收集为 Compose State 对象。Category 对象具有 name 属性,该属性是表示类别名称的 String 值。

要显示类别名称,请执行以下步骤

  1. 在 Android Studio 中,打开启动代码的 CatalogBrowser.kt 文件,然后在 CatalogBrowser 可组合函数中添加一个 LazyColumn 可组合函数。
  2. 调用 catalogBrowserViewModel.categoryList.collectAsStateWithLifeCycle() 方法将流收集为 State 对象。
  3. categoryList 声明为在上一步骤中创建的 State 对象的委托属性。
  4. 使用 categoryList 变量作为参数,调用 items 函数。
  5. 使用类别名称作为参数,调用 Text 可组合函数,该参数作为 lambda 表达式的参数传递。

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 对象的列表,这些对象表示属于该类别的电影。

要显示每个类别的内容列表,请执行以下步骤

  1. 添加 LazyRow 可组合函数,然后向其传递一个 lambda 表达式。
  2. 在 lambda 中,使用 category.movieList 属性值调用 items 函数,然后向其传递一个 lambda。
  3. 在传递给 items 函数的 lambda 中,使用 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)
                }
            }
        }
    }
}

可选:调整布局

  1. 要设置类别之间的间距,请将 Arrangement 对象传递给 LazyColumn 可组合函数,并使用 verticalArrangement 参数。通过调用 Arrangement#spacedBy 方法创建 Arrangement 对象。
  2. 要设置电影卡片之间的间距,请将 Arrangement 对象传递给 LazyRow 可组合函数,并使用 horizontalArrangement 参数。
  3. 要设置列的缩进,请使用 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 对象具有以下三个字符串属性作为电影的元数据

  • title。电影标题。
  • studio。制作电影的工作室名称。
  • description。电影的简短概述。

要在详细信息屏幕上显示此元数据,请按照以下步骤操作

  1. 添加一个 Column 可组合函数,然后使用 Modifier.padding 方法创建的 Modifier 对象,在列周围设置 32 dp 垂直和 48 dp 水平间隙。
  2. 添加一个 Text 可组合函数来显示电影标题。
  3. 添加一个 Text 可组合函数来显示工作室名称。
  4. 添加一个 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 属性,它指示由对象描述的电影的背景图片的位置。

要显示给定电影的背景图片,请按照以下步骤操作

  1. 添加一个 Box 可组合函数作为 Column 可组合函数的包装器,并使用通过 Details 可组合函数传递的 modifier 对象。
  2. Box 可组合函数中,调用 modifier 对象的 fillMaxSize 方法,使 Box 可组合函数填充可以分配给 Details 可组合函数的最大大小。
  3. 使用以下参数向 Box 可组合函数添加一个 AsyncImage 可组合函数
  • 将给定 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 对象包含函数以引用当前主题值,例如 TypographyColorScheme 类中的值。

要参考 MaterialTheme 对象以实现一致的主题,请按照以下步骤操作

  1. MaterialTheme.typography.displayMedium 属性设置为电影标题的文本样式。
  2. MaterialTheme.typography.bodySmall 属性设置为第二个 Text 可组合函数的文本样式。
  3. MaterialTheme.colorScheme.background 属性设置为使用 Modifier.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 可组合函数的布局,请按照以下步骤操作

  1. Box 可组合函数设置为使用 fillMaxSize 修饰符的整个可用空间。
  2. 使用 background 修饰符设置 Box 可组合函数的背景,以使用线性渐变填充背景,该渐变是通过调用 Brush.linearGradient 函数使用包含 MaterialTheme.colorScheme.background 值和 Color.TransparentColor 对象列表创建的。
  3. 使用 padding 修饰符,在 Column 可组合函数周围设置 48.dp 水平和 24.dp 垂直间隙。
  4. 使用通过调用 Modifier.width 函数使用 0.5f 值创建的 width 修饰符,设置 Column 可组合函数的宽度。
  5. 在第二个 Text 可组合函数和第三个 Text 可组合函数之间添加 8.dp 间距,并使用 SpacerSpacer 可组合函数的高度由使用 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 修饰符向 MovieCard 可组合函数添加一个 event 侦听器。按下方向键的中心按钮时,将使用与 MovieCard 可组合函数关联的电影对象作为参数,调用 CatalogBrowserViewModel#showDetails 方法。

  1. 打开 com.example.tvcomposeintroduction.ui.screens.CatalogBrowser 文件。
  2. 使用 onClick 参数向 MovieCard 可组合函数传递一个 lambda 函数。
  3. 使用与 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 组件,它会在指定持续时间后自动更新其幻灯片。它通常用于突出显示特色内容。

要向目录浏览器屏幕添加轮播以突出显示特色内容列表中的电影,请按照以下步骤操作

  1. 打开 com.example.tvcomposeintroduction.ui.screens.CatalogBrowser 文件。
  2. 调用 item 函数向 LazyColumn 可组合函数添加一个项目。
  3. 在传递给 item 函数的 lambda 中,将 featuredMovieList 声明为委托属性,然后将 State 对象设置为委托对象,该对象从 catalogBrowserViewModel.featuredMovieList 属性收集。
  4. item 函数内调用 Carousel 可组合函数,然后传入以下参数
  • 通过 slideCount 参数传递 featuredMovieList 变量的大小。
  • 一个 Modifier 对象,使用 Modifier.fillMaxWidthModifier.height 方法指定轮播大小。Carousel 可组合函数使用 376 dp 的高度,方法是向 Modifier.height 方法传递 376.dp 值。
  • 一个使用整数值调用的 lambda,该整数值表示可见轮播项目的索引。
  1. featuredMovieList 变量和给定索引值中检索 Movie 对象。
  2. Carousel 可组合函数添加一个 Box 可组合函数。
  3. Box 可组合函数添加一个 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,
        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 可组合函数将一个组件放在另一个组件的顶部。有关详细信息,请参阅 布局基础知识

要显示背景图片,请按照以下步骤操作

  1. Text 可组合函数之前,调用 AsyncImage 可组合函数以加载与 Movie 对象关联的背景图片。
  2. 更新 Text 可组合函数的位置和文本样式,以提高可见度。
  3. 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,以便用户可以通过点击按钮触发到详细信息屏幕的屏幕过渡。

要让用户在详细信息屏幕上看到可见轮播中的电影详细信息,请按照以下步骤操作

  1. Carousel 可组合函数中的 Box 可组合函数中调用 Column 可组合函数
  2. Carousel 中的 Text 可组合函数移到 Column 可组合函数中
  3. Column 可组合函数中的 Text 可组合函数之后调用 Button 可组合函数
  4. Button 可组合函数中调用 Text 可组合函数,并使用 stringResource 函数返回的值,该函数使用 R.string.show_details 调用。
  5. 在传递给 Button 可组合函数的 onClick 参数的 lambda 中,使用 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) })
                }
            }
        }
    }
}

可选:调整布局

要调整轮播的布局,请按照以下步骤操作

  1. Carousel 可组合函数中,使用 MaterialTheme.colorScheme.background 值为 backgroundColor 值赋值
  2. 使用 Box 可组合函数包装 Column 可组合函数
  3. Alignment.BottomStart 值传递给 Box 组件的 contentAlignment 参数。
  4. fillMaxSize 修饰符传递给 Box 可组合函数的修饰符参数。 fillMaxSize 修饰符是使用 Modifier.fillMaxSize() 函数创建的。
  5. 在传递给 Box 可组合函数的 fillMaxSize 修饰符上调用 drawBehind() 方法
  6. 在传递给 drawBehind 修饰符的 lambda 中,使用通过调用 Brush 对象创建的 brush 值,该对象是通过调用 Brush.linearGradient 函数使用两个 Color 对象列表创建的。该列表是通过使用 backgroundColor 值和 Color.Transparent 值调用 listOf 函数创建的。
  7. 在传递给 drawBehind 修饰符的 lambda 中,使用 brush 对象调用 drawRect,以在背景图像上创建 srim 层
  8. 使用通过调用 Modifier.padding 使用 20.dp 值创建的 padding 修饰符指定 Column 可组合函数的填充。
  9. Column 可组合函数中,在 Text 可组合函数和 Button 可组合函数之间添加一个 Spacer 可组合函数,其值为 20.dp

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. 获取解决方案代码

要下载此代码实验室的解决方案代码,请执行以下操作之一

  • 单击以下按钮将其下载为 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 来实现显示内容列表的屏幕。
  • 用于显示内容详细信息的基本屏幕实现。
  • 如何在两个屏幕之间添加屏幕过渡。