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을 완료하세요.
- 컴포저블과 수정자에 관한 기본 지식
- 샘플 앱을 실행할 다음 기기 중 하나:
- Android TV 기기
- TV 기기 정의 카테고리에 프로필이 있는 Android 가상 기기
빌드할 항목
- 카탈로그 브라우저 화면과 세부정보 화면이 있는 동영상 플레이어 앱
- 사용자가 선택하도록 동영상 목록을 보여주는 카탈로그 브라우저 화면. 아래 이미지와 같이 표시됩니다.
- 선택한 동영상에 관한 제목, 설명, 길이 등의 메타데이터를 보여주는 세부정보 화면. 아래 이미지와 같이 표시됩니다.
필요한 항목
- 최신 버전의 Android 스튜디오
- Android TV 기기 또는 TV 기기 카테고리의 가상 기기
2. 설정
이 Codelab의 테마 설정과 기본 설정이 포함된 코드를 가져오려면 다음 중 하나를 실행합니다.
- 이 GitHub 저장소에서 코드를 클론합니다.
$ git clone https://github.com/android/tv-codelabs.git
main
브랜치에는 시작 코드가 있고 solution
브랜치에는 솔루션 코드가 포함되어 있습니다.
- 시작 코드가 포함된
main.zip
파일과 솔루션 코드가 포함된solution.zip
파일을 다운로드합니다.
코드를 다운로드했으므로 이제 Android 스튜디오에서 IntroductionToComposeForTV 프로젝트 폴더를 엽니다. 이제 시작할 준비가 되었습니다.
3. 카탈로그 브라우저 화면 구현
카탈로그 브라우저 화면을 통해 사용자는 영화 카탈로그를 둘러볼 수 있습니다. 카탈로그 브라우저를 컴포저블 함수로 구현합니다. CatalogBrowser.kt
파일에서 CatalogBrowser
컴포저블 함수를 찾을 수 있습니다. 이 컴포저블 함수에서 카탈로그 브라우저 화면을 구현합니다.
시작 코드에는 CatalogBrowserViewModel
클래스라는 ViewModel이 있으며, 이 ViewModel에는 영화 콘텐츠를 설명하는 Movie
객체를 검색하는 여러 속성과 메서드가 있습니다. 가져온 Movie
객체로 카탈로그 브라우저를 구현합니다.
CatalogBrowser.kt
@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
modifier: Modifier = Modifier,
catalogBrowserViewModel: CatalogBrowserViewModel = hiltViewModel(),
onMovieSelected: (Movie) -> Unit = {}
) {
}
카테고리 이름 표시
Category
목록의 흐름인 catalogBrowserViewModel.categoryList
속성을 사용하여 카테고리 목록에 액세스할 수 있습니다. 이 흐름은 collectAsStateWithLifecycle
메서드 호출을 통해 Compose State
객체로 수집됩니다. Category
객체에는 카테고리 이름을 나타내는 String
값인 name
속성이 있습니다.
카테고리 이름을 표시하려면 다음 단계를 따르세요.
- Android 스튜디오에서 시작 코드의
CatalogBrowser.kt
파일을 열고LazyColumn
컴포저블 함수를CatalogBrowser
컴포저블 함수에 추가합니다. catalogBrowserViewModel.categoryList.collectAsStateWithLifeCycle()
메서드를 호출하여 흐름을State
객체로 수집합니다.categoryList
를 이전 단계에서 만든State
객체의 위임 속성으로 선언합니다.categoryList
변수를 매개변수로 사용하여items
함수를 호출합니다.- 카테고리 이름을 람다 인수로 전달되는 매개변수로 사용하여
Text
컴포저블 함수를 호출합니다.
CatalogBrowser.kt
@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
modifier: Modifier = Modifier,
catalogBrowserViewModel: CatalogBrowserViewModel = hiltViewModel(),
onMovieSelected: (Movie) -> Unit = {}
) {
val categoryList by catalogBrowserViewModel.categoryList.collectAsStateWithLifecycle()
LazyColumn(modifier = modifier) {
items(categoryList) { category ->
Text(text = category.name)
}
}
}
각 카테고리의 콘텐츠 목록 표시
Category
객체에는 movieList
라는 또 다른 속성이 있습니다. 이 속성은 카테고리에 속한 영화를 나타내는 Movie
객체의 목록입니다.
각 카테고리의 콘텐츠 목록을 표시하려면 다음 단계를 따르세요.
LazyRow
컴포저블 함수를 추가하고 이 함수에 람다를 전달합니다.- 람다에서
items
함수를 호출하며 이때category
.movieList
속성 값을 사용합니다. 그런 다음, 함수에 람다를 전달합니다. items
함수에 전달된 람다에서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()
LazyColumn(modifier = modifier) {
items(categoryList) { category ->
Text(text = category.name)
LazyRow {
items(category.movieList) {movie ->
MovieCard(movie = movie)
}
}
}
}
}
선택사항: 레이아웃 조정
- 카테고리 간의 간격을 설정하려면
verticalArrangement
매개변수를 사용하여Arrangement
객체를LazyColumn
컴포저블 함수에 전달합니다.Arrangement
객체는Arrangement#spacedBy
메서드를 호출하여 생성됩니다. - 영화 카드 간의 간격을 설정하려면
horizontalArrangement
매개변수를 사용하여Arrangement
객체를LazyRow
컴포저블 함수에 전달합니다. - 열에 들여쓰기를 설정하려면
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()
LazyColumn(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(16.dp),
contentPadding = PaddingValues(horizontal = 48.dp, vertical = 32.dp)
) {
items(categoryList) { category ->
Text(text = category.name)
LazyRow(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
items(category.movieList) { movie ->
MovieCard(movie = movie)
}
}
}
}
}
4. 세부정보 화면 구현
세부정보 화면에는 선택한 영화에 관한 세부정보가 표시됩니다. Details.kt
파일에 Details
컴포저블 함수가 있습니다. 이 함수에 코드를 추가하여 세부정보 화면을 구현합니다.
Details.kt
@Composable
fun Details(movie: Movie, modifier: Modifier = Modifier) {
}
영화 제목, 스튜디오 이름, 설명 표시
Movie
객체에는 영화의 메타데이터로 다음과 같은 세 문자열 속성이 있습니다.
title
. 영화 제목studio
. 영화를 제작한 스튜디오의 이름description
. 영화에 대한 간단한 요약
이 메타데이터를 세부정보 화면에 표시하려면 다음 단계를 따르세요.
Column
컴포저블 함수를 추가한 후에Modifier.padding
메서드로 만든Modifier
객체를 사용하여 열 주위에 세로 간격 32dp 및 가로 간격 48dp를 설정합니다.Text
컴포저블 함수를 추가하여 영화 제목을 표시합니다.Text
컴포저블 함수를 추가하여 스튜디오 이름을 표시합니다.Text
컴포저블 함수를 추가하여 영화 설명을 표시합니다.
Details.kt
@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
속성이 있습니다.
특정 영화의 배경 이미지를 표시하려면 다음 단계를 따르세요.
Box
컴포저블 함수를Details
컴포저블 함수를 통해 전달된modifier
객체가 있는Column
컴포저블 함수의 래퍼로 추가합니다.Box
컴포저블 함수에서modifier
객체의fillMaxSize
메서드를 호출하여Details
컴포저블 함수에 할당 가능한 최대 크기를Box
컴포저블 함수가 채우도록 합니다.- 다음 매개변수가 있는
AsyncImage
컴포저블 함수를Box
컴포저블 함수에 추가합니다.
- 지정된
Movie
객체의backgroundImageUrl
속성 값을model
매개변수로 설정합니다. null
을contentDescription
매개변수에 전달합니다.
ContentScale.Crop
객체를contentScale
매개변수에 전달합니다. 다양한ContentScale
옵션을 보려면 콘텐츠 크기 조정을 참고하세요.Modifier.fillMaxSize
메서드의 반환 값을modifier
매개변수에 전달합니다.
Details.kt
@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
객체에는 Typography
및 ColorScheme
클래스의 함수와 같이 현재 테마 값을 참조하는 함수가 포함되어 있습니다.
일관된 테마 설정을 위해 MaterialTheme
객체를 참조하려면 다음 단계를 따르세요.
MaterialTheme.typography.displayMedium
속성을 영화 제목의 텍스트 스타일로 설정합니다.MaterialTheme.typography.bodySmall
속성을 두 번째Text
컴포저블 함수의 텍스트 스타일로 설정합니다.Modifier.background
메서드를 사용하여MaterialTheme.colorScheme.background
속성을Column
컴포저블 함수의 배경 색상으로 설정합니다.
Details.kt
@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
컴포저블 함수의 레이아웃을 조정하려면 다음 단계를 따르세요.
fillMaxSize
수정자를 통해 사용 가능한 전체 공간을 사용하도록Box
컴포저블 함수를 설정합니다.Box
컴포저블 함수의 백그라운드를background
수정자를 사용하여 설정하여 백그라운드를MaterialTheme.colorScheme.background
값과Color.Transparent
가 포함된Color
객체 목록으로Brush.linearGradient
함수를 호출하여 만든 선형 그래디언트로 채웁니다.padding
수정자를 사용하여Column
컴포저블 함수 주위에48.dp
가로 및24.dp
세로 간격을 설정합니다.0.5f
값으로Modifier.width
함수를 호출하여 만든width
수정자를 사용하여Column
컴포저블 함수의 너비를 설정합니다.Spacer
를 사용하여 두 번째Text
컴포저블 함수와 세 번째Text
컴포저블 사이에8.dp
공간을 추가합니다.Spacer
컴포저블 함수의 높이는Modifier.height
함수로 만든height
수정자로 지정됩니다.
Details.kt
@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
수정자를 사용하여 event
리스너를 MovieCard
컴포저블 함수에 추가합니다. 방향 패드의 가운데 버튼을 누르면 MovieCard
컴포저블 함수와 연결된 영화 객체로 인수로 하여 CatalogBrowserViewModel#showDetails
메서드가 호출됩니다.
com.example.tvcomposeintroduction.ui.screens.CatalogBrowser
파일을 엽니다.onClick
매개변수를 사용하여 람다 함수를MovieCard
컴포저블 함수에 전달합니다.MovieCard
컴포저블 함수와 연결된 영화 객체로onMovieSelected
콜백을 호출합니다.
CatalogBrowser.kt
@Composable
fun CatalogBrowser(
modifier: Modifier = Modifier,
catalogBrowserViewModel: CatalogBrowserViewModel = hiltViewModel(),
onMovieSelected: (Movie) -> Unit = {}
) {
val categoryList by
catalogBrowserViewModel.categoryList.collectAsStateWithLifecycle()
LazyColumn(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(16.dp),
contentPadding = PaddingValues(horizontal = 48.dp, vertical = 32.dp)
) {
items(categoryList) { category ->
Text(text = category.name)
LazyRow(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
items(category.movieList) { movie ->
MovieCard(movie = movie, onClick = { onMovieSelected(movie) })
}
}
}
}
}
6. 카탈로그 브라우저 화면에 캐러셀을 추가하여 추천 콘텐츠를 강조표시합니다.
캐러셀은 일반적으로 조정되는 UI 구성요소로, 지정된 시간이 지나면 슬라이드를 자동으로 업데이트합니다. 일반적으로 추천 콘텐츠를 강조하는 데 사용됩니다.
추천 콘텐츠 목록에서 영화를 강조표시하기 위해 카탈로그 브라우저 화면에 캐러셀을 추가하려면 다음 단계를 따르세요.
com.example.tvcomposeintroduction.ui.screens.CatalogBrowser
파일을 엽니다.item
함수를 호출하여LazyColumn
컴포저블 함수에 항목을 추가합니다.item
함수에 전달된 람다에서 위임 속성으로featuredMovieList
를 선언한 후에State
객체를 위임되도록 설정합니다. 이는catalogBrowserViewModel.featuredMovieList
속성에서 수집됩니다.item
함수 내에서Carousel
컴포저블 함수를 호출한 후에 다음 매개변수를 전달합니다.
slideCount
매개변수를 통한featuredMovieList
변수의 크기Modifier.fillMaxWidth
메서드 및Modifier.height
메서드를 사용하여 캐러셀 크기를 지정하는Modifier
객체.Carousel
컴포저블 함수는Modifier.height
메서드에376.dp
값을 전달하여 높이 376dp를 사용합니다.- 표시된 캐러셀 항목의 색인을 나타내는 정수 값으로 호출된 람다
featuredMovieList
변수와 지정된 색인 값에서Movie
객체를 검색합니다.Box
컴포저블 함수를Carousel
컴포저블 함수에 추가합니다.Text
컴포저블 함수를Box
컴포저블 함수에 추가하여 영화 제목을 표시합니다.
CatalogBrowser.kt
@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
modifier: Modifier = Modifier,
catalogBrowserViewModel: CatalogBrowserViewModel = hiltViewModel(),
onMovieSelected: (Movie) -> Unit = {}
) {
val categoryList by
catalogBrowserViewModel.categoryList.collectAsStateWithLifecycle()
LazyColumn(
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)
LazyRow(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
items(category.movieList) { movie ->
MovieCard(movie = movie, onClick = { onMovieSelected(movie) })
}
}
}
}
}
배경 이미지 표시
Box
컴포저블 함수는 한 구성요소를 다른 구성요소 위에 배치합니다. 자세한 내용은 레이아웃 기본사항을 참고하세요.
배경 이미지를 표시하려면 다음 단계를 따르세요.
AsyncImage
컴포저블 함수를 호출하여Movie
객체와 연결된 배경 이미지를Text
컴포저블 함수의 백그라운드에 로드합니다.- 가시성을 높이기 위해
Text
컴포저블 함수에서 위치와 텍스트 스타일을 업데이트합니다. - 레이아웃 이동을 방지하기 위해 자리표시자를
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()
LazyColumn(
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)
LazyRow(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
items(category.movieList) { movie ->
MovieCard(movie = movie, onClick = { onMovieSelected(movie) })
}
}
}
}
}
세부정보 화면에 화면 전환 추가
사용자가 버튼을 클릭하여 세부정보 화면으로의 화면 전환을 트리거할 수 있도록 Button
을 캐러셀에 추가할 수 있습니다.
세부정보 화면에 표시된 캐러셀에서 영화의 세부정보가 사용자에게 표시되도록 하려면 다음 단계를 따르세요.
Carousel
컴포저블의Box
컴포저블에서Column
컴포저블 함수를 호출합니다.Carousel
의Text
컴포저블을Column
컴포저블 함수로 이동합니다.Column
컴포저블 함수에서Text
컴포저블 함수 다음에Button
컴포저블 함수를 호출합니다.R.string.show_details
로 호출된stringResource
함수의 반환 값을 사용하여Button
컴포저블 함수에서Text
컴포저블 함수를 호출합니다.Button
컴포저블 함수의onClick
매개변수에 전달된 람다에서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()
LazyColumn(
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)
LazyRow(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
items(category.movieList) { movie ->
MovieCard(movie = movie, onClick = { onMovieSelected(movie) })
}
}
}
}
}
선택사항: 레이아웃 조정
캐러셀의 레이아웃을 조정하려면 다음 단계를 따르세요.
Carousel
컴포저블 함수에서backgroundColor
값을MaterialTheme.colorScheme.background
값과 함께 할당합니다.Column
컴포저블 함수를Box
컴포저블로 래핑합니다.Alignment.BottomStart
값을Box
구성요소의contentAlignment
매개변수에 전달합니다.fillMaxSize
수정자를Box
컴포저블 함수의 수정자 매개변수에 전달합니다.fillMaxSize
수정자는Modifier.fillMaxSize()
함수로 만들어집니다.Box
컴포저블에 전달된fillMaxSize
수정자를 통해drawBehind()
메서드를 호출합니다.drawBehind
수정자에 전달된 람다에서 2개의Color
객체 목록으로Brush.linearGradient
함수를 호출하여 만든Brush
객체로brush
값을 할당합니다.backgroundColor
값과Color.Transparent
값으로listOf
함수를 호출하여 목록을 만듭니다.drawBehind
수정자에 전달된 람다에서brush
객체로drawRect
를 호출하여 배경 이미지 위에 srim 레이어를 만듭니다.20.dp
값으로Modifier.padding
을 호출하여 만든padding
수정자를 사용하여Column
컴포저블 함수의 패딩을 지정합니다.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()
LazyColumn(
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)
LazyRow(
horizontalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier.height(200.dp)
) {
items(category.movieList) { movie ->
MovieCard(
movie,
onClick = {
onMovieSelected(it)
}
)
}
}
}
}
}
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의 기본사항을 살펴보았습니다.
- LazyColumn과 LazyLow를 결합하여 콘텐츠 목록을 표시하도록 화면을 구현하는 방법
- 콘텐츠 세부정보를 표시하는 기본 화면 구현
- 두 화면 간의 화면 전환을 추가하는 방법