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 Studio
2. 进行设置
如需获取包含此 Codelab 的主题和基本设置的代码,请执行以下任一操作:
- 从此 GitHub 代码库克隆代码:
$ git clone https://github.com/android/tv-codelabs.git
main
分支包含起始代码,solution
分支包含解决方案代码。
- 下载
main.zip
文件(包含起始代码)和solution.zip
文件(包含解决方案代码)。
现在,您已下载相应代码,请在 Android Studio 中打开 IntroductionToComposeForTV 项目文件夹。现已准备就绪,可以开始开发项目了。
3. 实现目录浏览器页面
用户可以在目录浏览器页面上浏览电影目录。您将目录浏览器实现为 Composable
函数。您可以在 CatalogBrowser.kt
文件中找到 CatalogBrowser
Composable
函数。您将在此 Composable
函数中实现目录浏览器页面。
起始代码有一个名为 CatalogBrowserViewModel
类的 ViewModel,其中包含多个属性和方法,可用于检索描述电影内容的 Movie
对象。您需要使用检索到的 Movie
对象来实现一个目录浏览器。
CatalogBrowser.kt
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.tv.material3.ExperimentalTvMaterial3Api
import com.example.tvcomposeintroduction.data.Movie
@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
modifier: Modifier = Modifier,
catalogBrowserViewModel: CatalogBrowserViewModel = viewModel(),
onMovieSelected: (Movie) -> Unit = {}
) {
}
显示类别名称
您可以使用 catalogBrowserViewModel.categoryList
属性访问类别列表,该属性是 Category
列表的流程。该流程通过调用 Compose State
对象来收集,方法是调用其 collectAsState
方法。Category
对象具有 name
属性,该属性是表示类别名称的 String
值。
若想显示类别名称,请按以下步骤操作:
- 在 Android Studio 中,打开起始代码的
CatalogBrowser.kt
文件,然后将TvLazyColumn
Composable
函数添加到CatalogBrowser
Composable
函数。 - 调用
catalogBrowserViewModel.categoryList.collectAsState()
方法以State
对象的形式收集数据流。 - 将
categoryList
声明为您在上一步中创建的State
对象的委托属性。 - 使用
categoryList
变量作为参数来调用items
函数。 - 使用类别名称作为参数调用
Text
Composable
函数,该参数将作为 lambda 的参数传递。
CatalogBrowser.kt
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.tv.foundation.lazy.list.TvLazyColumn
import androidx.tv.foundation.lazy.list.items
import androidx.tv.material3.ExperimentalTvMaterial3Api
import androidx.tv.material3.Text
import com.example.tvcomposeintroduction.data.Movie
@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
modifier: Modifier = Modifier,
catalogBrowserViewModel: CatalogBrowserViewModel = viewModel(),
onMovieSelected: (Movie) -> Unit = {}
) {
val categoryList by
catalogBrowserViewModel.categoryList.collectAsState()
TvLazyColumn(modifier = modifier) {
items(categoryList) { category ->
Text(text = category.name)
}
}
}
显示每个类别的内容列表
Category
对象有另一个名为 movieList
的属性。该属性是一系列 Movie
对象,表示属于此类别的电影。
如需显示各个类别的内容列表,请按以下步骤操作:
- 添加
TvLazyRow
Composable
函数,然后向其传递一个 lambda。 - 在 lambda 中,使用
category
.movieList
属性值调用items
函数,然后向其传递一个 lambda。 - 在传递给
items
函数的 lambda 中,使用Movie
对象调用MovieCard
Composable
函数。
CatalogBrowser.kt
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.tv.foundation.lazy.list.TvLazyColumn
import androidx.tv.foundation.lazy.list.TvLazyRow
import androidx.tv.foundation.lazy.list.items
import androidx.tv.material3.ExperimentalTvMaterial3Api
import androidx.tv.material3.Text
import com.example.tvcomposeintroduction.data.Movie
import com.example.tvcomposeintroduction.ui.components.MovieCard
@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
modifier: Modifier = Modifier,
catalogBrowserViewModel: CatalogBrowserViewModel = viewModel(),
onMovieSelected: (Movie) -> Unit = {}
) {
val categoryList by
catalogBrowserViewModel.categoryList.collectAsState()
TvLazyColumn(modifier = modifier) {
items(categoryList) { category ->
Text(text = category.name)
TvLazyRow {
items(category.movieList) {movie ->
MovieCard(movie = movie)
}
}
}
}
}
可选:调整布局
- 如需设置类别之间的间隔,请将
Arrangement
对象作为verticalArrangement
参数传递给TvLazyColumn
Composable
函数。可通过调用Arrangement#spacedBy
方法来创建Arrangement
对象。 - 如需设置电影卡片之间的间隔,请将
Arrangement
对象作为horizontalArrangement
参数传递给TvLazyRow
Composable
函数。 - 如需为列设置缩进,请将
PaddingValue
对象作为contentPadding
参数传递。
CatalogBrowser.kt
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.tv.foundation.lazy.list.TvLazyColumn
import androidx.tv.foundation.lazy.list.TvLazyRow
import androidx.tv.foundation.lazy.list.items
import androidx.tv.material3.ExperimentalTvMaterial3Api
import androidx.tv.material3.Text
import com.example.tvcomposeintroduction.data.Movie
import com.example.tvcomposeintroduction.ui.components.MovieCard
@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
modifier: Modifier = Modifier,
catalogBrowserViewModel: CatalogBrowserViewModel = viewModel(),
onMovieSelected: (Movie) -> Unit = {}
) {
val categoryList by
catalogBrowserViewModel.categoryList.collectAsState()
TvLazyColumn(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(16.dp),
contentPadding = PaddingValues(horizontal = 48.dp, vertical = 32.dp)
) {
items(categoryList) { category ->
Text(text = category.name)
TvLazyRow(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
items(category.movieList) { movie ->
MovieCard(movie = movie)
}
}
}
}
}
4. 实现详情页面
详情页面会显示所选影片的详细信息。Details.kt
文件中有一个 Details
Composable
函数。您需要为此函数添加代码,以实现详情页面。
Details.kt
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.tv.material3.ExperimentalTvMaterial3Api
import com.example.tvcomposeintroduction.data.Movie
@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun Details(movie: Movie, modifier: Modifier = Modifier) {
}
显示影片名、工作室名称和说明
Movie
对象有以下三个字符串属性作为电影的元数据:
title
:影片名。studio
:制作该电影的工作室名称。description
:影片的简短摘要。
如需在详情页面上显示此元数据,请按以下步骤操作:
- 添加一个
Column
Composable
函数,然后使用Modifier.padding
方法创建Modifier
对象,将其设置为列的垂直间距为 32 dp,水平间距为 48 dp。 - 添加
Text
Composable
函数以显示影片名。 - 添加
Text
Composable
函数以显示工作室名称。 - 添加
Text
Composable
函数以显示电影说明。
Details.kt
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.tv.material3.ExperimentalTvMaterial3Api
import androidx.tv.material3.Text
import com.example.tvcomposeintroduction.data.Movie
@OptIn(ExperimentalTvMaterial3Api::class)
@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.title)
}
}
在 Details
Composable
函数的参数中指定的 Modifier
对象会在下一个任务中使用。
显示与给定的 Movie
对象关联的背景图片
Movie
对象具有 backgroundImageUrl
属性,该属性指示了该对象描述的电影的背景图片位置。
若想显示指定影片的背景图片,请按以下步骤操作:
- 添加
Box
Composable
函数作为Column
Composable
函数的封装容器,其中modifier
对象通过Details
Composable
函数传递。 - 在
Box
Composable
函数中,调用modifier
对象的fillMaxSize
方法,使Box
Composable
函数填充最大的大小,可以分配给Details
Composable
函数。 - 将包含以下参数的
AsyncImage
Composable
函数添加到Box
Composable
函数中:
- 将给定
Movie
对象的backgroundImageUrl
属性的值设置为model
参数。 - 将
null
传递给contentDescription
参数。
- 将
ContentScale.Crop
对象传递给contentScale
参数。若想查看不同的ContentScale
选项,请参阅内容缩放。 - 将
Modifier.fillMaxSize
方法的返回值传递给modifier
参数。 - 设置通过调用
Modifier.padding
方法创建的Modifier
对象,将其设置为列的垂直间距为 32 dp,水平间距为 48 dp。
Details.kt
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp
import androidx.tv.material3.ExperimentalTvMaterial3Api
import androidx.tv.material3.Text
import coil.compose.AsyncImage
import com.example.tvcomposeintroduction.data.Movie
@OptIn(ExperimentalTvMaterial3Api::class)
@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
.padding(vertical = 32.dp, horizontal = 48.dp)
) {
Text(
text = movie.title,
)
Text(
text = movie.studio,
)
Text(
text = movie.title,
)
}
}
}
引用 MaterialTheme
对象以确保一致的主题设置
MaterialTheme
对象包含引用当前主题值的函数,例如 Typography
和 [ColorScheme
][ColorScheme] 类中的函数。
如需引用 MaterialTheme
对象以确保一致的主题设置,请按以下步骤操作:
- 将
MaterialTheme.typography.headlineLarge
属性设置为影片名的文本样式。 - 将
MaterialTheme.typography.headlineMedium
属性设置为其他两个Text
Composable
函数的文本样式。 - 使用
Modifier.background
方法将MaterialTheme.colorScheme.background
属性设置为Column
Composable
函数的背景颜色。
[ColorScheme]: /reference/kotlin/androidx/tv/material3/ColorScheme)
Details.kt
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp
import androidx.tv.material3.ExperimentalTvMaterial3Api
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text
import coil.compose.AsyncImage
import com.example.tvcomposeintroduction.data.Movie
@OptIn(ExperimentalTvMaterial3Api::class)
@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
.padding(vertical = 32.dp, horizontal = 48.dp)
) {
Text(
text = movie.title,
style = MaterialTheme.typography.headlineLarge,
)
Text(
text = movie.studio,
style = MaterialTheme.typography.headlineMedium,
)
Text(
text = movie.title,
style = MaterialTheme.typography.headlineMedium,
)
}
}
}
5. 添加界面之间的导航
现在,您已拥有目录浏览器页面和详情页面。用户在目录浏览器页面上选择内容后,相应页面必须转换为详情页面。为此,您可以使用 clickable
修饰符将 event
监听器添加到 MovieCard
Composable
函数。当用户按方向键的中心按钮时,使用与 MovieCard
Composable
函数关联的电影对象调用 CatalogBrowserViewModel#showDetails
方法。
- 打开
com.example.tvcomposeintroduction.ui.screens.CatalogBrowser
文件。 - 使用
onClick
参数将 lambda 函数传递给MovieCard
Composable
函数。 - 使用与
MovieCard
Composable
函数关联的电影对象调用onMovieSelected
回调。
CatalogBrowser.kt
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.tv.foundation.lazy.list.TvLazyColumn
import androidx.tv.foundation.lazy.list.TvLazyRow
import androidx.tv.foundation.lazy.list.items
import androidx.tv.material3.ExperimentalTvMaterial3Api
import androidx.tv.material3.Text
import com.example.tvcomposeintroduction.data.Movie
import com.example.tvcomposeintroduction.ui.components.MovieCard
@Composable
fun CatalogBrowser(
modifier: Modifier = Modifier,
catalogBrowserViewModel: CatalogBrowserViewModel = viewModel(),
onMovieSelected: (Movie) -> Unit = {}
) {
val categoryList by
catalogBrowserViewModel.categoryList.collectAsState()
TvLazyColumn(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(16.dp),
contentPadding = PaddingValues(horizontal = 48.dp, vertical = 32.dp)
) {
items(categoryList) { category ->
Text(text = category.name)
TvLazyRow(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
items(category.movieList) { movie ->
MovieCard(movie = movie, onClick = { onMovieSelected(movie) })
}
}
}
}
}
6. 在目录浏览器页面中添加轮播界面,以突出显示精选内容
轮播界面是一个自适应调整的界面组件,会在指定时长后自动更新其幻灯片,通常用于突出显示精选内容。
如需将轮播界面添加到目录浏览器页面以突出显示精选内容列表中的电影,请按以下步骤操作:
- 打开
com.example.tvcomposeintroduction.ui.screens.CatalogBrowser
文件。 - 调用
item
函数,以向TvLazyColumn
Composable
函数添加项。 - 在传递给
item
函数的 lambda 中将featuredMovieList
声明为委托属性,然后将State
对象设置为委托,后者是从catalogBrowserViewModel.featuredMovieList
属性收集的。 - 在
item
函数中调用Carousel
Composable
函数,然后传入以下参数:
- 通过
slideCount
参数设置的featuredMovieList
变量的大小。 - 使用
Modifier.fillMaxWidth
和Modifier.height
方法指定轮播界面大小的Modifier
对象。Carousel
Composable
函数通过将376.dp
值传递给Modifier.height
方法来设置 376 dp 的高度。 - 通过整数值调用的 lambda 表示可见轮播项的索引。
- 通过
featuredMovieList
变量和指定的索引值中检索Movie
对象。 - 将
CarouselSlide
Composable
函数添加到Carousel
Composable
函数。 - 向
CarouselSlide
Composable
函数添加Text
Composable
函数,以显示影片名。
CatalogBrowser.kt
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.tv.foundation.lazy.list.TvLazyColumn
import androidx.tv.foundation.lazy.list.TvLazyRow
import androidx.tv.foundation.lazy.list.items
import androidx.tv.material3.Carousel
import androidx.tv.material3.ExperimentalTvMaterial3Api
import androidx.tv.material3.Text
import com.example.tvcomposeintroduction.data.Movie
import com.example.tvcomposeintroduction.ui.components.MovieCard
@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
modifier: Modifier = Modifier,
catalogBrowserViewModel: CatalogBrowserViewModel = viewModel(),
onMovieSelected: (Movie) -> Unit = {}
) {
val categoryList by
catalogBrowserViewModel.categoryList.collectAsState()
TvLazyColumn(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(16.dp),
contentPadding = PaddingValues(horizontal = 48.dp, vertical = 32.dp)
) {
item {
val featuredMovieList by catalogBrowserViewModel.featuredMovieList.collectAsState()
Carousel(
slideCount = featuredMovieList.size,
modifier = Modifier
.fillMaxWidth()
.height(376.dp)
) { indexOfCarouselSlide ->
val featuredMovie =
featuredMovieList[indexOfCarouselSlide]
CarouselSlide {
Text(text = featuredMovie.title)
}
}
}
items(categoryList) { category ->
Text(text = category.name)
TvLazyRow(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
items(category.movieList) { movie ->
MovieCard(movie = movie, onClick = { onMovieSelected(movie) })
}
}
}
}
}
显示背景图片
CarouselSlide
Composable
函数可以接受另一个 lambda,以指定 CarouselSlide
Composable
函数的背景的显示方式。
如需显示背景图片,请按以下步骤操作:
- 将 lambda 传递给带有
background
参数的CarouselSlide
Composable
函数。 - 调用
AsyncImage
Composable
函数,将与Movie
对象相关联的背景图片加载到CarouselSlide
Composable
函数的背景。 - 更新
CarouselSlide
Composable
函数中Text
Composable
函数的位置和文本样式,以提高曝光度。 - 为
AsyncImage
Composable
函数设置占位符以避免布局偏移。起始代码有一个占位符作为可绘制对象,您可以使用R.drawable.placeholder
引用该可绘制对象。
CatalogBrowser.kt
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.tv.foundation.lazy.list.TvLazyColumn
import androidx.tv.foundation.lazy.list.TvLazyRow
import androidx.tv.foundation.lazy.list.items
import androidx.tv.material3.Carousel
import androidx.tv.material3.ExperimentalTvMaterial3Api
import androidx.tv.material3.Text
import coil.compose.AsyncImage
import com.example.tvcomposeintroduction.R
import com.example.tvcomposeintroduction.data.Movie
import com.example.tvcomposeintroduction.ui.components.MovieCard
@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
modifier: Modifier = Modifier,
catalogBrowserViewModel: CatalogBrowserViewModel = viewModel(),
onMovieSelected: (Movie) -> Unit = {}
) {
val categoryList by
catalogBrowserViewModel.categoryList.collectAsState()
TvLazyColumn(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(16.dp),
contentPadding = PaddingValues(horizontal = 48.dp, vertical = 32.dp)
) {
item {
val featuredMovieList by catalogBrowserViewModel.featuredMovieList.collectAsState()
Carousel(
slideCount = featuredMovieList.size,
modifier = Modifier
.fillMaxWidth()
.height(376.dp),
) { indexOfCarouselItem ->
val featuredMovie = featuredMovieList[indexOfCarouselItem]
CarouselSlide(
background = {
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)
TvLazyRow(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
items(category.movieList) { movie ->
MovieCard(movie = movie, onClick = { onMovieSelected(movie) })
}
}
}
}
}
向详情页面添加屏幕转换
您可以让用户点击 CarouselSlide
Composable
函数。
若想让用户在详情页面的可见轮播界面中查看影片详情,请按以下步骤操作:
- 通过
modifier
参数将Modifier.clickable
方法的返回值传递给CarouselSlide
Composable
函数。 - 在传递给
Modifier.clickable
方法的 lambda 中,使用可见的CarouselSlide
Composable
函数的Movie
对象调用onMovieSelected
函数。
CatalogBrowser.kt
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.tv.foundation.lazy.list.TvLazyColumn
import androidx.tv.foundation.lazy.list.TvLazyRow
import androidx.tv.foundation.lazy.list.items
import androidx.tv.material3.Carousel
import androidx.tv.material3.ExperimentalTvMaterial3Api
import androidx.tv.material3.Text
import coil.compose.AsyncImage
import com.example.tvcomposeintroduction.R
import com.example.tvcomposeintroduction.data.Movie
import com.example.tvcomposeintroduction.ui.components.MovieCard
@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
modifier: Modifier = Modifier,
catalogBrowserViewModel: CatalogBrowserViewModel = viewModel(),
onMovieSelected: (Movie) -> Unit = {}
) {
val categoryList by
catalogBrowserViewModel.categoryList.collectAsState()
TvLazyColumn(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(16.dp),
contentPadding = PaddingValues(horizontal = 48.dp, vertical = 32.dp)
) {
item {
val featuredMovieList by catalogBrowserViewModel.featuredMovieList.collectAsState()
Carousel(
slideCount = featuredMovieList.size,
modifier = Modifier
.fillMaxWidth()
.height(376.dp),
) { indexOfCarouselItem ->
val featuredMovie = featuredMovieList[indexOfCarouselItem]
CarouselSlide(
background = {
AsyncImage(
model = featuredMovie.backgroundImageUrl,
contentDescription = null,
placeholder = painterResource(
id = R.drawable.placeholder
),
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize(),
)
},
modifier = Modifier.clickable { onMovieSelected(featuredMovie) }
) {
Text(text = featuredMovie.title)
}
}
}
items(categoryList) { category ->
Text(text = category.name)
TvLazyRow(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
items(category.movieList) { movie ->
MovieCard(movie = movie, onClick = { onMovieSelected(movie) })
}
}
}
}
}
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 的基础知识:
- 如何通过组合 TvLazyColumn 和 TvLazyLow 实现一个显示内容列表的页面。
- 用于显示内容详情的基本页面实现。
- 如何在两个页面之间添加页面转换。