1. 开始之前
Compose for TV 是用于开发在 Android TV 上运行的应用的最新 UI 框架。它为 TV 应用解锁了 Jetpack Compose 的所有优势,让您的应用更轻松地构建美观且功能强大的 UI。Compose for TV 的一些特定优势包括:
- 灵活性。Compose 可用于创建任何类型的 UI,从简单的布局到复杂的动画。组件开箱即用,但也可以根据您的应用需求进行自定义和样式设置。
- 简化和加速开发。Compose 与现有代码兼容,并允许开发者用更少的代码构建应用。
- 直观性:Compose 使用声明式语法,使您能够直观地更改 UI,并调试、理解和审查代码。
电视应用的一个常见用例是媒体消费。用户浏览内容目录并选择他们想要观看的内容。内容可以是电影、电视节目或播客。用户选择内容后,他们可能希望查看更多相关信息,例如简短描述、播放时长和创作者姓名。在本 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 属性访问类别列表,该属性是 Category 列表的流。通过调用其 collectAsStateWithLifecycle 方法,该流被收集为 Compose State 对象。Category 对象具有 name 属性,该属性是一个表示类别名称的 String 值。
要显示类别名称,请按照以下步骤操作:
- 在 Android Studio 中,打开起始代码的
CatalogBrowser.kt文件,然后将一个LazyColumn可组合函数添加到CatalogBrowser可组合函数中。 - 调用
catalogBrowserViewModel.categoryList.collectAsStateWithLifeCycle()方法将流收集为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)
}
}
}
}
}
可选:调整布局
- 要设置类别之间的间距,请将
Arrangement对象以及verticalArrangement参数传递给LazyColumn可组合函数。通过调用Arrangement#spacedBy方法来创建Arrangement对象。 - 要设置电影卡之间的间距,请将
Arrangement对象以及horizontalArrangement参数传递给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 对象具有以下三个字符串属性作为电影的元数据:
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参数。 - 向
contentDescription参数传递null。
- 向
contentScale参数传递ContentScale.Crop对象。要查看不同的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垂直间距 - 使用通过调用
Modifier.width函数并传递0.5f值来创建的width修饰符设置Column可组合函数的宽度 - 在第二个
Text可组合函数和第三个Text可组合函数之间添加8.dp的间距,使用Spacer。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 监听器。当方向键的中心按钮被按下时,将调用 CatalogBrowserViewModel#showDetails 方法,并将与 MovieCard 可组合函数关联的电影对象作为参数。
- 打开
com.example.tvcomposeintroduction.ui.screens.CatalogBrowser文件。 - 将 lambda 函数与
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函数的 lambda 表达式中,将featuredMovieList声明为委托属性,然后设置要委托的State对象,该对象是从catalogBrowserViewModel.featuredMovieList属性收集的。 - 在
item函数内部调用Carousel可组合函数,然后传入以下参数:
- 通过
slideCount参数传递featuredMovieList变量的大小。 - 一个
Modifier对象,用于使用Modifier.fillMaxWidth和Modifier.height方法指定轮播大小。Carousel可组合函数通过向Modifier.height方法传递376.dp值来使用 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可组合函数 - 在
Button可组合函数中,使用调用stringResource函数并传入R.string.show_details获得的返回值来调用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可组合函数中,将backgroundColor值赋为MaterialTheme.colorScheme.background值 - 用
Box可组合项包装Column可组合函数 - 将
Alignment.BottomStart值传递给Box组件的contentAlignment参数。 - 将
fillMaxSize修饰符传递给Box可组合函数的 modifier 参数。fillMaxSize修饰符是使用Modifier.fillMaxSize()函数创建的。 - 在传递给
Box可组合函数的fillMaxSize修饰符之上调用drawBehind()方法 - 在传递给
drawBehind修饰符的 lambda 表达式中,将brush值赋给通过调用Brush.linearGradient函数并传递两个Color对象的列表来创建的Brush对象。该列表是通过调用listOf函数并传递backgroundColor值和Color.Transparent值来创建的。 - 在传递给
drawBehind修饰符的 lambda 表达式中调用drawRect并传入brush对象,以便在背景图像上方创建一个蒙版层 - 使用通过调用
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. 获取解决方案代码
要下载此 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 和 LazyRow 实现显示内容列表的屏幕。
- 显示内容详细信息的基本屏幕实现。
- 如何在两个屏幕之间添加屏幕转换。