Giới thiệu về Compose dành cho TV

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 duyệt danh mục hiển thị danh sách phim nổi bật\nvới một băng chuyền ở phía trên cùng.\nMàn hình cũng hiển thị một danh sách phim cho mỗi danh mục.

  • 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:

Màn hình chi tiết cho thấy siêu dữ liệu của phim,\ntrong đó có tên phim, xưởng phim và phần mô tả ngắn.\nSiêu dữ liệu xuất hiện trên hình nền liên kết với phim.

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:

$ 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ệp solution.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:

  1. 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ợp TvLazyColumn vào hàm có khả năng kết hợp CatalogBrowser.
  2. Gọi phương thức catalogBrowserViewModel.categoryList.collectAsStateWithLifeCycle() để thu thập luồng dưới dạng một đối tượng State.
  3. Khai báo categoryList dưới dạng thuộc tính uỷ quyền của đối tượng State mà bạn đã tạo ở bước trước.
  4. Gọi hàm items có biến categoryList làm một tham số.
  5. 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()
    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 đó.

Để 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:

  1. Thêm hàm có khả năng kết hợp TvLazyRow, rồi truyền hàm lambda vào hàm đó.
  2. Trong hàm lambda, hãy gọi hàm items bằng giá trị thuộc tính category.movieList rồi truyền biểu thức lambda vào thuộc tính đó.
  3. 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ợp MovieCard bằng đối tượng Movie.

CatalogBrowser.kt

@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = hiltViewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
    val categoryList by
    catalogBrowserViewModel.categoryList.collectAsStateWithLifecycle()
    TvLazyColumn(modifier = modifier) {
        items(categoryList) { category ->
            Text(text = category.name)
            TvLazyRow {
                items(category.movieList) {movie ->
                    MovieCard(movie = movie)
                }
            }
        }
    }
}

Không bắt buộc: Điều chỉnh bố cục

  1. Để 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ợp TvLazyColumn bằng tham số verticalArrangement. Đối tượng Arrangement được tạo bằng cách gọi phương thức Arrangement#spacedBy.
  2. Để 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ợp TvLazyRow bằng tham số horizontalArrangement.
  3. Để 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()
    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 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

@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.

Để 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:

  1. 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ượng Modifier tạo bởi phương thức Modifier.padding.
  2. Thêm một hàm có khả năng kết hợp Text để hiển thị tên phim.
  3. Thêm một hàm có khả năng kết hợp Text để hiển thị tên xưởng phim.
  4. 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

@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.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:

  1. 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ợp Column với đối tượng modifier được truyền qua hàm có khả năng kết hợp Details.
  2. Trong hàm có khả năng kết hợp Box, hãy gọi phương thức fillMaxSize của đối tượng modifier để hàm có khả năng kết hợp Box 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ợp Details.
  3. 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ợp Box:
  • Đặt giá trị của thuộc tính backgroundImageUrl của đối tượng Movie đã 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 cho ContentScale, 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

@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 {
            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 TypographyColorScheme.

Để 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:

  1. Thiết lập thuộc tính MaterialTheme.typography.displayMedium thành kiểu văn bản của tên phim.
  2. 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ợp Text thứ hai.
  3. 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ợp Column bằng phương thức Modifier.background.

Details.kt

@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
                .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:

  1. 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 đổi fillMaxSize
  2. 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 đổi background để 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àm Brush.linearGradient với danh sách đối tượng Color chứa giá trị MaterialTheme.colorScheme.backgroundColor.Transparent
  3. Thiết lập khoảng trống theo chiều ngang 48.dp và khoảng trống theo chiều dọc 24.dp xung quanh hàm có khả năng kết hợp Column bằng Đối tượng sửa đổi padding
  4. 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 đổi width được tạo bằng cách gọi hàm Modifier.width với giá trị 0.5f
  5. Thêm khoảng trống 8.dp giữa hàm có khả năng kết hợp Text thứ hai và thành phần kết hợp Text thứ ba bằng Spacer. Chiều cao của hàm có khả năng kết hợp Spacer được chỉ định bằng đối tượng sửa đổi height được tạo bằng hàm Modifier.height

Details.kt

@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()
        )
        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ố.

  1. Mở tệp com.example.tvcomposeintroduction.ui.screens.CatalogBrowser.
  2. Truyền một hàm lambda vào hàm có khả năng kết hợp MovieCard có tham số onClick.
  3. 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ợp MovieCard.

