TV용 Compose 소개

1. 시작하기 전에

TV용 Compose는 Android TV에서 실행되는 앱을 개발하는 최신 UI 프레임워크입니다. 이를 통해 TV 앱용 Jetpack Compose의 모든 이점을 활용할 수 있으므로 디자인과 기능이 뛰어난 앱 UI를 더 쉽게 빌드할 수 있습니다. TV용 Compose의 구체적인 이점은 다음과 같습니다.

  • 유연성. Compose를 사용하면 간단한 레이아웃부터 복잡한 애니메이션에 이르기까지 모든 유형의 UI를 만들 수 있습니다. 구성요소는 즉시 사용할 수 있지만 앱의 요구사항에 맞게 맞춤설정하거나 스타일을 지정할 수 있습니다.
  • 개발 간소화 및 가속화. Compose는 기존 코드와 호환되며 개발자가 더 적은 코드로 앱을 빌드할 수 있습니다.
  • 직관적. Compose는 선언적 문법을 사용하므로, 직관적으로 UI를 변경하고 코드를 이해하고 디버그 및 검토할 수 있습니다.

TV 앱의 일반적인 사용 사례는 미디어 소비입니다. 사용자는 콘텐츠 카탈로그를 둘러보고 시청할 콘텐츠를 선택합니다. 콘텐츠는 영화, TV 프로그램 또는 팟캐스트일 수 있습니다. 사용자는 콘텐츠를 선택한 후에 간략한 설명, 재생 시간, 크리에이터 이름 등 자세한 내용을 확인하고 싶을 수 있습니다. 이 Codelab에서는 TV용 Compose를 사용하여 카탈로그 브라우저 화면과 세부정보 화면을 구현하는 방법을 알아봅니다.

기본 요건

  • 람다를 포함한 Kotlin 문법 사용 경험
  • 기본적인 Compose 사용 경험. Compose에 익숙하지 않다면 Jetpack Compose 기본사항 Codelab을 완료하세요.
  • 컴포저블과 수정자에 관한 기본 지식

빌드할 항목

  • 카탈로그 브라우저 화면과 세부정보 화면이 있는 동영상 플레이어 앱
  • 사용자가 선택하도록 동영상 목록을 보여주는 카탈로그 브라우저 화면. 아래 이미지와 같이 표시됩니다.

카탈로그 브라우저에는 위에 캐러셀이 있는 추천 동영상\n목록이 표시됩니다.\n화면에 각 카테고리의 영화 목록도 표시됩니다.

  • 선택한 동영상에 관한 제목, 설명, 길이 등의 메타데이터를 보여주는 세부정보 화면. 아래 이미지와 같이 표시됩니다.

세부정보 화면에는 영화에 관해 제목, 스튜디오, 간략한 설명\n등의 메타데이터가 표시됩니다.\n메타데이터는 영화와 연결된 배경 이미지에 표시됩니다.

필요한 항목

2. 설정

이 Codelab의 테마 설정과 기본 설정이 포함된 코드를 가져오려면 다음 중 하나를 실행합니다.

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

main 브랜치에는 시작 코드가 있고 solution 브랜치에는 솔루션 코드가 포함되어 있습니다.

  • 시작 코드가 포함된 main.zip 파일과 솔루션 코드가 포함된 solution.zip 파일을 다운로드합니다.

코드를 다운로드했으므로 이제 Android 스튜디오에서 IntroductionToComposeForTV 프로젝트 폴더를 엽니다. 이제 시작할 준비가 되었습니다.

3. 카탈로그 브라우저 화면 구현

카탈로그 브라우저 화면을 통해 사용자는 영화 카탈로그를 둘러볼 수 있습니다. 카탈로그 브라우저를 Composable 함수로 구현합니다. CatalogBrowser.kt 파일에서 CatalogBrowser Composable 함수를 찾을 수 있습니다. 이 Composable 함수에서 카탈로그 브라우저 화면을 구현합니다.

시작 코드에는 CatalogBrowserViewModel 클래스라는 ViewModel이 있으며, 이 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 = {}
) {
}

카테고리 이름 표시

Category 목록의 흐름catalogBrowserViewModel.categoryList 속성을 사용하여 카테고리 목록에 액세스할 수 있습니다. 이 흐름은 collectAsState 메서드 호출을 통해 Compose State 객체로 수집됩니다. Category 객체에는 카테고리 이름을 나타내는 String 값인 name 속성이 있습니다.

