TV용 스크롤 가능한 레이아웃 만들기

TV 앱의 경우 탐색 환경은 효율적인 포커스 기반 탐색에 의존합니다. 표준 Compose Foundation 지연 레이아웃을 사용하면 포커스 기반 스크롤을 자동으로 처리하여 활성 항목을 계속 표시하는 고성능 세로 및 가로 목록을 만들 수 있습니다.

TV에 최적화된 기본 스크롤 동작

Compose Foundation 1.7.0부터 표준 지연 레이아웃 (LazyRow, LazyColumn 등)에는 포커스 위치 지정 기능에 대한 기본 지원이 포함됩니다. 포커스된 항목을 계속 표시하고 사용자에게 직관적으로 배치하는 데 도움이 되므로 TV 앱의 카탈로그를 빌드하는 데 권장되는 방법입니다.

기본 스크롤 가능한 목록을 구현하려면 표준 지연 구성요소를 사용하세요. 이러한 구성요소는 D패드 탐색을 자동으로 처리하고 포커스된 항목을 표시합니다.

import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items

@Composable
fun MovieCatalog(movies: List<Movie>) {
    LazyRow {
        items(movies) { movie ->
            MovieCard(
                movie = movie,
                onClick = { /* Handle click */ }
            )
        }
    }
}

BringIntoViewSpec으로 스크롤 동작 맞춤설정

디자인에 특정 '피벗' 포인트가 필요한 경우 (예: 포커스된 항목을 왼쪽 가장자리에서 정확히 30% 떨어진 곳에 유지) BringIntoViewSpec을 사용하여 스크롤 동작을 맞춤설정할 수 있습니다. 이렇게 하면 이전 pivotOffsets 기능이 대체되어 뷰포트가 포커스된 항목을 수용하기 위해 스크롤해야 하는 방식을 정확하게 정의할 수 있습니다.

1. 맞춤 BringIntoViewSpec 정의

다음 도우미 컴포저블을 사용하면 상위 및 하위 분수를 기반으로 '피벗'을 정의할 수 있습니다. parentFraction은 항목이 컨테이너에서 배치되어야 하는 위치를 결정하고 childFraction은 항목의 어느 부분이 해당 포인트에 정렬되는지 결정합니다.

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun PositionFocusedItemInLazyLayout(
    parentFraction: Float = 0.3f,
    childFraction: Float = 0f,
    content: @Composable () -> Unit,
) {
    val bringIntoViewSpec = remember(parentFraction, childFraction) {
        object : BringIntoViewSpec {
            override fun calculateScrollDistance(
                offset: Float,       // Item's initial position
                size: Float,         // Item's size
                containerSize: Float // Container's size
            ): Float {
                // Calculate the offset position of the item's leading edge.
                val initialTargetForLeadingEdge =
                    parentFraction * containerSize - (childFraction * size)
                // If the item fits in the container, and scrolling would cause
                // its trailing edge to be clipped, adjust targetForLeadingEdge
                // to prevent over-scrolling near the end of list.
                val targetForLeadingEdge = if (size <= containerSize &&
                    (containerSize - initialTargetForLeadingEdge) < size) {
                    // If clipped, align the item's trailing edge with the
                    // container's trailing edge.
                    containerSize - size
                } else {
                    initialTargetForLeadingEdge
                }
                // Return scroll distance relative to initial item position.
                return offset - targetForLeadingEdge
            }
        }
    }

    // Apply the spec to all scrollables in the hierarchy
    CompositionLocalProvider(
        LocalBringIntoViewSpec provides bringIntoViewSpec,
        content = content,
    )
}

2. 맞춤 사양 적용

도우미로 레이아웃을 래핑하여 위치 지정을 적용합니다. 이는 카탈로그의 여러 행에서 '일관된 포커스 선'을 만드는 데 유용합니다.

PositionFocusedItemInLazyLayout(
    parentFraction = 0.3f, // Pivot 30% from the edge
    childFraction = 0.5f   // Center of the item aligns with the pivot
) {
    LazyColumn {
        items(sectionList) { section ->
            // This row and its items will respect the 30% pivot
            LazyRow { ... }
        }
    }
}

3. 특정 중첩 레이아웃 선택 해제

맞춤 피벗 대신 표준 스크롤 동작을 사용해야 하는 특정 중첩 레이아웃이 있는 경우 DefaultBringIntoViewSpec을 제공합니다.

private val DefaultBringIntoViewSpec = object : BringIntoViewSpec {}

PositionFocusedItemInLazyLayout {
    LazyColumn {
        item {
            // This row will ignore the custom pivot and use default behavior
            CompositionLocalProvider(LocalBringIntoViewSpec provides DefaultBringIntoViewSpec) {
                LazyRow { ... }
            }
        }
    }
}

실제로 빈 BringIntoViewSpec을 전달하면 프레임워크의 기본 동작이 우선 적용됩니다.

TV Foundation에서 Compose Foundation으로 이전

androidx.tv.foundation의 TV 관련 지연 레이아웃은 표준 Compose Foundation 레이아웃을 위해 지원 중단됩니다.

종속 항목 업데이트

build.gradle이 다음 항목에 버전 1.7.0 이상을 사용하는지 확인합니다.

  • androidx.compose.foundation
  • androidx.compose.runtime

구성요소 매핑

이전하려면 가져오기를 업데이트하고 구성요소에서 Tv 접두사를 삭제합니다.

지원 중단된 TV 구성요소 Compose Foundation 대체
TvLazyRow LazyRow
TvLazyColumn LazyColumn
TvLazyHorizontalGrid LazyHorizontalGrid
TvLazyVerticalGrid LazyVerticalGrid
pivotOffsets BringIntoViewSpec (LocalBringIntoViewSpec을 통해)