1. Trước khi bắt đầu
Compose cho TV là khung giao diện người dùng mới nhất để phát triển các ứng dụng chạy trên Android TV. Điều này giúp tận dụng toàn bộ lợi ích của Jetpack Compose cho các ứng dụng truyền hình, giúp bạn dễ dàng xây dựng giao diện người dùng đẹp có đầy đủ chức năng. Sau đây là một số lợi ích cụ thể của Compose dành cho TV:
- Tính linh hoạt. Bạn có thể dùng Compose để tạo mọi loại giao diện người dùng với bố cục từ đơn giản cho đến có ảnh động phức tạp. Các thành phần vẫn hoạt động tốt, nhưng cũng có thể được tuỳ chỉnh và định kiểu cho phù hợp với nhu cầu của ứng dụng.
- Quá trình phát triển nhanh chóng và đơn giản. Compose tương thích với mã nguồn hiện có và cho phép nhà phát triển tạo ứng dụng với ít mã hơn.
- Tính trực quan: Compose sử dụng cú pháp khai báo giúp việc thay đổi giao diện người dùng, gỡ lỗi, hiểu và xem lại mã trở nên trực quan.
Một trường hợp sử dụng phổ biến đối với ứng dụng truyền hình là tiêu thụ nội dung phương tiện. Người dùng duyệt qua danh mục nội dung và chọn nội dung họ muốn xem. Nội dung có thể là phim, chương trình truyền hình hoặc podcast. Sau khi chọn một nội dung, có thể người dùng muốn xem thêm thông tin về nội dung đó, chẳng hạn như đoạn mô tả ngắn, thời lượng phát và tên của nhà sản xuất. Trong lớp học lập trình này, bạn sẽ tìm hiểu cách triển khai màn hình trình duyệt danh mục và màn hình hiển thị thông tin chi tiết trong Compose dành cho TV.
Điều kiện tiên quyết
- Kinh nghiệm về cú pháp Kotlin, bao gồm cả lambda.
- Kinh nghiệm cơ bản về Compose Nếu bạn chưa hiểu rõ về Compose, hãy hoàn thành lớp học lập trình Kiến thức cơ bản về Jetpack Compose.
- Kiến thức cơ bản về thành phần kết hợp và đối tượng sửa đổi.
- Bất cứ thiết bị nào sau đây để chạy ứng dụng mẫu:
- Một thiết bị Android TV
- Một thiết bị Android ảo có hồ sơ thuộc danh mục định nghĩa thiết bị TV
Sản phẩm bạn sẽ tạo ra
- Ứng dụng phát video có màn hình duyệt danh mục và màn hình hiển thị thông tin chi tiết.
- Màn hình duyệt danh mục hiển thị danh sách video để người dùng chọn. Màn sẽ có giao diện như hình sau:
- Màn hình chi tiết hiển thị siêu dữ liệu của video đã chọn, chẳng hạn như tên, đoạn mô tả và thời lượng. Màn sẽ có giao diện như hình sau:
Bạn cần có
- Phiên bản mới nhất của Android Studio
- Một thiết bị Android TV hoặc thiết bị ảo thuộc danh mục thiết bị TV
2. Bắt đầu thiết lập
Để lấy đoạn mã chứa giao diện và và chế độ thiết lập cơ bản cho lớp học lập trình này, hãy làm theo một trong những cách sau:
- Sao chép mã nguồn từ kho lưu trữ GitHub này:
$ git clone https://github.com/android/tv-codelabs.git
Nhánh main
chứa mã nguồn khởi đầu và nhánh solution
chứa mã nguồn giải pháp.
- Tải tệp
main.zip
chứa mã nguồn khởi đầu và tệpsolution.zip
chứa mã nguồn giải pháp.
Sau khi tải mã nguồn xuống, bạn hãy mở thư mục dự án IntroductionToComposeForTV trong Android Studio. Bây giờ, bạn đã sẵn sàng để bắt đầu.
3. Triển khai màn hình duyệt danh mục trình duyệt
Màn hình duyệt xem danh mục cho phép người dùng duyệt xem danh mục phim. Bạn triển khai màn duyệt xem danh mục dưới dạng một hàm có khả năng kết hợp. Bạn có thể tìm thấy hàm có khả năng kết hợp CatalogBrowser
trong tệp CatalogBrowser.kt
. Bạn sẽ triển khai màn hình duyệt xem danh mục trong hàm có khả năng kết hợp này.
Đoạn mã khởi đầu có một ViewModel gọi tới lớp CatalogBrowserViewModel
. Lớp này có một số thuộc tính và phương thức để truy xuất các đối tượng Movie
mô tả nội dung phim. Bạn triển khai một màn duyệt danh mục có các đối tượng Movie
đã truy xuất.
CatalogBrowser.kt
@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
modifier: Modifier = Modifier,
catalogBrowserViewModel: CatalogBrowserViewModel = hiltViewModel(),
onMovieSelected: (Movie) -> Unit = {}
) {
}
Hiển thị tên danh mục
Bạn có thể truy cập vào danh sách danh mục bằng thuộc tính catalogBrowserViewModel.categoryList
(là một luồng của danh sách Category
). Luồng này được thu thập dưới dạng một đối tượng State
trong Compose bằng cách gọi phương thức collectAsStateWithLifecycle
. Đối tượng Category
có thuộc tính name
(là một giá trị String
thể hiện tên danh mục).
Để hiện tên danh mục, hãy làm theo các bước sau:
- Trong Android Studio, hãy mở tệp
CatalogBrowser.kt
của đoạn mã khởi đầu, sau đó thêm hàm có khả năng kết hợpLazyColumn
vào hàm có khả năng kết hợpCatalogBrowser
. - Gọi phương thức
catalogBrowserViewModel.categoryList.collectAsStateWithLifeCycle()
để thu thập luồng dưới dạng một đối tượngState
. - Khai báo
categoryList
dưới dạng thuộc tính uỷ quyền của đối tượngState
mà bạn đã tạo ở bước trước. - Gọi hàm
items
có biếncategoryList
làm một tham số. - Gọi hàm có khả năng kết hợp
Text
bằng tên danh mục làm tham số được truyền dưới dạng đối số của 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)
}
}
}
Hiển thị danh sách nội dung cho từng danh mục
Đối tượng Category
có một thuộc tính khác có tên là movieList
. Thuộc tính này là danh sách các đối tượng Movie
biểu diễn cho phim thuộc danh mục đó.
Để cho thấy danh sách nội dung đối với từng danh mục, hãy làm theo các bước sau:
- Thêm hàm có khả năng kết hợp
LazyRow
, rồi truyền hàm lambda vào hàm đó. - Trong hàm lambda, hãy gọi hàm
items
bằng giá trị thuộc tínhcategory
.movieList
rồi truyền biểu thức lambda vào thuộc tính đó. - Trong hàm lambda được truyền vào hàm
items
, hãy gọi hàm có khả năng kết hợpMovieCard
bằng đối tượngMovie
.
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)
}
}
}
}
}
Không bắt buộc: Điều chỉnh bố cục
- Để thiết lập khoảng cách giữa các danh mục, hãy truyền đối tượng
Arrangement
vào hàm có khả năng kết hợpLazyColumn
bằng tham sốverticalArrangement
. Đối tượngArrangement
được tạo bằng cách gọi phương thứcArrangement#spacedBy
. - Để thiết lập khoảng cách giữa các thẻ phim, hãy truyền một đối tượng
Arrangement
vào hàm có khả năng kết hợpLazyRow
bằng tham sốhorizontalArrangement
. - Để thiết lập khoảng thụt lề cho cột này, hãy truyền đối tượng
PaddingValue
có tham số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. Triển khai màn hình chi tiết
Màn hình chi tiết cho thấy thông tin về bộ phim đã chọn. Có một hàm có khả năng kết hợp Details
trong tệp Details.kt
. Bạn thêm mã vào hàm này để triển khai màn hình chi tiết.
Details.kt
@Composable
fun Details(movie: Movie, modifier: Modifier = Modifier) {
}
Hiển thị tên phim, tên xưởng phim và nội dung mô tả
Đối tượng Movie
có ba thuộc tính chuỗi dưới dạng siêu dữ liệu của phim:
title
. Tên phim.studio
. Tên của studio sản xuất phim.description
. Tóm tắt ngắn về phim.
Để cho thấy siêu dữ liệu này trên màn hình chi tiết, hãy làm theo các bước sau:
- Thêm một hàm có khả năng kết hợp
Column
, sau đó thiết lập khoảng trống có kích thước 32 dp theo chiều dọc và 48 dp theo chiều ngang xung quanh cột bằng đối tượngModifier
tạo bởi phương thứcModifier.padding
. - Thêm một hàm có khả năng kết hợp
Text
để hiển thị tên phim. - Thêm một hàm có khả năng kết hợp
Text
để hiển thị tên xưởng phim. - Thêm hàm có khả năng kết hợp
Text
để hiển thị nội dung mô tả bộ phim.
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)
}
}
Đối tượng Modifier
được chỉ định trong tham số của hàm có khả năng kết hợp Details
sẽ được dùng trong tác vụ tiếp theo.
Hiển thị hình nền được liên kết với một đối tượng Movie
nhất định
Đối tượng Movie
có thuộc tính backgroundImageUrl
cho biết vị trí của hình nền cho phim mà đối tượng mô tả.
Để hiển thị hình nền cho một bộ phim cụ thể, hãy làm theo các bước sau:
- Thêm một hàm có khả năng kết hợp
Box
để làm trình bao bọc của hàm có khả năng kết hợpColumn
với đối tượngmodifier
được truyền qua hàm có khả năng kết hợpDetails
. - Trong hàm có khả năng kết hợp
Box
, hãy gọi phương thứcfillMaxSize
của đối tượngmodifier
để hàm có khả năng kết hợpBox
lấp đầy kích thước tối đa có thể được phân bổ cho hàm có khả năng kết hợpDetails
. - Thêm một hàm có khả năng kết hợp
AsyncImage
có các tham số sau vào hàm có khả năng kết hợpBox
:
- Đặt giá trị của thuộc tính
backgroundImageUrl
của đối tượngMovie
đã cho thành tham sốmodel
. - Truyền giá trị
null
vào tham sốcontentDescription
.
- Truyền một đối tượng
ContentScale.Crop
đến tham sốcontentScale
. Để xem các lựa chọn choContentScale
, hãy xem phần Phạm vi của nội dung. - Truyền giá trị trả về của phương thức
Modifier.fillMaxSize
vào tham số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)
}
}
}
Tham khảo đối tượng MaterialTheme
để tạo giao diện nhất quán
Đối tượng MaterialTheme
chứa các hàm tham chiếu đến các giá trị giao diện hiện tại, chẳng hạn như giá trị trong các lớp Typography
và ColorScheme
.
Để tham chiếu đến đối tượng MaterialTheme
nhằm tạo giao diện nhất quán, hãy làm theo các bước sau:
- Thiết lập thuộc tính
MaterialTheme.typography.displayMedium
thành kiểu văn bản của tên phim. - Thiết lập thuộc tính
MaterialTheme.typography.bodySmall
thành kiểu văn bản của hàm có khả năng kết hợpText
thứ hai. - Thiết lập thuộc tính
MaterialTheme.colorScheme.background
thành màu nền của hàm có khả năng kết hợpColumn
bằng phương thứcModifier.background
.
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)
}
}
}
Không bắt buộc: Điều chỉnh bố cục
Để điều chỉnh bố cục của hàm có khả năng kết hợp Details
, hãy làm theo các bước sau:
- Thiết lập hàm có khả năng kết hợp
Box
để sử dụng toàn bộ không gian còn trống bằng đối tượng sửa đổifillMaxSize
- Thiết lập nền của hàm có khả năng kết hợp
Box
bằng đối tượng sửa đổibackground
để tô màu nền bằng dải màu chuyển tiếp tuyến tính được tạo bằng cách gọi hàmBrush.linearGradient
với danh sách đối tượngColor
chứa giá trịMaterialTheme.colorScheme.background
vàColor.Transparent
- Thiết lập khoảng trống theo chiều ngang
48.dp
và khoảng trống theo chiều dọc24.dp
xung quanh hàm có khả năng kết hợpColumn
bằng Đối tượng sửa đổipadding
- Thiết lập chiều rộng của hàm có khả năng kết hợp
Column
bằng đối tượng sửa đổiwidth
được tạo bằng cách gọi hàmModifier.width
với giá trị0.5f
- Dùng
Spacer
để thêm khoảng trống8.dp
giữa hàm có khả năng kết hợpText
thứ hai và thành phần kết hợpText
thứ ba. Chiều cao của hàm có khả năng kết hợpSpacer
được chỉ định bằng đối tượng sửa đổiheight
được tạo bằng hàmModifier.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. Thêm tính năng điều hướng giữa các màn hình
Giờ đây, bạn đã có màn hình duyệt danh mục và màn hình chi tiết. Sau khi người dùng chọn nội dung trên màn hình duyệt xem danh mục, màn hình đó phải chuyển sang màn hình chi tiết. Để có thể thực hiện việc này, hãy sử dụng đối tượng sửa đổi clickable
để thêm trình nghe event
vào hàm có khả năng kết hợp MovieCard
. Khi nhấn nút giữa của bàn phím di chuyển, phương thức CatalogBrowserViewModel#showDetails
sẽ được gọi với đối tượng phim liên kết với hàm có khả năng kết hợp MovieCard
làm một đối số.
- Mở tệp
com.example.tvcomposeintroduction.ui.screens.CatalogBrowser
. - Truyền một hàm lambda vào hàm có khả năng kết hợp
MovieCard
có tham sốonClick
. - Gọi lệnh gọi lại
onMovieSelected
với đối tượng phim liên kết với hàm có khả năng kết hợpMovieCard
.
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. Thêm băng chuyền vào màn hình duyệt danh mục để làm nổi bật phần nội dung nổi bật
Băng chuyền là thành phần giao diện người dùng thường được điều chỉnh. Thành phần này sẽ tự động cập nhật trang trình bày sau một khoảng thời gian cụ thể. Thẻ này thường được dùng để làm nổi bật phần nội dung nổi bật.
Để thêm băng chuyền vào màn hình trình duyệt danh mục để làm nổi bật phim trong danh sách nội dung nổi bật, hãy làm theo các bước sau:
- Mở tệp
com.example.tvcomposeintroduction.ui.screens.CatalogBrowser
. - Gọi hàm
item
để thêm một mục vào hàm có khả năng kết hợpLazyColumn
. - Khai báo
featuredMovieList
dưới dạng một thuộc tính được uỷ quyền trong hàm lambda truyền đến hàmitem
rồi thiết lập đối tượngState
thành được uỷ quyền. Hàm này được thu thập từ thuộc tínhcatalogBrowserViewModel.featuredMovieList
. - Gọi hàm có khả năng kết hợp
Carousel
bên trong hàmitem
, rồi truyền những tham số sau:
- Kích thước của biến
featuredMovieList
thông qua tham sốslideCount
. - Đối tượng
Modifier
để chỉ định kích thước băng chuyền bằng các phương thứcModifier.fillMaxWidth
vàModifier.height
. Hàm có khả năng kết hợpCarousel
sử dụng chiều cao có giá trị 376 dp bằng cách truyền một giá trị376.dp
vào phương thứcModifier.height
. - Hàm lambda được gọi với một giá trị số nguyên cho biết chỉ mục của mục băng chuyền đang hiển thị.
- Truy xuất đối tượng
Movie
từ biếnfeaturedMovieList
và giá trị chỉ mục đã cho. - Thêm một hàm có khả năng kết hợp
Box
vào hàm có khả năng kết hợpCarousel
. - Thêm một hàm có khả năng kết hợp
Text
vào hàm có khả năng kết hợpBox
để hiển thị tên phim.
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) })
}
}
}
}
}
Hiển thị hình nền
Hàm có khả năng kết hợp Box
đặt một thành phần lên trên một thành phần khác. Tham khảo nội dung Kiến thức cơ bản về bố cục để biết thông tin cụ thể.
Để hiển thị hình nền, hãy làm theo các bước sau:
- Gọi hàm có khả năng kết hợp
AsyncImage
để tải hình nền liên kết với đối tượngMovie
trước khi gọi hàm có khả năng kết hợpText
. - Cập nhật vị trí và kiểu văn bản của hàm có khả năng kết hợp
Text
để hiển thị tốt hơn. - Thiết lập một phần giữ chỗ cho hàm có khả năng kết hợp
AsyncImage
để tránh xáo trộn bố cục. Đoạn mã khởi đầu có một phần giữ chỗ dưới dạng một đối tượng có thể vẽ mà bạn có thể tham chiếu bằngR.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) })
}
}
}
}
}
Thêm hiệu ứng chuyển đổi sang màn hình chi tiết
Bạn có thể thêm Button
vào băng chuyền để người dùng có thể kích hoạt hiệu ứng chuyển đổi sang màn hình chi tiết bằng cách nhấp vào nút.
Để cho phép người dùng xem thông tin về phim trong phần băng chuyền hiển thị trên màn hình chi tiết, hãy làm theo các bước sau:
- Gọi hàm có khả năng kết hợp
Column
trong thành phần kết hợpBox
trong thành phần kết hợpCarousel
- Di chuyển thành phần kết hợp
Text
trongCarousel
sang hàm có khả năng kết hợpColumn
- Gọi hàm có khả năng kết hợp
Button
sau hàm có khả năng kết hợpText
trong hàm có khả năng kết hợpColumn
- Gọi hàm có khả năng kết hợp
Text
trong hàm có khả năng kết hợpButton
bằng giá trị trả về của hàmstringResource
được gọi bằngR.string.show_details
. - Gọi hàm
onMovieSelected
có biếnfeaturedMovie
trong biểu thức lambda được truyền đến tham sốonClick
của hàm có khả năng kết hợpButton
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) })
}
}
}
}
}
Không bắt buộc: Điều chỉnh bố cục
Để điều chỉnh bố cục của băng chuyền, hãy làm theo các bước sau:
- Chỉ định giá trị
backgroundColor
thành giá trịMaterialTheme.colorScheme.background
trong hàm có khả năng kết hợpCarousel
- Gói hàm có khả năng kết hợp
Column
bằng một thành phần kết hợpBox
- Truyền giá trị
Alignment.BottomStart
vào tham sốcontentAlignment
của thành phầnBox
. - Truyền đối tượng sửa đổi
fillMaxSize
đến tham số đối tượng sửa đổi của hàm có khả năng kết hợpBox
. Đối tượng sửa đổifillMaxSize
được tạo bằng hàmModifier.fillMaxSize()
. - Gọi phương thức
drawBehind()
qua đối tượng sửa đổifillMaxSize
được truyền đến thành phần kết hợpBox
- Trong biểu thức lambda được truyền đến đối tượng sửa đổi
drawBehind
, hãy gán giá trịbrush
bằng đối tượngBrush
được tạo bằng cách gọi hàmBrush.linearGradient
với danh sách gồm hai đối tượngColor
. Danh sách này được tạo bằng cách gọi hàmlistOf
với giá trịbackgroundColor
và giá trịColor.Transparent
. - Gọi
drawRect
bằng đối tượngbrush
trong hàm lambda được truyền đến đối tượng sửa đổidrawBehind
để tạo một lớp srim trên hình nền - Chỉ định khoảng đệm của hàm có khả năng kết hợp
Column
bằng đối tượng sửa đổipadding
được tạo bằng cách gọiModifier.padding
với giá trị20.dp
. - Thêm một hàm có khả năng kết hợp
Spacer
có giá trị20.dp
giữa thành phần kết hợpText
và thành phần kết hợpButton
trong hàm có khả năng kết hợpColumn
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. Lấy mã giải pháp
Để tải mã giải pháp cho lớp học lập trình này, hãy làm theo một trong những cách sau:
- Nhấp vào nút sau để tải tệp xuống dưới dạng tệp zip, sau đó giải nén và mở tệp trong Android Studio.
- Truy xuất bằng Git:
$ git clone https://github.com/android/tv-codelabs.git $ cd tv-codelabs $ git checkout solution $ cd IntroductionToComposeForTV
8. Chúc mừng bạn!
Xin chúc mừng! Bạn đã tìm hiểu các kiến thức cơ bản về Compose dành cho TV:
- Cách triển khai màn hình để hiển thị một danh sách nội dung bằng cách kết hợp LazyColumn và LazyLow.
- Cách triển khai màn hình cơ bản để hiển thị nội dung chi tiết.
- Cách thêm hiệu ứng chuyển đổi giữa hai màn hình.