CatalogBrowser.kt

@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = hiltViewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
    val categoryList by
    catalogBrowserViewModel.categoryList.collectAsStateWithLifecycle()
    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:

  1. Mở tệp com.example.tvcomposeintroduction.ui.screens.CatalogBrowser.
  2. Gọi hàm item để thêm một mục vào hàm có khả năng kết hợp TvLazyColumn.
  3. 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àm item rồi thiết lập đối tượng State thành được uỷ quyền. Hàm này được thu thập từ thuộc tính catalogBrowserViewModel.featuredMovieList.
  4. Gọi hàm có khả năng kết hợp Carousel bên trong hàm item, 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ức Modifier.fillMaxWidthModifier.height. Hàm có khả năng kết hợp Carousel 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ức Modifier.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ị.
  1. Truy xuất đối tượng Movie từ biến featuredMovieList và giá trị chỉ mục đã cho.
  2. 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ợp Carousel.
  3. 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ợp Box để 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()
    TvLazyColumn(
        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)
            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 có khả năng kết hợp Box đặt một thành phần lê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:

  1. 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ượng Movie trước khi gọi hàm có khả năng kết hợp Text.
  2. 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.
  3. 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ằng 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()
    TvLazyColumn(
        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)
            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ể 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:

  1. Gọi hàm có khả năng kết hợp Column trong thành phần kết hợp Box trong thành phần kết hợp Carousel
  2. Di chuyển thành phần kết hợp Text trong Carousel sang hàm có khả năng kết hợp Column
  3. Gọi hàm có khả năng kết hợp Button sau hàm có khả năng kết hợp Text trong hàm có khả năng kết hợp Column
  4. Gọi hàm có khả năng kết hợp Text trong hàm có khả năng kết hợp Button bằng giá trị trả về của hàm stringResource được gọi bằng R.string.show_details.
  5. Gọi hàm onMovieSelected có biến featuredMovie trong biểu thức lambda được truyền đến tham số onClick của hàm có khả năng kết hợp Button

CatalogBrowser.kt

@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = hiltViewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
    val categoryList by
    catalogBrowserViewModel.categoryList.collectAsStateWithLifecycle()
    TvLazyColumn(
        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)
            TvLazyRow(
                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:

  1. Chỉ định giá trị backgroundColor thành giá trị MaterialTheme.colorScheme.background trong hàm có khả năng kết hợp Carousel
  2. Gói hàm có khả năng kết hợp Column bằng một thành phần kết hợp Box
  3. Truyền giá trị Alignment.BottomStart vào tham số contentAlignment của thành phần Box.
  4. 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ợp Box. Đối tượng sửa đổi fillMaxSize được tạo bằng hàm Modifier.fillMaxSize().
  5. Gọi phương thức drawBehind() qua đối tượng sửa đổi fillMaxSize được truyền đến thành phần kết hợp Box
  6. 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ượng Brush được tạo bằng cách gọi hàm Brush.linearGradient với danh sách gồm hai đối tượng Color. Danh sách này được tạo bằng cách gọi hàm listOf với giá trị backgroundColor và giá trị Color.Transparent.
  7. Gọi drawRect bằng đối tượng brush trong hàm lambda được truyền đến đối tượng sửa đổi drawBehind để tạo một lớp srim trên hình nền
  8. 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 đổi padding được tạo bằng cách gọi Modifier.padding với giá trị 20.dp.
  9. 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ợp Text và thành phần kết hợp Button trong hàm có khả năng kết hợp Column

CatalogBrowser.kt

@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = hiltViewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
    val categoryList by catalogBrowserViewModel.categoryList.collectAsStateWithLifecycle()
    TvLazyColumn(
        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)
            TvLazyRow(
                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ị 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.