Compose for TV 簡介

1. 事前準備

Compose for TV 是最新的 UI 架構,用於開發在 Android TV 上執行的應用程式。此架構可享有適用於 TV 應用程式的 Jetpack Compose 所有優勢,更輕鬆地為應用程式建構美觀且實用的使用者介面。Compose for TV 的一些特定優點如下:

  • 高度彈性。Compose 可用於建立任何類型的 UI,從簡單的版面配置到複雜的動畫都沒問題。元件可立即使用,但也可以配合應用程式需求自訂及設定樣式。
  • 簡化及加速開發作業。Compose 與現有程式碼相容,且可讓開發人員用較少的程式碼建構應用程式。
  • 操作直覺:Compose 採用宣告式語法,讓使用者以符合直覺的方式變更使用者介面,以及偵錯、解讀和檢查程式碼。

電視應用程式的常見用途是使用媒體。使用者瀏覽內容目錄,然後選取要觀看的內容。內容可以是電影、電視節目或 Podcast。使用者選取內容後,可能會想查看更多內容資訊,例如簡短說明、播放長度和創作者名稱。在本程式碼研究室中,您可以學到如何使用 Compose for TV 實作目錄瀏覽器畫面和詳細資料畫面。

必要條件

  • 具備 Kotlin 語法經驗 (包括 lambda)。
  • 具備 Compose 的基本經驗。如果您不熟悉 Compose,請先完成「Jetpack Compose 基本概念」程式碼研究室。
  • 具備可組合函式和修飾符的基本知識。
  • 使用下列任一裝置執行範例應用程式:
    • Android TV 裝置
    • 在電視裝置定義類別中含有設定檔的Android 虛擬裝置

建構內容

  • 包含目錄瀏覽器畫面和詳細資料畫面的影片播放器應用程式。
  • 目錄瀏覽器畫面,顯示使用者可選擇的影片清單。如下圖所示:

目錄瀏覽器會在畫面頂端\n以輪轉介面方式顯示精選電影清單。\n畫面中也會顯示每個類別的電影清單。

  • 詳細資料畫面,顯示所選影片的中繼資料,例如標題、說明和長度。如下圖所示:

詳細資料畫面會顯示電影的中繼資料,\n包括電影名稱、電影公司和簡短說明。\n中繼資料會顯示在與電影相關的背景圖片中。

需求條件

  • 最新版 Android Studio
  • Android TV 裝置或電視裝置類別中的虛擬裝置

2. 做好準備

如要取得含有本程式碼研究室的主題設定和基本設定程式碼,請執行下列任一操作:

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

main 分支版本含有範例程式碼,solution 分支版本則包含解決方案程式碼。

  • 下載含有範例程式碼的 main.zip 檔案,以及包含解決方案程式碼的 solution.zip 檔案。

下載程式碼後,請在 Android Studio 中開啟「IntroductionToComposeForTV」專案資料夾。您已完成開始本研究室的準備。

3. 實作目錄瀏覽器畫面

目錄瀏覽器畫面可讓使用者瀏覽電影目錄。您要將目錄瀏覽器實作為可組合函式。您可以在 CatalogBrowser.kt 檔案中找到 CatalogBrowser 可組合函式。您會在這個可組合函式中實作目錄瀏覽器畫面。

範例程式碼含有稱為 CatalogBrowserViewModel 類別的 ViewModel,其中包含幾種屬性和方法,用於擷取描述電影內容的 Movie 物件。您要使用擷取的 Movie 物件實作目錄瀏覽器。

CatalogBrowser.kt

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

顯示類別名稱

您可以使用 catalogBrowserViewModel.categoryList 屬性 (即 Category 清單的資料流) 存取類別清單。系統會透過呼叫資料流的 collectAsStateWithLifecycle 方法,將資料流收集為 Compose State 物件。Category 物件包含 name 屬性,是代表類別名稱的 String 值。

