1. 准备工作
Compose for TV 是用于开发在 Android TV 上运行的应用的最新应用框架。它融合了适用于 TV 应用的 Jetpack Compose 的所有优势,可让您更轻松地为应用构建功能强大且美观出众的界面。Compose for TV 的一些具体优势包括:
- 灵活性。Compose 可用于创建任何类型的界面,从简单的布局到复杂的动画。组件是可直接使用的,但也可以进行自定义和样式设置,以满足您的应用的需求。
- 简化并加速开发。Compose 与现有代码兼容,让开发者可以使用更少的代码构建应用。
- 直观:Compose 使用声明式语法,直观地更改界面以及调试、理解和审核代码。
TV 应用的常见应用场景是媒体使用。用户浏览内容目录并选择想要观看的内容。这些内容可以是电影、电视节目或播客。用户选择一段内容后,他们可能需要查看更多信息,例如简短说明、播放时长和创作者的姓名。在本 Codelab 中,您将学习如何使用 Compose for TV 实现目录浏览器页面和详情页面。
前提条件
- 有使用 Kotlin 语法(包括 lambda)的经验。
- 有使用 Compose 的基本经验。如果您不熟悉 Compose,请先完成 Jetpack Compose 基础知识 Codelab。
- 具备可组合函数和修饰符方面的基础知识。
- 用于运行示例应用的以下任意设备:- Android TV 设备
- Android 虚拟设备,包含属于 TV 设备定义类别的配置文件。
 
构建内容
- 您将构建一个视频播放器应用,其中包含一个目录浏览器页面和一个详情页面。
- 目录浏览器页面将显示一系列视频供用户选择,如下图所示:

- 详情页面会显示所选视频的元数据,例如标题、说明和时长,如下图所示:

