Wear OS용 Compose로 목록 사용


목록을 사용하면 사용자는 Wear OS 기기에서 여러 선택 항목 중 하나를 고를 수 있습니다.

많은 Wear OS 기기가 원형 화면을 사용하므로 화면의 상단과 하단 근처에 표시되는 목록 항목을 보기가 어렵습니다. 이러한 이유로 Wear OS용 Compose에는 크기 조정 및 모핑 애니메이션을 지원하는 TransformingLazyColumn이라는 LazyColumn 클래스의 한 가지 버전이 포함되어 있습니다. 항목이 가장자리로 이동하면 항목이 더 작아지고 페이드아웃됩니다.

권장되는 크기 조정 및 스크롤 효과를 적용하려면 다음 단계를 따르세요.

  1. Modifier.transformedHeight를 사용하여 항목이 화면을 스크롤할 때 Compose에서 높이 변경사항을 계산하도록 허용합니다.
  2. transformation = SurfaceTransformation(transformationSpec)을 사용하여 항목 콘텐츠 축소 등 시각적 효과를 적용합니다.
  3. ` transformation`을 매개변수로 사용하지 않는 구성요소(예: `Text`)에 맞춤 `TransformationSpec`을 사용합니다.

다음 애니메이션은 목록 요소가 화면 상단과 하단에 접근할 때 크기가 조정되고 모양이 변경되는 방식을 보여줍니다.

다음 코드 스니펫은 TransformingLazyColumn 레이아웃을 사용하여 다양한 Wear OS 화면 크기에서 멋지게 보이는 콘텐츠를 만드는 방법을 보여줍니다.

이 스니펫은 목록 항목에 설정하여 목록의 상단과 하단에 올바른 패딩을 적용해야 하는 minimumVerticalContentPadding 수정자의 사용법도 보여줍니다.

스크롤 표시기를 표시하려면 ScreenScaffoldTransformingLazyColumn 간에 columnState를 공유합니다.

val columnState = rememberTransformingLazyColumnState()
val transformationSpec = rememberTransformationSpec()
ScreenScaffold(
    scrollState = columnState
) { contentPadding ->
    TransformingLazyColumn(
        state = columnState,
        contentPadding = contentPadding
    ) {
        item {
            ListHeader(
                modifier = Modifier
                    .fillMaxWidth()
                    .transformedHeight(this, transformationSpec)
                    .minimumVerticalContentPadding(ListHeaderDefaults.minimumTopListContentPadding),
                transformation = SurfaceTransformation(transformationSpec)
            ) {
                Text(text = "Header")
            }
        }
        // ... other items
        item {
            Button(
                modifier = Modifier
                    .fillMaxWidth()
                    .transformedHeight(this, transformationSpec)
                    .minimumVerticalContentPadding(ButtonDefaults.minimumVerticalListContentPadding),
                transformation = SurfaceTransformation(transformationSpec),
                onClick = { /* ... */ },
                icon = {
                    Icon(
                        imageVector = Icons.Default.Build,
                        contentDescription = "build",
                    )
                },
            ) {
                Text(
                    text = "Build",
                    maxLines = 1,
                    overflow = TextOverflow.Ellipsis,
                )
            }
        }
    }
}

맞추기 및 플링 효과 추가

맞추기를 사용하면 사용자가 스크롤 또는 플링 동작을 완료할 때 목록이 특정 지점(일반적으로 화면 중앙)에 정확하게 배치된 항목으로 정렬됩니다. 항목이 중앙에서 멀어질수록 크기가 조정되고 모핑되는 원형 화면에서 맞추기는 가장 관련성 높은 항목이 최적의 시청 영역에서 완전히 표시되고 읽을 수 있도록 하는 데 특히 유용합니다.

맞추기 및 플링 동작을 추가하려면 flingBehavior 매개변수를 TransformingLazyColumnDefaults.snapFlingBehavior(columnState)로 설정합니다. 실제 크라운 또는 베젤을 사용할 때 일관된 환경을 제공하려면 rotaryScrollableBehaviorRotaryScrollableDefaults.snapBehavior(columnState)를 사용하여 일치하도록 설정합니다.

val columnState = rememberTransformingLazyColumnState()
ScreenScaffold(scrollState = columnState) { contentPadding ->
    TransformingLazyColumn(
        state = columnState,
        flingBehavior = TransformingLazyColumnDefaults.snapFlingBehavior(columnState),
        rotaryScrollableBehavior = RotaryScrollableDefaults.snapBehavior(columnState)
    ) {
        // ...
        // ...
    }
}

레이아웃 반전

기본적으로 스크롤 가능한 목록은 상단 가장자리에 고정됩니다. 사용자가 표준 목록의 하단으로 스크롤하고 새 항목이 끝에 추가되면 목록은 현재 항목에 대한 사용자 뷰를 유지합니다. 예를 들어 사용자가 화면 하단에서 항목 10을 보고 있고 항목 11이 추가되면 뷰는 항목 10에 계속 집중되고 항목 11은 현재 뷰 아래의 화면 밖에 표시됩니다.