如要顯示類別名稱,請按照下列步驟操作:

  1. 在 Android Studio 中開啟範例程式碼的 CatalogBrowser.kt 檔案,然後將 TvLazyColumn 可組合函式新增至 CatalogBrowser 可組合函式。
  2. 呼叫 catalogBrowserViewModel.categoryList.collectAsStateWithLifeCycle() 方法,將資料流收集為 State 物件。
  3. categoryList 宣告為您在上一個步驟中所建立 State 物件的委派屬性。
  4. 使用 categoryList 變數做為參數來呼叫 items 函式。
  5. 使用類別名稱做為參數來呼叫 Text 可組合函式,該參數會做為 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)
        }
    }
}

顯示每個類別的內容清單

Category 物件還有另一個名為 movieList 的屬性。該屬性是 Movie 物件清單,代表屬於該類別的電影。

如要顯示各個類別的內容清單,請按照下列步驟操作:

  1. 新增 TvLazyRow 可組合函式,然後將 lambda 傳遞至該函式。
  2. 在 lambda 中,使用 category.movieList 屬性值呼叫 items 函式,然後將 lambda 傳遞至該函式。
  3. 在傳遞至 items 函式的 lambda 中,使用 Movie 物件呼叫 MovieCard 可組合函式。

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

選用:調整版面配置

  1. 如要設定類別之間的間距,請使用 verticalArrangement 參數將 Arrangement 物件傳遞至 TvLazyColumn 可組合函式。請透過呼叫 Arrangement#spacedBy 方法來建立 Arrangement 物件。
  2. 如要設定電影資訊卡之間的間距,請使用 horizontalArrangement 參數將 Arrangement 物件傳遞至 TvLazyRow 可組合函式。
  3. 若要為資料欄設定縮排,請使用 contentPadding 參數傳遞 PaddingValue 物件。

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. 實作詳細資料畫面

詳細資料畫面會顯示所選電影的詳細資料。Details.kt 檔案中有 Details 可組合函式。請將程式碼新增至這個函式,以實作詳細資料畫面。

Details.kt

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

顯示電影名稱、電影公司名稱和說明

Movie 物件包含下列三個字串屬性,做為電影的中繼資料:

  • title:電影名稱。
  • studio:製作電影的公司名稱。
  • description:電影的簡短摘要。

如要在詳細資料畫面上顯示這項中繼資料,請按照下列步驟操作:

  1. 新增 Column 可組合函式,然後使用由 Modifier.padding 方法建立的 Modifier 物件,將資料欄周圍的垂直間距設為 32 dp,水平間距設為 48 dp。
  2. 新增 Text 可組合函式以顯示電影標題。
  3. 新增 Text 可組合函式以顯示電影公司名稱。
  4. 新增 Text 可組合函式以顯示電影說明。

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

Details 可組合函式的參數中指定的 Modifier 物件,會在下一個工作中用到。

顯示與指定 Movie「物件」相關聯的背景圖片

Movie 物件包含 backgroundImageUrl 屬性,表示物件所描述電影的背景圖片位置。

如要顯示特定電影的背景圖片,請按照下列步驟操作:

  1. 新增 Box 可組合函式做為 Column 可組合函式的包裝函式,其中 modifier 物件是透過 Details 可組合函式傳遞。
  2. Box 可組合函式中,呼叫 modifier 物件的 fillMaxSize 方法,讓 Box 可組合函式填入可分配至 Details 可組合函式的大小上限。
  3. 將含有下列參數的 AsyncImage 可組合函式新增至 Box 可組合函式:
  • 將指定 Movie 物件的 backgroundImageUrl 屬性值設為 model 參數。
  • null 傳遞至 contentDescription 參數。
  • ContentScale.Crop 物件傳遞至 contentScale 參數。如要查看不同的 ContentScale 選項,請參閱「內容縮放」。
  • Modifier.fillMaxSize 方法的傳回值傳遞至 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)
        }
    }
}

