그리드가 있는 넓은 클래식 카드 레이아웃

별 4개 평점 아이콘

TV 앱 디자인 가이드라인은 이미지와 설명 콘텐츠 블록을 나란히 표시하는 와이드 클래식 카드라는 카드 변형을 정의합니다. TV 화면용 복잡한 2차원 레이아웃을 빌드할 때 기존의 좌표 기반 위치 지정 또는 깊게 중첩된 RowColumn 컨테이너를 사용하면 코드를 읽고, 유지보수하고, 리팩터링하기 어려울 수 있습니다. 와이드 클래식 카드는 Jetpack Compose Grid API의 이상적인 사용 사례로, 의미론적이고 사용자 정의된 2D 레이아웃 영역을 사용하여 나란히 있는 요소를 깔끔하게 구성할 수 있도록 지원합니다.

이름이 지정된 레이아웃 영역이 5개(이미지, 제목, 부제목, 설명, 추가 텍스트)인 와이드 클래식 카드 구성요소의 사양
그림 1. 와이드 클래식 카드 5개의 명명된 영역: 이미지 영역 및 콘텐츠 블록의 4개 텍스트 영역

권장사항

Grid를 사용하여 카드 레이아웃을 구현합니다. Grid 컨테이너를 사용하면 실제 레이아웃 측정기준을 하위 항목 배치에서 분리하여 코드 가독성을 개선할 수 있습니다. 정확한 트랙 색인에 의존하는 대신 area 함수로 의미론적 명명된 영역을 정의하고 gridItem 수정자를 사용하여 하위 컴포저블을 배치합니다. 유연한 트랙 크기 조정을 GridTrackSize.Autominmax(0.dp, 1.fr), 정확한 트랙 간격 (gap(row = 8.dp, column = 16.dp)) 과 결합하여 복잡한 나란히 있는 카드 구성요소를 명확하고 안정적으로 구성합니다.

재료

  • Grid: 구성 람다를 기반으로 하위 컴포저블을 행과 열로 정렬하는 Jetpack Compose의 2D 컨테이너 레이아웃
  • gridItem: Grid 레이아웃 내에서 하위 UI 요소를 특정 명명된 areaId와 연결하는 Modifier 확장 프로그램
  • area: 의미론적 areaId를 특정 행 및 열 범위 또는 범위에 매핑하는 GridConfigurationScope 내의 구성 DSL 함수
  • Card: 관련 UI 요소 및 스타일을 그룹화하는 Material Design 컨테이너 컴포저블
  • GridTrackSize.MinMax: 행 또는 열 측정기준의 유연한 경계 (minmax)를 정의하는 트랙 크기 조정 속성
  • GridTrackSize.Auto: 콘텐츠에 맞게 행 또는 열 트랙의 크기를 자동으로 조정하는 트랙 크기 조정 속성 (column(GridTrackSize.Auto))
  • gap: 그리드 트랙 간의 세로 (row) 및 가로 (column) 간격을 지정하는 구성 함수

단계

영역 식별자를 정의하고, 그리드 트랙과 영역을 구성한 다음, 하위 컴포저블을 배치합니다.

1. 의미론적 영역 식별자 정의

먼저 와이드 클래식 카드 레이아웃 내 영역의 유형 안전 표현을 만듭니다. enum 클래스는 문자열 오타를 방지하고 트랙을 매핑하고 하위 항목을 배치할 때 컴파일 시간 안전을 제공합니다.

enum class CardArea {
    Image,
    Title,
    Subtitle,
    Description,
    ExtraText,
}

2. 2D 그리드 트랙 및 명명된 영역 구성

Grid 컴포저블의 config 람다 내에서 카드 구조를 구성하는 열과 행을 정의합니다. 이미지 열에 GridTrackSize.Auto를 사용하여 썸네일 너비를 깔끔하게 래핑합니다. 콘텐츠 열에 minmax(0.dp, 1.fr)를 사용하여 나머지 카드 너비에 걸쳐 유연하게 확장합니다. gap(row = 8.dp, column = 16.dp)로 트랙 간격을 지정합니다. area 함수를 사용하여 CardArea 식별자를 실제 트랙 좌표에 매핑합니다.

