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.
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
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 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 danh mục dưới dạng hàm Composable
. Bạn có thể tìm thấy hàm CatalogBrowser
Composable
trong tệp CatalogBrowser.kt
. Bạn triển khai màn hình duyệt danh mục trong hàm Composable
này.
Mã khởi đầu có 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
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 = {}
) {
}
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 (flow) này được thu thập dưới dạng đối tượng State
trong Compose bằng cách gọi phương thức collectAsState
. Đối tượng Category
có thuộc tính name
(là giá trị String
biểu diễn tên danh mục).
Để hiển thị 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 mã nguồn khởi đầu, sau đó thêm hàmTvLazyColumn
Composable
vào hàmCatalogBrowser
Composable
. - Gọi phương thức
catalogBrowserViewModel.categoryList.collectAsState()
để thu thập luồng dưới dạng đố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 tham số. - Gọi hàm
Text
Composable
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
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)
}
}
}
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 đó.
Để hiển thị danh sách nội dung cho từng danh mục, hãy làm theo các bước sau:
- Thêm hàm
TvLazyRow
Composable
, sau đó truyền hàm lambda vào hàm đó. - Trong hàm lambda, hãy gọi hàm
items
bằngcategory
.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àmMovieCard
Composable
bằng đối tượngMovie
.
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)
}
}
}
}
}
Điều chỉnh bố cục (không bắt buộc)
- Để đặt khoảng cách giữa các danh mục, hãy truyền đối tượng
Arrangement
vào hàmTvLazyColumn
Composable
bằng tham sốverticalArrangement
. Đối tượngArrangement
được tạo bằng cách gọi phương thứcArrangement#spacedBy
. - Để đặt khoảng cách giữa các thẻ phim, hãy truyền một đối tượng
Arrangement
vào hàmTvLazyRow
Composable
bằng tham sốhorizontalArrangement
. - Để đặt khoảng thụt lề cho cột này, hãy truyền đối tượng
PaddingValue
có tham số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. Triển khai màn hình chi tiết
Màn hình chi tiết hiển thị thông tin về bộ phim đã chọn. Có một hàm Details
Composable
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
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) {
}
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.
Để hiển thị 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
Column
Composable
, sau đó đặt 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
được tạo bằng phương thứcModifier.padding
. - Thêm một hàm
Text
Composable
để hiển thị tên phim. - Thêm một hàm
Text
Composable
để hiển thị tên studio. - Thêm hàm
Text
Composable
để hiển thị nội dung mô tả phim.
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)
}
}
Đối tượng Modifier
được chỉ định trong tham số của hàm Details
Composable
đượ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
Box
Composable
để góiColumn
Composable
có đối tượngmodifier
được truyền qua hàmDetails
Composable
. - Trong hàm
Box
Composable
, hãy gọi phương thứcfillMaxSize
của đối tượngmodifier
để hàmBox
Composable
lấp đầy kích thước tối đa có thể được phân bổ choDetails
Composable
. - Thêm một hàm
AsyncImage
Composable
có các tham số sau vào hàmBox
Composable
:
- Đặ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
. - Đặt khoảng trống có kích thước 32 dp theo chiều dọc và 48 dp theo chiều ngang so với cột bằng cách đặt đối tượng
Modifier
được tạo bằng cách gọi phương thứcModifier.padding
.
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,
)
}
}
}
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ư các giá trị trong các lớp Typography
và [ColorScheme
][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:
- Đặt thuộc tính
MaterialTheme.typography.headlineLarge
thành kiểu văn bản của tên phim. - Đặt thuộc tính
MaterialTheme.typography.headlineMedium
thành kiểu văn bản của hai hàmText
Composable
nữa. - Đặt thuộc tính
MaterialTheme.colorScheme.background
thành màu nền của hàmColumn
Composable
bằng phương thứcModifier.background
.
[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. 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 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, bạn hãy sử dụng đối tượng sửa đổi clickable
để thêm trình nghe event
vào hàm MovieCard
Composable
. 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 MovieCard
Composable
làm đối số.
- Mở tệp
com.example.tvcomposeintroduction.ui.screens.CatalogBrowser
. - Truyền một hàm lambda vào hàm
MovieCard
Composable
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àmMovieCard
Composable
.
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. 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àmTvLazyColumn
Composable
. - Khai báo
featuredMovieList
dưới dạng một thuộc tính được uỷ quyền trong hàm lambda được truyền đến hàmitem
rồi đặt đố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
Carousel
Composable
bên trong hàmitem
, sau đó truyền các 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 phương thứcModifier.fillMaxWidth
vàModifier.height
. HàmCarousel
Composable
sử dụng chiều cao có giá trị 376 dp bằng cách truyền 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 hàm
CarouselSlide
Composable
vào hàmCarousel
Composable
. - Thêm hàm
Text
Composable
vào hàmCarouselSlide
Composable
để hiển thị tên phim.
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) })
}
}
}
}
}
Hiển thị hình nền
Hàm CarouselSlide
Composable
có thể lấy một hàm lambda khác để chỉ định cách nền của hàm CarouselSlide
Composable
hiển thị.
Để hiển thị hình nền, hãy làm theo các bước sau:
- Truyền một hàm lambda vào hàm
CarouselSlide
Composable
bằng tham sốbackground
. - Gọi hàm
AsyncImage
Composable
để tải hình nền liên kết với đối tượngMovie
làm nền của hàmCarouselSlide
Composable
. - Cập nhật vị trí và kiểu văn bản của hàm
Text
Composable
trong hàmCarouselSlide
Composable
để hiển thị tốt hơn. - Đặt một phần giữ chỗ cho hàm
AsyncImage
Composable
để tránh xáo trộn bố cục. Mã khởi đầu có 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
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) })
}
}
}
}
}
Thêm hiệu ứng chuyển đổi sang màn hình chi tiết
Bạn có thể cho phép người dùng nhấp vào hàm CarouselSlide
Composable
.
Để 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:
- Truyền giá trị trả về của phương thức
Modifier.clickable
vào hàmCarouselSlide
Composable
thông qua tham sốmodifier
. - Gọi hàm
onMovieSelected
có đối tượngMovie
cho hàmCarouselSlide
Composable
hiển thị trong lambda được truyền vào phương thứcModifier.clickable
.
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. 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ị danh sách nội dung bằng cách kết hợp TvLazyColumn và TvLazyLow.
- 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.