메시지 애플리케이션 또는 실시간 로그와 같은 사용 사례에서는 이 동작이 일반적으로 바람직하지 않습니다. 새 항목이 도착하면 사용자는 이미 목록 하단에 있는 경우 최신 콘텐츠를 즉시 보고 싶어 합니다. 한 번에 많은 항목이 도착하면 목록은 하단에 가장 최신 항목을 표시하도록 건너뛰어야 합니다. 즉, 사용자가 다시 위로 스크롤하지 않는 한 일부 중간 항목이 전혀 표시되지 않을 수 있습니다.

이러한 사용 사례를 지원하기 위해 TransformingLazyColumn을 사용하면 reverseLayout = true를 설정하여 레이아웃을 반전할 수 있습니다. 이렇게 하면 목록의 앵커가 상단 가장자리에서 하단 가장자리로 변경됩니다.

편의를 위해 reverseLayout = true를 설정하면 항목의 시각적 순서와 스크롤 동작의 방향도 반전됩니다.

  • 항목은 하단에서 상단으로 구성됩니다. 즉, 색인 0이 화면 하단에 표시됩니다.
  • 위로 스크롤하면 색인이 더 높은 항목이 표시됩니다.

레이아웃 반전과 함께 맞추기 및 플링 동작을 추가하려면 다음 스니펫과 같이 flingBehaviorrotaryScrollableBehavior를 결합하면 됩니다.

val columnState = rememberTransformingLazyColumnState()
val transformationSpec = rememberTransformationSpec()
ScreenScaffold(scrollState = columnState) { contentPadding ->
    TransformingLazyColumn(
        state = columnState,
        contentPadding = contentPadding,
        reverseLayout = true,
        modifier = Modifier.fillMaxWidth()
    ) {
        items(10) { index ->
            Button(
                label = {
                    Text(
                        text = "Item ${index + 1}"
                    )
                },
                onClick = {},
                modifier = Modifier
                    .fillMaxWidth()
                    .transformedHeight(this, transformationSpec)
                    .minimumVerticalContentPadding(ButtonDefaults.minimumVerticalListContentPadding),
                transformation = SurfaceTransformation(transformationSpec)
            )
        }
        item {
            // With reverseLayout = true, the last item declared appears at the top.
            ListHeader(
                modifier = Modifier
                    .fillMaxWidth()
                    .transformedHeight(this, transformationSpec)
                    .minimumVerticalContentPadding(ListHeaderDefaults.minimumTopListContentPadding),
                transformation = SurfaceTransformation(transformationSpec)
            ) {
                Text("Header")
            }
        }
    }
}

다음 이미지는 일반 목록과 반전된 목록의 차이점을 보여줍니다.

일반 레이아웃이 적용된 TransformingLazyColumn으로, Item 1이 상단에 표시되고 항목이 오름차순으로 표시됩니다.
그림 1. 콘텐츠가 위에서 아래로 채워지는 표준 목록 레이아웃
역방향 레이아웃이 적용된 TransformingLazyColumn으로, Item 1이 하단에 표시되고 항목이 상단으로 갈수록 내림차순으로 표시됩니다.
그림 2. 콘텐츠가 아래에서 위로 채워지는 반전된 목록 레이아웃

목록의 가장자리 버튼

Material 3의 경우 목록 하단에 가장자리에 붙어 있는 버튼인 EdgeButton을 추가할 수 있습니다. 하지만 TransformingLazyColumn 내에 항목으로 추가하지 말고 ScreenScaffoldedgeButton 슬롯을 사용해야 합니다.

edgeButton 슬롯을 사용하면 버튼이 화면 하단에 올바르게 배치되고 목록이 스크롤될 때 적절하게 동작합니다.

다음 코드 스니펫은 EdgeButton을 구현하는 방법을 보여줍니다.

val columnState = rememberTransformingLazyColumnState()
ScreenScaffold(
    scrollState = columnState,
    edgeButton = {
        EdgeButton(
            onClick = { /* TODO */ },
            modifier = Modifier.scrollable(
                columnState,
                orientation = Orientation.Vertical,
                reverseDirection = true,
                // Apply overscroll to the EdgeButton for proper scrolling behavior.
                overscrollEffect = rememberOverscrollEffect(),
            )
        ) {
            Text("More")
        }
    }
) { contentPadding ->
    TransformingLazyColumn(
        contentPadding = contentPadding,
        state = columnState,
    ) {
        // ...
        // ...
    }
}

목록의 SwipeToReveal

SwipeToReveal 구성요소를 사용하면 스와이프하여 Card 또는 Chip과 같은 목록 항목의 작업에 액세스할 수 있습니다. 스와이프하면 일반적으로 측면에서 하나 또는 두 개의 작업 버튼 (예: '삭제' 또는 '더보기')이 표시됩니다.

TransformingLazyColumn 내에서 SwipeToReveal을 사용하는 경우 다음 가이드라인을 따르세요.

  • 스크롤 시 재설정: 사용자가 목록을 스크롤할 때 스와이프로 열린 항목을 덮인 상태로 재설정합니다.
  • 일관된 높이: 일관된 모양을 보장하려면 작업 버튼 높이를 내부 스와이프 항목 (Button 또는 Card)과 일치하도록 설정합니다.
  • 컨테이너 변환: transformedHeight 수정자와 transformationSpecSwipeToReveal 구성요소 자체에 적용합니다.
  • 이중 변환하지 않음: 내부 스와이프 항목 (`SwipeToReveal`SwipeToReveal 컨테이너 내부의 카드 또는 버튼)에 transformedHeight 또는 transformation 수정자를 적용하지 마세요.