Grid(
    config = {
        // Define columns: left column for image, right for content
        column(GridTrackSize.Auto)
        column(minmax(0.dp, 1.fr))

        // Define row tracks for the vertical content stack
        row(GridTrackSize.Auto)
        row(GridTrackSize.Auto)
        row(GridTrackSize.Auto)
        row(GridTrackSize.Auto)

        // Map semantic identifiers to grid coordinates and spans
        area(CardArea.Image, row = 1, column = 1, rowSpan = 4)
        area(CardArea.Title, row = 1, column = 2)
        area(CardArea.Subtitle, row = 2, column = 2)
        area(CardArea.Description, row = 3, column = 2)
        area(CardArea.ExtraText, row = 4, column = 2)

        gap(row = 8.dp, column = 16.dp)
    }
) {
    // Child elements placed in Step 3
}

3. gridItem 수정자를 사용하여 하위 컴포저블 배치

트랙과 영역이 구성된 상태에서 gridItem 수정자를 사용하여 의미론적 의도에 따라 각 하위 UI 요소를 배치합니다. 하위 컴포저블은 원하는 순서로 선언할 수 있습니다. Grid 컨테이너는 각 요소를 할당된 2D 영역에 직접 자동으로 배치합니다.

@Composable
fun WideClassicCard(
    imageContent: @Composable () -> Unit,
    title: String,
    subtitle: String,
    description: String,
    extraText: String,
    modifier: Modifier = Modifier,
) {
    Card(modifier = modifier) {
        Grid(
            config = {
                // Define columns: left column for image, right for content
                column(GridTrackSize.Auto)
                column(minmax(0.dp, 1.fr))

                // Define row tracks for the vertical content stack
                row(GridTrackSize.Auto)
                row(GridTrackSize.Auto)
                row(GridTrackSize.Auto)
                row(GridTrackSize.Auto)

                // Map semantic identifiers to grid coordinates and spans
                area(CardArea.Image, row = 1, column = 1, rowSpan = 4)
                area(CardArea.Title, row = 1, column = 2)
                area(CardArea.Subtitle, row = 2, column = 2)
                area(CardArea.Description, row = 3, column = 2)
                area(CardArea.ExtraText, row = 4, column = 2)

                gap(row = 8.dp, column = 16.dp)
            },
        ) {
            Box(modifier = Modifier.gridItem(CardArea.Image)) {
                imageContent()
            }
            Text(
                text = title,
                style = MaterialTheme.typography.titleLarge,
                modifier = Modifier
                    .gridItem(CardArea.Title)
                    .padding(top = 16.dp, end = 16.dp),
            )

            Text(
                text = subtitle,
                style = MaterialTheme.typography.titleMedium,
                modifier = Modifier
                    .gridItem(CardArea.Subtitle)
                    .padding(end = 16.dp),
            )

            Text(
                text = description,
                style = MaterialTheme.typography.bodyMedium,
                modifier = Modifier
                    .gridItem(CardArea.Description)
                    .padding(end = 16.dp),
            )

            Text(
                text = extraText,
                style = MaterialTheme.typography.labelSmall,
                modifier = Modifier
                    .gridItem(CardArea.ExtraText)
                    .padding(bottom = 16.dp, end = 16.dp),
            )
        }
    }
}

결과

Grid 및 명명된 영역으로 와이드 클래식 카드를 구성하면 실제 레이아웃 트랙, 영역 좌표, 하위 컴포저블 배치 간에 깔끔한 분리를 달성할 수 있습니다. 이 구조는 나란히 있는 썸네일 및 설명 구성요소에 관한 TV 앱 디자인 가이드라인을 따르면서 레이아웃을 읽고 유지보수할 수 있도록 합니다.

추가 리소스