Pengantar Compose untuk TV

1. Sebelum memulai

Compose untuk TV adalah framework UI terbaru untuk mengembangkan aplikasi yang berjalan di Android TV. Framework ini mendapatkan semua manfaat Jetpack Compose untuk aplikasi TV sehingga mem-build UI yang indah dan fungsional untuk aplikasi Anda menjadi lebih mudah. Beberapa manfaat khusus Compose untuk TV mencakup hal berikut:

  • Fleksibilitas. Compose dapat digunakan untuk membuat jenis UI apa pun, mulai dari tata letak yang sederhana hingga animasi yang kompleks. Komponen dapat langsung difungsikan, tetapi juga dapat disesuaikan dan ditata agar sesuai dengan kebutuhan aplikasi Anda.
  • Pengembangan yang disederhanakan dan dipercepat. Compose kompatibel dengan kode yang sudah ada dan memungkinkan developer membuat aplikasi dengan lebih sedikit kode.
  • Intuitif: Compose menggunakan sintaksis deklaratif yang membuatnya intuitif untuk mengubah UI Anda, serta melakukan debug, memahami, dan meninjau kode Anda.

Kasus penggunaan umum untuk aplikasi TV adalah konsumsi media. Pengguna menjelajahi katalog konten dan memilih konten yang ingin ditonton. Konten dapat berupa film, acara TV, atau podcast. Setelah pengguna memilih konten, mereka mungkin ingin melihat informasi lebih lanjut tentang konten tersebut, seperti deskripsi singkat, durasi pemutaran, dan nama kreator. Dalam codelab ini, Anda akan mempelajari cara menerapkan layar browser katalog dan layar detail dengan Compose untuk TV.

Prasyarat

  • Pengalaman dengan sintaksis Kotlin, termasuk lambda.
  • Pengalaman dasar dengan Compose. Jika Anda tidak terbiasa dengan Compose, selesaikan codelab Dasar-dasar Jetpack Compose.
  • Pengetahuan dasar tentang composable dan pengubah.
  • Salah satu perangkat berikut untuk menjalankan aplikasi contoh:
    • Perangkat Android TV
    • Perangkat virtual Android dengan profil dalam kategori definisi perangkat TV

Yang Anda bangun

  • Aplikasi pemutar video dengan layar browser katalog dan layar detail.
  • Layar browser katalog yang menampilkan daftar video yang dapat dipilih pengguna. Tampilannya akan terlihat seperti gambar berikut:

Browser katalog menampilkan daftar film unggulan\ndengan carousel di atasnya.\nLayar juga menampilkan daftar film untuk setiap kategori.

  • Layar detail yang menunjukkan metadata video yang dipilih, seperti judul, deskripsi, dan durasi. Tampilannya akan terlihat seperti gambar berikut:

Layar detail menampilkan metadata film,\ntermasuk judul, studio, dan deskripsi singkat.\nMetadata ditampilkan di gambar latar yang terkait dengan film.

Yang Anda perlukan

  • Versi terbaru Android Studio
  • Perangkat Android TV atau perangkat virtual dalam kategori perangkat TV

2. Memulai persiapan

Untuk mendapatkan kode yang berisi tema dan penyiapan dasar untuk codelab ini, lakukan salah satu hal berikut:

$ git clone https://github.com/android/tv-codelabs.git

Cabang main berisi kode awal dan cabang solution berisi kode solusi.

Setelah berhasil mendownload kode, buka folder project IntroductionToComposeForTV di Android Studio. Sekarang Anda siap untuk memulai.

3. Menerapkan layar browser katalog

Layar browser katalog memungkinkan pengguna menjelajahi katalog film. Anda mengimplementasikan browser katalog sebagai fungsi composable. Anda dapat menemukan fungsi composable CatalogBrowser dalam file CatalogBrowser.kt. Anda akan menerapkan layar browser katalog dalam fungsi composable ini.

Kode awal memiliki ViewModel yang disebut class CatalogBrowserViewModel yang memiliki beberapa atribut dan metode untuk mengambil objek Movie yang mendeskripsikan konten film. Anda mengimplementasikan browser katalog dengan objek Movie yang diambil.

CatalogBrowser.kt

@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = hiltViewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
}

Menampilkan nama kategori

