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 基本概念」程式碼研究室。
  • 具備可組合函式和修飾符的基本知識。

建構內容

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

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

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

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

需求條件

2. 做好準備

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

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

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

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

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

3. 實作目錄瀏覽器畫面

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

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

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

顯示類別名稱

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

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

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

顯示每個類別的內容清單

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

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

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

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

選用:調整版面配置

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  1. 新增 Box Composable 函式做為 Column Composable 函式的包裝函式,其中 modifier 物件是透過 Details Composable 函式傳遞。
  2. Box Composable 函式中,呼叫 modifier 物件的 fillMaxSize 方法,讓 Box Composable 函式填入可分配至 Details Composable 函式的最大值。
  3. 將含有下列參數的 AsyncImage Composable 函式新增至 Box Composable 函式:
  • 將指定 Movie 物件的 backgroundImageUrl 屬性值設為 model 參數。
  • null 傳遞至 contentDescription 參數。
  • ContentScale.Crop 物件傳遞至 contentScale 參數。如要查看不同的 ContentScale 選項,請參閱「內容縮放」。
  • Modifier.fillMaxSize 方法的傳回值傳遞至 modifier 參數。
  • 使用呼叫 Modifier.padding 方法建立的 Modifier 物件,將資料欄的垂直間距設為 32 dp,水平間距設為 48 dp。

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

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

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

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

  1. MaterialTheme.typography.headlineLarge 屬性設為電影標題的文字樣式。
  2. MaterialTheme.typography.headlineMedium 屬性設為其他兩個 Text Composable 函式的文字樣式。
  3. 使用 Modifier.background 方法,將 MaterialTheme.colorScheme.background 屬性設為 Column Composable 函式的背景顏色。

[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. 在畫面之間新增導覽

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

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

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

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

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

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

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

顯示背景圖片

CarouselSlide Composable 函式可接受另一個 lambda,以指定 CarouselSlide Composable 函式的背景顯示方式。

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

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

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

您可以讓使用者按一下 CarouselSlide Composable 函式。

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

  1. 透過 modifier 參數,將 Modifier.clickable 方法的傳回值傳遞至 CarouselSlide Composable 函式。
  2. 在傳遞至 Modifier.clickable 方法的 lambda 中,使用可見的 CarouselSlide Composable 函式的 Movie 物件呼叫 onMovieSelected 函式。

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

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

  • 點選下方按鈕即可將其下載為 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,實作顯示內容清單的螢幕畫面。
  • 實作顯示內容詳細資料的基本畫面。
  • 如何在兩個畫面之間新增畫面轉場。