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 虚拟设备
您将构建的内容
- 一个带目录浏览器屏幕和详细信息屏幕的视频播放器应用。
- 一个目录浏览器屏幕,显示用户可以选择的一系列视频。它看起来像下面的图片
- 一个详细信息屏幕,显示所选视频的元数据,例如标题、说明和长度。它看起来像下面的图片
您需要的内容
- 最新版本的Android Studio
- Android TV 设备或 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
属性访问类别列表,这是一个flow的Category
列表。通过调用其collectAsStateWithLifecycle
方法,该 flow 将作为Compose State
对象收集。Category
对象具有name
属性,这是一个表示类别名称的String
值。
要显示类别名称,请按照以下步骤操作:
- 在 Android Studio 中,打开入门代码的
CatalogBrowser.kt
文件,然后在CatalogBrowser
可组合函数中添加一个LazyColumn
可组合函数。 - 调用
catalogBrowserViewModel.categoryList.collectAsStateWithLifeCycle()
方法将 flow 作为State
对象收集。 - 将
categoryList
声明为在上一步骤中创建的State
对象的委托属性。 - 使用
categoryList
变量作为参数调用items
函数。 - 使用类别名称作为 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
对象列表,表示属于该类别的电影。
要显示每个类别的内容列表,请按照以下步骤操作
- 添加
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)
}
}
}
}
}
可选:调整布局
- 要设置类别之间的间距,请使用
verticalArrangement
参数向LazyColumn
可组合函数传递一个Arrangement
对象。通过调用Arrangement#spacedBy
方法创建Arrangement
对象。 - 要设置电影卡片之间的间距,请使用
horizontalArrangement
参数向LazyRow
可组合函数传递一个Arrangement
对象。 - 要设置列的缩进,请使用
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
。电影的简短摘要。
要在详情屏幕上显示此元数据,请按照以下步骤操作
- 添加一个
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
可组合函数的最大大小。 - 向
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
对象包含用于引用当前主题值的函数,例如 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
可组合函数的布局,请按照以下步骤操作
- 使用
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
在第二个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
方法。
- 打开
com.example.tvcomposeintroduction.ui.screens.CatalogBrowser
文件。 - 使用
onClick
参数向MovieCard
可组合函数传递一个 lambda 函数。 - 使用与
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
函数的 lambda 表达式中声明featuredMovieList
作为委托属性,然后设置要委托的State
对象,该对象是从catalogBrowserViewModel.featuredMovieList
属性收集的。 - 在
item
函数内调用Carousel
可组合函数,然后传入以下参数
- 通过
slideCount
参数传递featuredMovieList
变量的大小。 - 一个
Modifier
对象,用于使用Modifier.fillMaxWidth
和Modifier.height
方法指定轮播大小。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
组合函数将一个组件叠加在另一个组件之上。详情请参考 布局基础知识。
要显示背景图像,请按照以下步骤操作
- 在
Text
组合函数之前,调用AsyncImage
组合函数加载与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
组合函数 - 使用
stringResource
函数(使用R.string.show_details
调用)的返回值,在Button
组合函数中调用Text
组合函数。 - 在传递给
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.linearGradient
函数(包含两个Color
对象的列表)创建的Brush
对象为brush
赋值。该列表是用listOf
函数,以及backgroundColor
值和Color.Transparent
值创建的。 - 在传递给
drawBehind
修饰符的 lambda 表达式中,使用brush
对象调用drawRect
,在背景图像上创建一个渐变层。 - 使用调用
Modifier.padding
函数(使用20.dp
值)创建的padding
修饰符指定Column
组合函数的填充。 - 在
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. 获取解决方案代码
要下载此代码实验室的解决方案代码,请执行以下操作之一:
- 点击以下按钮将其下载为 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 来实现显示内容列表的屏幕。
- 显示内容详细信息的基本屏幕实现。
- 如何在两个屏幕之间添加屏幕过渡动画。