參照 MaterialTheme 物件,讓主題設定保持一致

MaterialTheme 物件含有用於參照目前主題值的函式,例如 TypographyColorScheme 類別中的值。

如要參照 MaterialTheme 物件以讓主題設定保持一致,請按照下列步驟操作:

  1. MaterialTheme.typography.displayMedium 屬性設為電影標題的文字樣式。
  2. MaterialTheme.typography.bodySmall 屬性設為第二個 Text 可組合函式的文字樣式。
  3. 使用 Modifier.background 方法,將 MaterialTheme.colorScheme.background 屬性設為 Column 可組合函式的背景顏色。

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

選用:調整版面配置

如要調整 Details 可組合函式的版面配置,請按照下列步驟操作:

  1. 設定 Box 可組合函式,透過 fillMaxSize 修飾符使用完整的可用空間
  2. 使用 background 修飾符設定 Box 可組合函式的背景,以便用呼叫 Brush.linearGradient 函式 (其中含有包含 MaterialTheme.colorScheme.backgroundColor.TransparentColor 物件清單) 所建立的線性漸層填入背景
  3. 使用 padding 修飾符,在 Column 可組合函式周圍設定 48.dp 水平和 24.dp 垂直間距
  4. 使用 width 修飾符 (以 0.5f 值呼叫 Modifier.width 函式所建立) 設定 Column 可組合函式的寬度
  5. 在第二個 Text 可組合函式和第三個 Text 可組合函式之間,使用 Spacer 新增 8.dp 空間。Spacer 可組合函式的高度,是透過 Modifier.height 函式建立的 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. 在畫面之間新增導覽

現在您已擁有目錄瀏覽器畫面和詳細資料畫面。使用者在目錄瀏覽器畫面中選取內容後,該畫面就必須轉換為詳細資料畫面。為此,請使用 clickable 修飾符在 MovieCard 可組合函式中加入 event 事件監聽器。當使用者按下方向鍵中間的按鈕時,請使用與 MovieCard 可組合函式相關聯的電影物件做為引數來呼叫 CatalogBrowserViewModel#showDetails 方法。

  1. 開啟 com.example.tvcomposeintroduction.ui.screens.CatalogBrowser 檔案。
  2. 使用 onClick 參數將 lambda 函式傳遞至 MovieCard 可組合函式。
  3. 使用與 MovieCard 可組合函式相關聯的電影物件呼叫 onMovieSelected 回呼。

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. 將輪轉介面加入目錄瀏覽器畫面,藉此凸顯精選內容

輪轉介面是一種常見的自動調整式 UI 元件,會在指定的時間長度過後自動更新介面的投影片,通常用來凸顯精選內容。

如要在目錄瀏覽器畫面中新增輪轉介面,藉此凸顯精選內容清單中的電影,請按照下列步驟操作:

  1. 開啟 com.example.tvcomposeintroduction.ui.screens.CatalogBrowser 檔案。
  2. 呼叫 item 函式,將項目新增至 TvLazyColumn 可組合函式。
  3. 在傳遞至 item 函式的 lambda 中宣告 featuredMovieList 做為委派屬性,然後將 State 物件 (從 catalogBrowserViewModel.featuredMovieList 屬性收集而來) 設為委派項目。
  4. item 函式中呼叫 Carousel 可組合函式,然後傳入下列參數:
  • 透過 slideCount 參數設定 featuredMovieList 變數的大小。
  • Modifier 物件,用於透過 Modifier.fillMaxWidthModifier.height 方法指定輪轉介面的大小。Carousel 可組合函式會藉由將 376.dp 值傳遞至 Modifier.height 方法,將高度設為 376 dp。
  • 透過整數值呼叫的 lambda,代表可見輪轉介面項目的索引。
  1. featuredMovieList 變數和指定的索引值擷取 Movie 物件。
  2. Box 可組合函式新增至 Carousel 可組合函式。
  3. Box 可組合函式中新增 Text 可組合函式,即可顯示電影標題。

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

