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.

Yang Anda build

  • 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

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 CatalogBrowser Composable 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

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

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 collectAsState. 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 TvLazyColumn Composable ke fungsi CatalogBrowser Composable.
  2. Panggil metode catalogBrowserViewModel.categoryList.collectAsState() 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 Text Composable dengan nama kategori sebagai parameter yang diteruskan sebagai argumen 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)
        }
    }
}

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 TvLazyRow Composable, 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 MovieCard Composable dengan objek Movie.

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

Opsional: Menyesuaikan tata letak

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

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

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

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 Column Composable, 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 Text Composable untuk menampilkan nama studio.
  4. Tambahkan fungsi Composable Text untuk menampilkan deskripsi film.

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

Objek Modifier yang ditentukan dalam parameter fungsi Details Composable 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 Box Composable sebagai wrapper fungsi Column Composable dengan objek modifier yang diteruskan melalui fungsi Details Composable.
  2. Dalam fungsi Box Composable, panggil metode fillMaxSize objek modifier untuk membuat fungsi Box Composable mengisi ukuran maksimum yang dapat dialokasikan ke Details fungsi Composable.
  3. Tambahkan fungsi AsyncImage Composable dengan parameter berikut ke fungsi Box Composable:
  • 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.
  • Tetapkan jarak horizontal 32 dp dan jarak horizontal 48 dp ke kolom dengan menetapkan objek Modifier yang dibuat dengan memanggil metode Modifier.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,
            )
        }
    }
}

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][ColorScheme].

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

  1. Tetapkan properti MaterialTheme.typography.headlineLarge ke gaya teks judul film.
  2. Setel properti MaterialTheme.typography.headlineMedium ke gaya teks dari dua fungsi Text Composable lainnya.
  3. Tetapkan properti MaterialTheme.colorScheme.background ke warna latar belakang fungsi Column Composable dengan metode Modifier.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. 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 MovieCard Composable. Saat tombol tengah dari tombol arah ditekan, metode CatalogBrowserViewModel#showDetails dipanggil dengan objek film yang terkait dengan fungsi MovieCard Composable sebagai argumen.

  1. Buka file com.example.tvcomposeintroduction.ui.screens.CatalogBrowser.
  2. Teruskan fungsi lambda ke fungsi MovieCard Composable dengan parameter onClick.
  3. Panggil callback onMovieSelected dengan objek film yang terkait dengan fungsi MovieCard 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. 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 TvLazyColumn Composable.
  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 Carousel Composable 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 Carousel Composable 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 CarouselSlide Composable ke fungsi Carousel Composable.
  3. Tambahkan fungsi Text Composable ke fungsi CarouselSlide Composable untuk menampilkan judul film.

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

Menampilkan gambar latar

Fungsi CarouselSlide Composable dapat menggunakan lambda lain untuk menentukan cara latar belakang fungsi CarouselSlide Composable ditampilkan.

Untuk menampilkan gambar latar, ikuti langkah-langkah berikut:

  1. Teruskan lambda ke fungsi CarouselSlide Composable dengan parameter background.
  2. Panggil fungsi AsyncImage Composable untuk memuat gambar latar yang terkait dengan objek Movie ke latar belakang fungsi CarouselSlide Composable.
  3. Perbarui posisi dan gaya teks fungsi Text Composable dalam fungsi CarouselSlide Composable untuk visibilitas yang lebih baik.
  4. Setel placeholder ke fungsi AsyncImage Composable untuk menghindari pergeseran tata letak. Kode awal memiliki placeholder sebagai drawable yang dapat Anda referensikan dengan R.drawable.placeholder.

CatalogBrowser.kt

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.tv.foundation.lazy.list.TvLazyColumn
import androidx.tv.foundation.lazy.list.TvLazyRow
import androidx.tv.foundation.lazy.list.items
import androidx.tv.material3.Carousel
import androidx.tv.material3.ExperimentalTvMaterial3Api
import androidx.tv.material3.Text
import coil.compose.AsyncImage
import com.example.tvcomposeintroduction.R
import com.example.tvcomposeintroduction.data.Movie
import com.example.tvcomposeintroduction.ui.components.MovieCard

@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = viewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
    val categoryList by
    catalogBrowserViewModel.categoryList.collectAsState()
    TvLazyColumn(
        modifier = modifier,
        verticalArrangement = Arrangement.spacedBy(16.dp),
        contentPadding = PaddingValues(horizontal = 48.dp, vertical = 32.dp)
    ) {
        item {
            val featuredMovieList by catalogBrowserViewModel.featuredMovieList.collectAsState()
            Carousel(
                slideCount = featuredMovieList.size,
                modifier = Modifier
                    .fillMaxWidth()
                    .height(376.dp),
            ) { indexOfCarouselItem ->
                val featuredMovie = featuredMovieList[indexOfCarouselItem]
                CarouselSlide(
                    background = {
                        AsyncImage(
                            model = featuredMovie.backgroundImageUrl,
                            contentDescription = null,
                            placeholder = painterResource(
                                id = R.drawable.placeholder
                            ),
                            contentScale = ContentScale.Crop,
                            modifier = Modifier.fillMaxSize(),
                        )
                    },
                ) {
                    Text(text = featuredMovie.title)
                }
            }
        }
        items(categoryList) { category ->
            Text(text = category.name)
            TvLazyRow(
                horizontalArrangement = Arrangement.spacedBy(8.dp)
            ) {
                items(category.movieList) { movie ->
                    MovieCard(movie = movie, onClick = { onMovieSelected(movie) })
                }
            }
        }
    }
}

Menambahkan transisi layar ke layar detail

Anda dapat mengizinkan pengguna mengklik fungsi CarouselSlide Composable.

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

  1. Teruskan nilai return metode Modifier.clickable ke fungsi CarouselSlide Composable melalui parameter modifier.
  2. Panggil fungsi onMovieSelected dengan objek Movie untuk fungsi CarouselSlide Composable yang terlihat di lambda yang diteruskan ke metode Modifier.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. 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.