Compose for TV 简介

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 语法,包括 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属性访问类别列表,这是一个flowCategory列表。通过调用其collectAsStateWithLifecycle方法,该 flow 将作为Compose State对象收集。Category对象具有name属性,这是一个表示类别名称的String值。

要显示类别名称,请按照以下步骤操作:

  1. 在 Android Studio 中,打开入门代码的CatalogBrowser.kt文件,然后在CatalogBrowser可组合函数中添加一个LazyColumn可组合函数。
  2. 调用catalogBrowserViewModel.categoryList.collectAsStateWithLifeCycle()方法将 flow 作为State对象收集。
  3. categoryList声明为在上一步骤中创建的State对象的委托属性。
  4. 使用categoryList变量作为参数调用items函数。
  5. 使用类别名称作为 lambda 参数传递的参数调用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 对象列表,表示属于该类别的电影。

要显示每个类别的内容列表,请按照以下步骤操作

  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. 要设置类别之间的间距,请使用 verticalArrangement 参数向 LazyColumn 可组合函数传递一个 Arrangement 对象。通过调用 Arrangement#spacedBy 方法创建 Arrangement 对象。
  2. 要设置电影卡片之间的间距,请使用 horizontalArrangement 参数向 LazyRow 可组合函数传递一个 Arrangement 对象。
  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. 使用 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 可组合函数的布局,请按照以下步骤操作

  1. 使用 fillMaxSize 修饰符设置 Box 可组合函数以使用整个可用空间
  2. 使用 background 修饰符设置 Box 可组合函数的背景,以使用线性渐变填充背景,该线性渐变是通过使用包含 MaterialTheme.colorScheme.background 值和 Color.TransparentColor 对象列表调用 Brush.linearGradient 函数创建的。
  3. 使用 padding 修饰符设置 Column 可组合函数周围 48.dp 的水平间距和 24.dp 的垂直间距。
  4. 使用通过使用 0.5f 值调用 Modifier.width 函数创建的 width 修饰符设置 Column 可组合函数的宽度。
  5. 使用 Spacer 在第二个 Text 可组合函数和第三个 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 修饰符向 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. 使用 stringResource 函数(使用 R.string.show_details 调用)的返回值,在 Button 组合函数中调用 Text 组合函数。
  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.linearGradient 函数(包含两个 Color 对象的列表)创建的 Brush 对象为 brush 赋值。该列表是用 listOf 函数,以及 backgroundColor 值和 Color.Transparent 值创建的。
  7. 在传递给 drawBehind 修饰符的 lambda 表达式中,使用 brush 对象调用 drawRect,在背景图像上创建一个渐变层。
  8. 使用调用 Modifier.padding 函数(使用 20.dp 值)创建的 padding 修饰符指定 Column 组合函数的填充。
  9. Column 组合函数中,在 Text 组合函数和 Button 组合函数之间添加一个值为 20.dpSpacer 组合函数。

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 和 LazyRow 来实现显示内容列表的屏幕。
  • 显示内容详细信息的基本屏幕实现。
  • 如何在两个屏幕之间添加屏幕过渡动画。