카테고리 이름을 표시하려면 다음 단계를 따르세요.

  1. Android 스튜디오에서 시작 코드의 CatalogBrowser.kt 파일을 열고 TvLazyColumn Composable 함수를 CatalogBrowser Composable 함수에 추가합니다.
  2. catalogBrowserViewModel.categoryList.collectAsState() 메서드를 호출하여 흐름을 State 객체로 수집합니다.
  3. categoryList를 이전 단계에서 만든 State 객체의 위임 속성으로 선언합니다.
  4. categoryList 변수를 매개변수로 사용하여 items 함수를 호출합니다.
  5. 카테고리 이름을 람다 인수로 전달되는 매개변수로 사용하여 Text 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.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 함수를 추가하고 이 함수에 람다를 전달합니다.
  2. 람다에서 items 함수를 호출하며 이때 category.movieList 속성 값을 사용합니다. 그런 다음, 함수에 람다를 전달합니다.
  3. items 함수에 전달된 람다에서 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 객체는 Arrangement#spacedBy 메서드를 호출하여 생성됩니다.
  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 객체를 사용하여 열 주위에 세로 간격 32dp 및 가로 간격 48dp를 설정합니다.
  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 함수를 Details Composable 함수를 통해 전달된 modifier 객체가 있는 Column Composable 함수의 래퍼로 추가합니다.
  2. Box Composable 함수에서 modifier 객체의 fillMaxSize 메서드를 호출하여 Details Composable 함수에 할당 가능한 최대 크기를 Box Composable 함수가 채우도록 합니다.
  3. 다음 매개변수가 있는 AsyncImage Composable 함수를 Box Composable 함수에 추가합니다.
  • 지정된 Movie 객체의 backgroundImageUrl 속성 값을 model 매개변수로 설정합니다.
  • nullcontentDescription 매개변수에 전달합니다.
  • ContentScale.Crop 객체를 contentScale 매개변수에 전달합니다. 다양한 ContentScale 옵션을 보려면 콘텐츠 크기 조정을 참고하세요.
  • Modifier.fillMaxSize 메서드의 반환 값을 modifier 매개변수에 전달합니다.
  • Modifier.padding 메서드 호출을 통해 생성된 Modifier 객체를 설정하여 세로 간격 32dp 및 가로 간격 48dp를 열에 설정합니다.

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 수정자를 사용하여 event 리스너를 MovieCard Composable 함수에 추가합니다. 방향 패드의 가운데 버튼을 누르면 MovieCard Composable 함수와 연결된 영화 객체로 인수로 하여 CatalogBrowserViewModel#showDetails 메서드가 호출됩니다.

  1. com.example.tvcomposeintroduction.ui.screens.CatalogBrowser 파일을 엽니다.
  2. onClick 매개변수를 사용하여 람다 함수를 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 함수에 전달된 람다에서 위임 속성으로 featuredMovieList를 선언한 후에 State 객체를 위임되도록 설정합니다. 이는 catalogBrowserViewModel.featuredMovieList 속성에서 수집됩니다.
  4. item 함수 내에서 Carousel Composable 함수를 호출한 후에 다음 매개변수를 전달합니다.
  • slideCount 매개변수를 통한 featuredMovieList 변수의 크기
  • Modifier.fillMaxWidth 메서드 및 Modifier.height 메서드를 사용하여 캐러셀 크기를 지정하는 Modifier 객체. Carousel Composable 함수는 Modifier.height 메서드에 376.dp 값을 전달하여 높이 376dp를 사용합니다.
  • 표시된 캐러셀 항목의 색인을 나타내는 정수 값으로 호출된 람다
  1. featuredMovieList 변수와 지정된 색인 값에서 Movie 객체를 검색합니다.
  2. Carousel Composable 함수에 CarouselSlide Composable 함수를 추가합니다.
  3. Text Composable 함수를 CarouselSlide 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 함수는 다른 람다를 사용하여 CarouselSlide Composable 함수의 배경이 표시되는 방식을 지정할 수 있습니다.

배경 이미지를 표시하려면 다음 단계를 따르세요.

  1. background 매개변수를 사용하여 람다를 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 메서드에 전달된 람다에서 표시된 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. 솔루션 코드 가져오기

이 Codelab의 솔루션 코드를 다운로드하려면 다음 중 하나를 실행합니다.

  • 다음 버튼을 클릭하여 ZIP 파일로 다운로드한 후에 압축을 풀고 Android 스튜디오에서 엽니다.

  • Git을 사용하여 가져옵니다.
$ git clone https://github.com/android/tv-codelabs.git
$ cd tv-codelabs
$ git checkout solution
$ cd IntroductionToComposeForTV

8. 축하합니다.

축하합니다. TV용 Compose의 기본사항을 살펴보았습니다.

  • TvLazyColumn과 TvLazyRow를 결합하여 콘텐츠 목록을 표시하도록 화면을 구현하는 방법
  • 콘텐츠 세부정보를 표시하는 기본 화면 구현
  • 두 화면 간의 화면 전환을 추가하는 방법