顯示背景圖片

Box 可組合函式會將一個元件放在另一個元件上。詳情請參閱「版面配置基本概念」一文。

如要顯示背景圖片,請按照下列步驟操作:

  1. 呼叫 AsyncImage 可組合函式,在 Text 可組合函式之前載入與 Movie 物件相關聯的背景圖片。
  2. 更新 Text 可組合函式的位置和文字樣式,讓畫面更加清楚。
  3. 將預留位置設為 AsyncImage 可組合函式,以免版面配置位移。範例程式碼含有預留位置做為可繪項目,您可以使用 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) })
                }
            }
        }
    }
}

在詳細資料畫面中新增畫面轉場

您可以在輪轉介面中新增 Button,讓使用者點選按鈕觸發畫面轉換,前往到詳細資料畫面。

如要讓使用者在詳細資料畫面上,查看可見輪轉介面中的電影詳細資料,請按照下列步驟操作:

  1. Carousel 可組合函式的 Box 可組合函式中呼叫 Column 可組合函式
  2. Carousel 中的 Text 可組合函式移至 Column 可組合函式
  3. Column 可組合函式的 Text 可組合函式後方呼叫 Button 可組合函式
  4. Button 可組合函式中,使用 R.string.show_details 呼叫的 stringResource 函式傳回值來呼叫 Text 可組合函式。
  5. 在傳遞至 Button 可組合函式的 onClick 參數的 lambda 中使用 featuredMovie 變數呼叫 onMovieSelected 函式

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

選用:調整版面配置

如要調整輪轉介面的版面配置,請按照下列步驟操作:

  1. Carousel 可組合函式中,以 MaterialTheme.colorScheme.background 值指派 backgroundColor
  2. 使用 Box 可組合函式納入 Column 可組合函式
  3. Alignment.BottomStart 值傳遞至 Box 元件的 contentAlignment 參數。
  4. fillMaxSize 修飾符傳遞至 Box 可組合函式的修飾符參數。fillMaxSize 修飾符是以 Modifier.fillMaxSize() 函式建立。
  5. 透過傳遞至 Box 可組合函式的 fillMaxSize 修飾符呼叫 drawBehind() 方法
  6. 在傳遞至 drawBehind 修飾符的 lambda 中,以 Brush 物件 (透過呼叫含有兩個 Color 物件的清單的 Brush.linearGradient 函式所建立) 指派 brush 值。清單是透過呼叫含有 backgroundColor 值和 Color.Transparent 值的 listOf 函式所建立。
  7. 在傳遞至 drawBehind 修飾符的 lambda 中使用 brush 物件呼叫 drawRect,藉此在背景圖片上建立 Srim 層
  8. 使用透過呼叫含有 20.dp 值的 Modifier.padding 所建立的 padding 修飾符,指定 Column 可組合函式的邊框間距。
  9. Column 可組合函式的 Text 可組合函式和 Button 可組合函式之間,新增含有 20.dp 值的 Spacer 可組合函式

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. 取得解決方案程式碼

如要下載本程式碼研究室的解決方案程式碼,請執行下列任一操作:

  • 點選下方按鈕即可將其下載為 ZIP 檔案,然後解壓縮並在 Android Studio 中開啟。

  • 使用 Git 擷取檔案:
$ git clone https://github.com/android/tv-codelabs.git
$ cd tv-codelabs
$ git checkout solution
$ cd IntroductionToComposeForTV

8. 恭喜。

恭喜!您已瞭解 Compose for TV 的基本概念:

  • 如何透過結合 TvLazyColumn 和 TvLazyLOW,實作顯示內容清單的螢幕畫面。
  • 實作顯示內容詳細資料的基本畫面。
  • 如何在兩個畫面之間新增畫面轉場。