카탈로그 브라우저 만들기

TV에서 실행되는 미디어 앱은 사용자가 콘텐츠 서비스를 탐색하고, 콘텐츠를 선택하고, 콘텐츠 재생을 시작할 수 있게 해야 합니다. 이런 유형의 앱 콘텐츠 탐색 환경은 단순하고 직관적이며 시각적으로 즐겁고 흥미로워야 합니다.

미디어 카탈로그 브라우저는 여러 섹션으로 구성되는 경향이 있으며 각 섹션에는 미디어 콘텐츠 목록이 있습니다. 미디어 카탈로그의 섹션의 예로는 재생목록, 추천 콘텐츠, 추천 카테고리가 있습니다.

그림 1. 일반적인 카탈로그 화면 사용자가 동영상 카탈로그 데이터를 둘러볼 수 있습니다.

TV용 Compose에서 제공하는 함수를 사용하여 앱의 미디어 카탈로그에서 음악이나 동영상을 탐색하기 위한 사용자 인터페이스를 구현합니다.

카탈로그용 컴포저블 함수 만들기

디스플레이에 표시되는 모든 항목은 TV용 Compose에서 구성 가능한 함수로 구현됩니다. 먼저 미디어 카탈로그 브라우저의 컴포저블 함수를 정의합니다.

@Composable
fun CatalogBrowser(
   featuredContentList: List<Movie>,
   sectionList: List<Section>,
   modifier: Modifier = Modifier,
   onItemSelected: (Movie) -> Unit = {},
) {
// ToDo: add implementation
}

CatalogBrowser는 미디어 카탈로그 브라우저를 구현하는 구성 가능한 함수입니다. 이 함수는 다음 인수를 사용합니다.

  • 추천 콘텐츠 목록입니다.
  • 섹션 목록입니다.
  • Modifier 객체입니다.
  • 화면 전환을 트리거하는 콜백 함수입니다.

UI 요소 설정

TV용 Compose는 많은 수의 항목이나 길이를 알 수 없는 목록을 표시하는 구성요소인 지연 목록을 제공합니다. LazyColumn를 호출하여 섹션을 세로로 배치합니다. LazyColumn는 항목 콘텐츠를 정의하는 DSL을 제공하는 LazyListScope.() -> Unit 블록을 제공합니다. 다음 예에서는 각 섹션이 섹션 간에 16dp 간격이 있는 세로 목록에 배치됩니다.

@Composable
fun CatalogBrowser(
   featuredContentList: List<Movie>,
   sectionList: List<Section>,
   modifier: Modifier = Modifier,
   onItemSelected: (Movie) -> Unit = {},
) {
  LazyColumn(
    modifier = modifier.fillMaxSize(),
    verticalArrangement = Arrangement.spacedBy(16.dp)
  ) {
    items(sectionList) { section ->
      Section(section, onItemSelected = onItemSelected)
    }
  }
}

이 예에서 구성 가능한 함수 Section는 섹션을 표시하는 방법을 정의합니다. 다음 함수에서 LazyRowLazyColumn의 이 가로 버전이 제공된 DSL을 호출하여 LazyListScope.() -> Unit 블록이 있는 가로 목록을 정의하는 데 유사하게 사용되는 방식을 보여줍니다.

@Composable
fun Section(
  section: Section,
  modifier: Modifier = Modifier,
  onItemSelected: (Movie) -> Unit = {},
) {
  Text(
    text = section.title,
    style = MaterialTheme.typography.headlineSmall,
  )
  LazyRow(
     modifier = modifier,
     horizontalArrangement = Arrangement.spacedBy(8.dp)
  ) {
    items(section.movieList){ movie ->
    MovieCard(
         movie = movie,
         onClick = { onItemSelected(movie) }
       )
    }
  }
}

Section 컴포저블에서는 Text 구성요소가 사용됩니다. 머티리얼 디자인에 정의된 텍스트 및 기타 구성요소는 tv-material 라이브러리에서 제공됩니다 . MaterialTheme 객체를 참조하여 Material Design에 정의된 텍스트 스타일을 변경할 수 있습니다. 이 객체는 tv-material 라이브러리에서도 제공합니다. Card는 tv-material 라이브러리의 일부입니다. MovieCard는 다음 스니펫으로 정의된 카탈로그에서 각 영화 데이터가 렌더링되는 방식을 정의합니다.

@Composable
fun MovieCard(
   movie: Movie,
   modifier: Modifier = Modifier,
   onClick: () -> Unit = {}
) {
   Card(modifier = modifier, onClick = onClick){
    AsyncImage(
       model = movie.thumbnailUrl,
       contentDescription = movie.title,
     )
   }
}

앞에서 설명한 예에서는 모든 영화가 동일하게 표시됩니다. 두 영역은 면적이 같으며 시각적으로 차이가 없습니다. Carousel를 사용하여 일부를 강조 표시할 수 있습니다.

캐러셀은 슬라이드, 페이드 또는 이동하여 표시될 수 있는 항목 집합에 정보를 표시합니다. 이 구성요소를 사용하여 새로 출시된 영화나 TV 프로그램의 새 에피소드와 같은 추천 콘텐츠를 강조 표시할 수 있습니다.

Carousel는 최소한 캐러셀의 항목 수와 각 항목을 그리는 방법을 지정해야 합니다. 첫 번째는 itemCount로 지정할 수 있습니다. 두 번째는 람다로 전달할 수 있습니다. 표시된 항목의 색인 번호가 람다에 제공됩니다. 지정된 색인 값으로 표시되는 항목을 확인할 수 있습니다.

@Composable
function FeaturedCarousel(
  featuredContentList: List<Movie>,
  modifier: Modifier = Modifier,
) {
  Carousel(
    itemCount = featuredContentList.size,
    modifier = modifier,
  ) { index ->
    val content = featuredContentList[index]
    Box {
      AsyncImage(
        model = content.backgroundImageUrl,
        contentDescription = content.description,
        placeholder = painterResource(
          id = R.drawable.placeholder
        ),
        contentScale = ContentScale.Crop,
        modifier = Modifier.fillMaxSize()
      )
      Text(text = content.title)
    }
  }
}

Carousel는 지연 목록의 항목(예: TvLazyColumn)일 수 있습니다. 다음 스니펫은 모든 Section 컴포저블 위에 있는 FeaturedCarousel 컴포저블을 보여줍니다.

@Composable
fun CatalogBrowser(
   featuredContentList: List<Movie>,
   sectionList: List<Section>,
   modifier: Modifier = Modifier,
   onItemSelected: (Movie) -> Unit = {},
) {
  TvLazyColumn(
    modifier = modifier.fillMaxSize(),
    verticalArrangement = Arrangement.spacedBy(16.dp)
  ) {

    item {
      FeaturedCarousel(featuredContentList)
    }

    items(sectionList) { section ->
      Section(section, onItemSelected = onItemSelected)
    }
  }
}