Anda dapat mengakses daftar kategori dengan atribut catalogBrowserViewModel.categoryList yang merupakan alur daftar Category. Alur ini dikumpulkan sebagai objek Compose State dengan memanggil metode collectAsStateWithLifecycle. Objek Category memiliki atribut name, yang merupakan nilai String yang mewakili nama kategori.

Untuk menampilkan nama kategori, ikuti langkah-langkah berikut:

  1. Di Android Studio, buka file CatalogBrowser.kt kode awal, lalu tambahkan fungsi composable TvLazyColumn ke fungsi composable CatalogBrowser.
  2. Panggil metode catalogBrowserViewModel.categoryList.collectAsStateWithLifeCycle() untuk mengumpulkan alur sebagai objek State.
  3. Deklarasikan categoryList sebagai properti yang didelegasikan dari objek State yang Anda buat di langkah sebelumnya.
  4. Panggil fungsi items dengan variabel categoryList sebagai parameter.
  5. Panggil fungsi composable Text dengan nama kategori sebagai parameter yang diteruskan sebagai argumen 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)
        }
    }
}

Menampilkan daftar konten untuk setiap kategori

Objek Category memiliki atribut lain yang disebut movieList. Atribut ini adalah daftar objek Movie yang mewakili film yang termasuk dalam kategori tersebut.

Untuk menampilkan daftar konten untuk setiap kategori, ikuti langkah-langkah berikut:

  1. Tambahkan fungsi composable TvLazyRow, lalu teruskan lambda ke fungsi tersebut.
  2. Di lambda, panggil fungsi items dengan category.Nilai atribut movieList, lalu teruskan lambda ke nilai tersebut.
  3. Di lambda yang diteruskan ke fungsi items, panggil fungsi composable MovieCard dengan objek 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)
                }
            }
        }
    }
}

Opsional: Menyesuaikan tata letak

  1. Untuk menetapkan jarak antar-kategori, teruskan objek Arrangement ke fungsi composable TvLazyColumn dengan parameter verticalArrangement. Objek Arrangement dibuat dengan memanggil metode Arrangement#spacedBy.
  2. Untuk menetapkan jarak antar-kartu film, teruskan objek Arrangement ke fungsi composable TvLazyRow dengan parameter horizontalArrangement.
  3. Untuk menetapkan indentasi ke kolom, teruskan objek PaddingValue dengan parameter 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. Menerapkan layar detail

Layar detail menampilkan detail film yang dipilih. Ada fungsi composable Details dalam file Details.kt. Anda akan menambahkan kode ke fungsi ini untuk menerapkan layar detail.

Details.kt

@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun Details(movie: Movie, modifier: Modifier = Modifier) {
}

Menampilkan judul film, nama studio, dan deskripsi

Objek Movie memiliki tiga atribut string berikut sebagai metadata film:

  • title. Judul film.
  • studio. Nama studio yang memproduksi film.
  • description. Ringkasan singkat film.

Untuk menampilkan metadata ini di layar detail, ikuti langkah-langkah berikut:

  1. Tambahkan fungsi composable Column, lalu tetapkan jarak vertikal 32 dp dan horizontal 48 dp di sekitar kolom dengan objek Modifier yang dibuat oleh metode Modifier.padding.
  2. Tambahkan fungsi composable Text untuk menampilkan judul film.
  3. Tambahkan fungsi composable Text untuk menampilkan nama studio.
  4. Tambahkan fungsi composable Text untuk menampilkan deskripsi film.

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)
    }
}

Objek Modifier yang ditentukan dalam parameter fungsi composable Details digunakan dalam tugas berikutnya.

Menampilkan gambar latar yang terkait dengan objek Movie tertentu

Objek Movie memiliki atribut backgroundImageUrl yang menunjukkan lokasi gambar latar untuk film yang dijelaskan oleh objek.

Agar gambar latar untuk film tertentu dapat ditampilkan, ikuti langkah-langkah berikut:

  1. Tambahkan fungsi composable Box sebagai wrapper fungsi composable Column dengan objek modifier yang diteruskan melalui fungsi composable Details.
  2. Dalam fungsi composable Box, panggil metode fillMaxSize objek modifier untuk membuat fungsi composable Box mengisi ukuran maksimum yang dapat dialokasikan ke fungsi composable Details.
  3. Tambahkan fungsi composable AsyncImage dengan parameter berikut ke fungsi composable Box:
  • Setel nilai atribut backgroundImageUrl dari objek Movie yang diberikan ke parameter model.
  • Teruskan null ke parameter contentDescription.
  • Teruskan objek ContentScale.Crop ke parameter contentScale. Untuk melihat opsi ContentScale yang berbeda, lihat Skala konten.
  • Teruskan nilai return metode Modifier.fillMaxSize ke parameter 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)
        }
    }
}