所需条件
- 最新版本的 Android Studio
- 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.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 列表的数据流。该数据流是以 ComposeState 对象的形式收集的(通过调用其 collectAsStateWithLifecycle 方法)。Category 对象具有 name 属性,该属性是表示类别名称的 String 值。
如需显示类别名称,请按以下步骤操作:
- 在 Android Studio 中,打开起始代码的 CatalogBrowser.kt文件,然后将LazyColumn可组合函数添加到CatalogBrowser可组合函数。
- 调用 catalogBrowserViewModel.categoryList.collectAsStateWithLifeCycle()方法以State对象的形式收集数据流。
- 将 categoryList声明为您在上一步中创建的State对象的委托属性。
- 使用 categoryList变量作为参数来调用items函数。
- 使用类别名称作为参数调用 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 对象,表示属于此类别的电影。
如需显示各个类别的内容列表,请按以下步骤操作:
- 添加 LazyRow可组合函数,然后向其传递一个 lambda。
- 在 lambda 中,使用 category.movieList属性值调用items函数,然后向其传递一个 lambda。
- 在传递给 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)
                }
            }
        }
    }
}
可选:调整布局
- 如需设置类别之间的间隔,请将 Arrangement对象作为verticalArrangement参数传递给LazyColumn可组合函数。可通过调用Arrangement#spacedBy方法来创建Arrangement对象。
- 如需设置电影卡片之间的间隔,请将 Arrangement对象作为horizontalArrangement参数传递给LazyRow可组合函数。
- 如需为列设置缩进,请将 PaddingValue对象作为contentPadding参数传递。
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:影片的简短摘要。
如需在详情页面上显示此元数据,请按以下步骤操作:
- 添加一个 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可组合函数的封装容器,其中包含通过Details可组合函数传递的modifier对象。
- 在 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属性设置为第二个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 可组合函数的布局,请按以下步骤操作:
- 设置 Box可组合函数,通过fillMaxSize修饰符使用整个可用空间
- 使用 background修饰符设置Box可组合函数的背景,以便使用通过调用Brush.linearGradient函数(使用一系列包含MaterialTheme.colorScheme.background值和Color.Transparent的Color对象)创建的线性渐变来填充背景。
- 使用 padding修饰符将Column可组合函数周围的水平间距设置为48.dp,垂直间距设置为24.dp
- 使用 width修饰符设置Column可组合函数的宽度,该修饰符是通过调用Modifier.width函数(使用0.5f值)创建的
- 使用 Spacer在第二个Text可组合函数和第三个Text可组合项之间添加8.dp的空间。使用通过Modifier.height函数创建的height修饰符指定Spacer可组合函数的高度
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 监听器。当用户按方向键的中心按钮时,将调用 CatalogBrowserViewModel#showDetails 方法(使用与 MovieCard 可组合函数关联的影片对象作为参数)。
- 打开 com.example.tvcomposeintroduction.ui.screens.CatalogBrowser文件。
- 使用 onClick参数将 lambda 函数传递给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. 在目录浏览器页面中添加轮播界面,以突出显示精选内容
轮播界面是一个自适应调整的界面组件,会在指定时长后自动更新其幻灯片,通常用于突出显示精选内容。
如需将轮播界面添加到目录浏览器页面以突出显示精选内容列表中的电影,请按以下步骤操作:
- 打开 com.example.tvcomposeintroduction.ui.screens.CatalogBrowser文件。
- 调用 item函数,为LazyColumn可组合函数添加项。
- 在传递给 item函数的 lambda 中将featuredMovieList声明为委托属性,然后将State对象设置为委托对象,后者是从catalogBrowserViewModel.featuredMovieList属性收集的。
- 在 item函数中调用Carousel可组合函数,然后传入以下参数:
- 通过 slideCount参数设置的featuredMovieList变量的大小。
- 使用 Modifier.fillMaxWidth和Modifier.height方法指定轮播界面大小的Modifier对象。Carousel可组合函数通过将376.dp值传递给Modifier.height方法,设置 376 dp 的高度。
- 通过整数值调用的 lambda,用于指明可见轮播项的索引。
- 通过 featuredMovieList变量和指定的索引值检索Movie对象。
- 为 Carousel可组合函数添加Box可组合函数。
- 为 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 可组合函数会将一个组件放在另一个组件上。如需了解详情,请参阅布局基础知识。
如需显示背景图片,请按以下步骤操作:
- 先调用 AsyncImage可组合函数,加载与Movie对象关联的背景图片,然后再调用Text可组合函数。
- 更新 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可组合函数
- 在 Button可组合函数中调用Text可组合函数,并使用R.string.show_details调用的stringResource函数的返回值。
- 在传递给 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) })
                }
            }
        }
    }
}
可选:调整布局
如需调整轮播界面的布局,请按以下步骤操作:
- 在 Carousel可组合函数中使用MaterialTheme.colorScheme.background值分配backgroundColor值
- 使用 Box可组合函数封装Column可组合函数
- 将 Alignment.BottomStart值传递给Box组件的contentAlignment参数。
- 将 fillMaxSize修饰符传递给Box可组合函数的修饰符参数。fillMaxSize修饰符是使用Modifier.fillMaxSize()函数创建的。
- 对传递给 Box可组合函数的fillMaxSize修饰符调用drawBehind()方法
- 在传递给 drawBehind修饰符的 lambda 中,使用Brush对象分配brush值,该对象是通过调用Brush.linearGradient函数(使用包含两个Color对象的列表)创建的。上述列表是通过调用listOf函数(使用backgroundColor值和Color.Transparent值)创建的。
- 使用传递给 drawBehind修饰符的 lambda 中的brush对象调用drawRect,在背景图片上制作 srim 层
- 使用 padding修饰符指定Column可组合函数的内边距,该修饰符是通过调用Modifier.padding(使用20.dp值)创建的。
- 在 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 实现一个显示内容列表的页面。
- 用于显示内容详情的基本页面实现。
- 如何在两个页面之间添加页面转换。
