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 스튜디오
2. 설정
이 Codelab의 테마 설정과 기본 설정이 포함된 코드를 가져오려면 다음 중 하나를 실행합니다.
- 이 GitHub 저장소에서 코드를 클론합니다.
$ 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
속성이 있습니다.
카테고리 이름을 표시하려면 다음 단계를 따르세요.
- Android 스튜디오에서 시작 코드의
CatalogBrowser.kt
파일을 열고TvLazyColumn
Composable
함수를CatalogBrowser
Composable
함수에 추가합니다. catalogBrowserViewModel.categoryList.collectAsState()
메서드를 호출하여 흐름을State
객체로 수집합니다.categoryList
를 이전 단계에서 만든State
객체의 위임 속성으로 선언합니다.categoryList
변수를 매개변수로 사용하여items
함수를 호출합니다.- 카테고리 이름을 람다 인수로 전달되는 매개변수로 사용하여
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
객체의 목록입니다.
각 카테고리의 콘텐츠 목록을 표시하려면 다음 단계를 따르세요.
TvLazyRow
Composable
함수를 추가하고 이 함수에 람다를 전달합니다.- 람다에서
items
함수를 호출하며 이때category
.movieList
속성 값을 사용합니다. 그런 다음, 함수에 람다를 전달합니다. 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)
}
}
}
}
}
선택사항: 레이아웃 조정
- 카테고리 간의 간격을 설정하려면
verticalArrangement
매개변수를 사용하여Arrangement
객체를TvLazyColumn
Composable
함수에 전달합니다.Arrangement
객체는Arrangement#spacedBy
메서드를 호출하여 생성됩니다. - 영화 카드 간의 간격을 설정하려면
horizontalArrangement
매개변수를 사용하여Arrangement
객체를TvLazyRow
Composable
함수에 전달합니다. - 열에 들여쓰기를 설정하려면
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
. 영화에 대한 간단한 요약
이 메타데이터를 세부정보 화면에 표시하려면 다음 단계를 따르세요.
Column
Composable
함수를 추가한 후에Modifier.padding
메서드로 만든Modifier
객체를 사용하여 열 주위에 세로 간격 32dp 및 가로 간격 48dp를 설정합니다.Text
Composable
함수를 추가하여 영화 제목을 표시합니다.Text
Composable
함수를 추가하여 스튜디오 이름을 표시합니다.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
속성이 있습니다.
특정 영화의 배경 이미지를 표시하려면 다음 단계를 따르세요.
Box
Composable
함수를Details
Composable
함수를 통해 전달된modifier
객체가 있는Column
Composable
함수의 래퍼로 추가합니다.Box
Composable
함수에서modifier
객체의fillMaxSize
메서드를 호출하여Details
Composable
함수에 할당 가능한 최대 크기를Box
Composable
함수가 채우도록 합니다.- 다음 매개변수가 있는
AsyncImage
Composable
함수를Box
Composable
함수에 추가합니다.
- 지정된
Movie
객체의backgroundImageUrl
속성 값을model
매개변수로 설정합니다. null
을contentDescription
매개변수에 전달합니다.
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
객체를 참조하려면 다음 단계를 따르세요.
MaterialTheme.typography.headlineLarge
속성을 영화 제목의 텍스트 스타일로 설정합니다.MaterialTheme.typography.headlineMedium
속성을 다른 두Text
Composable
함수의 텍스트 스타일로 설정합니다.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
메서드가 호출됩니다.
com.example.tvcomposeintroduction.ui.screens.CatalogBrowser
파일을 엽니다.onClick
매개변수를 사용하여 람다 함수를MovieCard
Composable
함수에 전달합니다.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 구성요소로, 지정된 시간이 지나면 슬라이드를 자동으로 업데이트합니다. 일반적으로 추천 콘텐츠를 강조하는 데 사용됩니다.
추천 콘텐츠 목록에서 영화를 강조표시하기 위해 카탈로그 브라우저 화면에 캐러셀을 추가하려면 다음 단계를 따르세요.
com.example.tvcomposeintroduction.ui.screens.CatalogBrowser
파일을 엽니다.item
함수를 호출하여TvLazyColumn
Composable
함수에 항목을 추가합니다.item
함수에 전달된 람다에서 위임 속성으로featuredMovieList
를 선언한 후에State
객체를 위임되도록 설정합니다. 이는catalogBrowserViewModel.featuredMovieList
속성에서 수집됩니다.item
함수 내에서Carousel
Composable
함수를 호출한 후에 다음 매개변수를 전달합니다.
slideCount
매개변수를 통한featuredMovieList
변수의 크기Modifier.fillMaxWidth
메서드 및Modifier.height
메서드를 사용하여 캐러셀 크기를 지정하는Modifier
객체.Carousel
Composable
함수는Modifier.height
메서드에376.dp
값을 전달하여 높이 376dp를 사용합니다.- 표시된 캐러셀 항목의 색인을 나타내는 정수 값으로 호출된 람다
featuredMovieList
변수와 지정된 색인 값에서Movie
객체를 검색합니다.Carousel
Composable
함수에CarouselSlide
Composable
함수를 추가합니다.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
함수의 배경이 표시되는 방식을 지정할 수 있습니다.
배경 이미지를 표시하려면 다음 단계를 따르세요.
background
매개변수를 사용하여 람다를CarouselSlide
Composable
함수에 전달합니다.AsyncImage
Composable
함수를 호출하여Movie
객체와 연결된 배경 이미지를CarouselSlide
Composable
함수의 배경에 로드합니다.- 가시성을 높이기 위해
CarouselSlide
Composable
함수에서Text
Composable
함수의 위치와 텍스트 스타일을 업데이트합니다. - 레이아웃 전환을 방지하기 위해 자리표시자를
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
함수를 클릭하도록 허용할 수 있습니다.
세부정보 화면에 표시된 캐러셀 항목에서 영화의 세부정보가 사용자에게 표시되도록 하려면 다음 단계를 따르세요.
modifier
매개변수를 통해Modifier.clickable
메서드의 반환 값을CarouselSlide
Composable
함수에 전달합니다.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를 결합하여 콘텐츠 목록을 표시하도록 화면을 구현하는 방법
- 콘텐츠 세부정보를 표시하는 기본 화면 구현
- 두 화면 간의 화면 전환을 추가하는 방법