Lihat objek MaterialTheme untuk penerapan tema yang konsisten

Objek MaterialTheme berisi fungsi untuk mereferensikan nilai tema saat ini, seperti yang ada di class Typography dan ColorScheme.

Untuk melihat objek MaterialTheme agar temanya konsisten, ikuti langkah-langkah berikut:

  1. Tetapkan properti MaterialTheme.typography.displayMedium ke gaya teks judul film.
  2. Setel properti MaterialTheme.typography.bodySmall ke gaya teks dari fungsi composable Text kedua.
  3. Tetapkan properti MaterialTheme.colorScheme.background ke warna latar belakang fungsi composable Column dengan metode 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)
        }
    }
}

Opsional: Menyesuaikan tata letak

Untuk menyesuaikan tata letak fungsi composable Details, ikuti langkah-langkah berikut:

  1. Tetapkan fungsi composable Box untuk menggunakan seluruh ruang yang tersedia dengan pengubah fillMaxSize
  2. Tetapkan latar belakang fungsi composable Box dengan pengubah background untuk mengisi latar belakang dengan gradien linear yang dibuat dengan memanggil fungsi Brush.linearGradient dengan daftar objek Color yang berisi nilai MaterialTheme.colorScheme.background dan Color.Transparent
  3. Tetapkan jarak horizontal 48.dp dan vertikal 24.dp di sekitar fungsi composable Column dengan Pengubah padding
  4. Tetapkan dengan fungsi composable Column dengan pengubah width yang dibuat dengan memanggil fungsi Modifier.width dengan nilai 0.5f
  5. Tambahkan jarak 8.dp antar fungsi composable Text kedua dan composable Text ketiga dengan Spacer. Tinggi fungsi composable Spacer ditentukan dengan pengubah height yang dibuat dengan fungsi 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. Menambahkan navigasi antar-layar

Sekarang Anda memiliki browser katalog dan layar detail. Setelah pengguna memilih konten di layar browser katalog, layar harus bertransisi ke layar detail. Untuk memungkinkan hal ini, gunakan pengubah clickable untuk menambahkan pemroses event ke fungsi composable MovieCard. Saat tombol tengah dari tombol arah ditekan, metode CatalogBrowserViewModel#showDetails dipanggil dengan objek film yang terkait dengan fungsi composable MovieCard sebagai argumen.

  1. Buka file com.example.tvcomposeintroduction.ui.screens.CatalogBrowser.
  2. Teruskan fungsi lambda ke fungsi composable MovieCard dengan parameter onClick.
  3. Panggil callback onMovieSelected dengan objek film yang terkait dengan fungsi composable 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. Menambahkan carousel ke layar browser katalog untuk menandai konten unggulan

Carousel adalah komponen UI yang umum diadaptasikan, yang otomatis mengupdate slide setelah durasi yang ditentukan. Atribut ini biasanya digunakan untuk menyoroti konten unggulan.

Untuk menambahkan carousel ke layar katalog guna menyoroti film dalam daftar konten unggulan, ikuti langkah-langkah berikut:

  1. Buka file com.example.tvcomposeintroduction.ui.screens.CatalogBrowser.
  2. Panggil fungsi item untuk menambahkan item ke fungsi composable TvLazyColumn.
  3. Deklarasikan featuredMovieList sebagai properti yang didelegasikan dalam lambda yang diteruskan ke fungsi item, lalu tetapkan objek State yang akan didelegasikan, yang dikumpulkan dari atribut catalogBrowserViewModel.featuredMovieList.
  4. Panggil fungsi composable Carousel di dalam fungsi item, lalu teruskan parameter berikut:
  • Ukuran variabel featuredMovieList melalui parameter slideCount.
  • Objek Modifier untuk menentukan ukuran carousel dengan metode Modifier.fillMaxWidth dan Modifier.height. Fungsi composable Carousel menggunakan tinggi 376 dp dengan meneruskan nilai 376.dp ke metode Modifier.height.
  • Lambda yang dipanggil dengan nilai bilangan bulat yang menunjukkan indeks item carousel yang terlihat.
  1. Ambil objek Movie dari variabel featuredMovieList dan nilai indeks yang diberikan.
  2. Tambahkan fungsi composable Box ke fungsi composable Carousel.
  3. Tambahkan fungsi composable Text ke fungsi composable Box untuk menampilkan judul film.

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) })
                }
            }
        }
    }
}

Menampilkan gambar latar

Fungsi composable Box menempatkan satu komponen di atas komponen lain. Baca Dasar-dasar tata letak untuk mengetahui detail selengkapnya.

Untuk menampilkan gambar latar, ikuti langkah-langkah berikut:

  1. Panggil fungsi composable AsyncImage untuk memuat gambar latar yang terkait dengan objek Movie sebelum fungsi composable Text.
  2. Perbarui posisi dan gaya teks fungsi composable Text untuk visibilitas yang lebih baik.
  3. Tetapkan placeholder ke fungsi composable AsyncImage untuk menghindari pergeseran tata letak. Kode awal memiliki placeholder sebagai drawable yang dapat Anda referensikan dengan 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) })
                }
            }
        }
    }
}

Menambahkan transisi layar ke layar detail

Anda dapat menambahkan Button ke carousel agar pengguna dapat memicu transisi layar ke layar detail dengan mengklik tombol.

Untuk mengizinkan pengguna melihat detail film di carousel yang terlihat di layar detail, ikuti langkah-langkah berikut:

  1. Panggil fungsi composable Column di composable Box di composable Carousel
  2. Pindahkan composable Text di Carousel ke fungsi composable Column
  3. Panggil fungsi composable Button setelah fungsi composable Text di fungsi composable Column
  4. Panggil fungsi composable Text dalam fungsi composable Button dengan nilai yang ditampilkan dari fungsi stringResource yang dipanggil dengan R.string.show_details.
  5. Panggil fungsi onMovieSelected dengan variabel featuredMovie di lambda yang diteruskan ke parameter onClick dari fungsi composable 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) })
                }
            }
        }
    }
}

Opsional: Menyesuaikan tata letak

Untuk menyesuaikan tata letak carousel, ikuti langkah-langkah berikut:

  1. Tetapkan nilai backgroundColor dengan nilai MaterialTheme.colorScheme.background dalam fungsi composable Carousel
  2. Gabungkan fungsi composable Column dengan composable Box
  3. Teruskan nilai Alignment.BottomStart ke parameter contentAlignment komponen Box.
  4. Teruskan pengubah fillMaxSize ke parameter pengubah fungsi composable Box. Pengubah fillMaxSize dibuat dengan fungsi Modifier.fillMaxSize().
  5. Panggil metode drawBehind() di atas pengubah fillMaxSize yang diteruskan ke composable Box
  6. Di lambda yang diteruskan ke drawBehind pengubah, tetapkan nilai brush dengan objek Brush yang dibuat dengan memanggil fungsi Brush.linearGradient dengan daftar dua objek Color. Daftar ini dibuat dengan memanggil fungsi listOf dengan nilai backgroundColor dan nilai Color.Transparent.
  7. Panggil drawRect dengan objek brush di lambda yang diteruskan ke pengubah drawBehind untuk membuat lapisan srim di atas gambar latar
  8. Tentukan padding fungsi composable Column dengan pengubah padding yang dibuat dengan memanggil Modifier.padding dengan nilai 20.dp.
  9. Tambahkan fungsi composable Spacer dengan nilai 20.dp antara composable Text dan composable Button dalam fungsi composable 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. Mendapatkan kode solusi

Untuk mendownload kode solusi untuk codelab ini, lakukan salah satu hal berikut:

  • Klik tombol berikut untuk mendownloadnya sebagai file zip, lalu ekstrak dan buka di Android Studio.

  • Ambil dengan Git:
$ git clone https://github.com/android/tv-codelabs.git
$ cd tv-codelabs
$ git checkout solution
$ cd IntroductionToComposeForTV

8. Selamat.

Selamat! Anda telah mempelajari dasar-dasar Compose untuk TV:

  • Cara menerapkan layar untuk menampilkan daftar konten dengan menggabungkan TvLazyColumn dan TvLazyLow.
  • Implementasi layar dasar untuk menampilkan detail konten.
  • Cara menambahkan transisi layar antara dua layar.