사용자 확장 가능 콘텐츠 지원

손가락을 벌려 확대하는 동작을 구현하여 앱에서 확장 가능한 콘텐츠를 지원합니다. 이는 접근성을 개선하는 표준적이고 플랫폼 일관적인 방법으로, 사용자가 필요에 맞게 텍스트와 UI 요소의 크기를 직관적으로 조정할 수 있습니다. 앱은 세밀한 제어와 문맥 동작을 통해 맞춤 확장 동작을 정의할 수 있습니다. 이를 통해 사용자는 화면 확대와 같은 시스템 수준 기능보다 더 빠르게 발견할 수 있는 환경을 제공합니다.

확장 전략 선택

이 가이드에서 다루는 전략을 사용하면 UI가 화면 너비에 맞게 리플로우되고 재구성됩니다. 이렇게 하면 긴 텍스트 줄을 읽는 데 필요한 가로 패닝과 번거로운 '지그재그' 동작이 필요하지 않으므로 접근성이 크게 향상됩니다.

추가 자료: 연구에 따르면 저시력 사용자의 경우 콘텐츠를 리플로우하는 것이 2차원 패닝이 필요한 인터페이스보다 훨씬 더 읽기 쉽고 탐색하기 쉽습니다. 자세한 내용은 휴대기기에서 화면 이동 및 스캔과 자동 조정 가능 콘텐츠 비교를 참고하세요.

모든 요소 또는 텍스트 요소만 확장

다음 표는 각 확장 전략의 시각적 효과를 보여줍니다.

전략 밀도 조정 글꼴 크기 조정

동작

모든 항목을 비례적으로 조정합니다. 콘텐츠가 컨테이너에 맞게 재배치되므로 사용자가 모든 콘텐츠를 보기 위해 가로로 화면을 이동할 필요가 없습니다.

텍스트 요소에만 영향을 미칩니다. 전체 레이아웃과 텍스트가 아닌 구성요소는 동일한 크기로 유지됩니다.

확장되는 대상

모든 시각적 요소: 텍스트, 구성요소 (버튼, 아이콘), 이미지, 레이아웃 간격 (패딩, 여백)

텍스트만 표시

시연

추천

시각적 차이를 확인했으므로 다음 표를 통해 장단점을 비교하고 콘텐츠에 가장 적합한 전략을 선택할 수 있습니다.

UI 유형

추천 전략

추론

읽기 중심 레이아웃

예: 뉴스 기사, 메시지 앱

밀도 또는 글꼴 크기 조정

인라인 이미지를 포함한 전체 콘텐츠 영역의 크기를 조절하려면 밀도 조절을 사용하는 것이 좋습니다.

글꼴 크기 조절은 텍스트 크기만 조절해야 하는 경우에 간단한 대안입니다.

시각적으로 구조화된 레이아웃

예: 앱 스토어, 소셜 미디어 피드

밀도 조정

캐러셀 또는 그리드에서 이미지와 텍스트 간의 시각적 관계를 유지합니다. 리플로우 특성은 중첩된 스크롤 요소와 충돌할 수 있는 가로 패닝을 방지합니다.

Jetpack Compose에서 확장 동작 감지

사용자 확장 가능 콘텐츠를 지원하려면 먼저 멀티 터치 동작을 감지해야 합니다. Jetpack Compose에서는 Modifier.transformable를 사용하여 이 작업을 할 수 있습니다.

transformable 수정자는 마지막 동작 이벤트 이후의 zoomChange 델타를 제공하는 상위 수준 API입니다. 이렇게 하면 상태 업데이트 로직이 직접 누적 (예: scale *= zoomChange)으로 간소화되므로 이 가이드에서 다루는 적응형 확장 전략에 적합합니다.

구현 예

다음 예는 밀도 조정 및 글꼴 크기 조절 전략을 구현하는 방법을 보여줍니다.

밀도 조정

이 접근 방식은 UI 영역의 기본 density를 확장합니다. 따라서 패딩, 간격, 구성요소 크기를 비롯한 모든 레이아웃 기반 측정값이 화면 크기나 해상도가 변경된 것처럼 조정됩니다. 텍스트 크기도 밀도에 따라 달라지므로 비례적으로 확장됩니다. 이 전략은 특정 영역 내의 모든 요소를 균일하게 확대하여 UI의 전반적인 시각적 리듬과 비율을 유지하려는 경우에 효과적입니다.

private class DensityScalingState(
    // Note: For accessibility, typical min/max values are ~0.75x and ~3.5x.
    private val minScale: Float = 0.75f,
    private val maxScale: Float = 3.5f,
    private val currentDensity: Density
) {
    val transformableState = TransformableState { zoomChange, _, _ ->
        scaleFactor.floatValue =
            (scaleFactor.floatValue * zoomChange).coerceIn(minScale, maxScale)
    }
    val scaleFactor = mutableFloatStateOf(1f)
    fun scaledDensity(): Density {
        return Density(
            currentDensity.density * scaleFactor.floatValue,
            currentDensity.fontScale
        )
    }
}

@Composable
fun DensityScalingSample() {
    val currentDensity = LocalDensity.current
    val scaleState =
        remember(currentDensity) { DensityScalingState(currentDensity = currentDensity) }

    Box(
        modifier = Modifier
            .fillMaxSize()
            .transformable(state = scaleState.transformableState),
        contentAlignment = Alignment.TopCenter
    ) {
        CompositionLocalProvider(
            LocalDensity provides scaleState.scaledDensity()
        ) {
            DemoCard()
        }
    }
}

글꼴 크기 조정

이 전략은 fontScale 요소만 수정하는 더 타겟팅된 전략입니다. 그 결과 텍스트 요소만 커지거나 작아지고 컨테이너, 패딩, 아이콘과 같은 다른 모든 레이아웃 구성요소는 고정된 크기로 유지됩니다. 이 전략은 읽기 중심 앱에서 텍스트 가독성을 개선하는 데 적합합니다.

class FontScaleState(
    // Note: For accessibility, typical min/max values are ~0.75x and ~3.5x.
    private val minScale: Float = 0.75f,
    private val maxScale: Float = 3.5f,
    private val currentDensity: Density
) {
    val transformableState = TransformableState { zoomChange, _, _ ->
        scaleFactor.floatValue =
            (scaleFactor.floatValue * zoomChange).coerceIn(minScale, maxScale)
    }
    val scaleFactor = mutableFloatStateOf(1f)
    fun scaledFont(): Density {
        return Density(
            currentDensity.density,
            currentDensity.fontScale * scaleFactor.floatValue
        )
    }
}

@Composable
fun FontScalingSample() {
    val currentDensity = LocalDensity.current
    val scaleState = remember { FontScaleState(currentDensity = currentDensity) }

    Box(
        modifier = Modifier
            .fillMaxSize()
            .transformable(state = scaleState.transformableState),
        contentAlignment = Alignment.TopCenter
    ) {
        CompositionLocalProvider(
            LocalDensity provides scaleState.scaledFont()
        ) {
            DemoCard()
        }
    }
}

공유 데모 UI

이는 앞의 두 예시에서 서로 다른 크기 조절 동작을 강조하기 위해 사용하는 공유 DemoCard 컴포저블입니다.

@Composable
private fun DemoCard() {
    Card(
        modifier = Modifier
            .width(360.dp)
            .padding(16.dp),
        shape = RoundedCornerShape(12.dp)
    ) {
        Column(
            modifier = Modifier.padding(16.dp),
            verticalArrangement = Arrangement.spacedBy(16.dp)
        ) {
            Text("Demo Card", style = MaterialTheme.typography.headlineMedium)
            var isChecked by remember { mutableStateOf(true) }
            Row(verticalAlignment = Alignment.CenterVertically) {
                Text("Demo Switch", Modifier.weight(1f), style = MaterialTheme.typography.bodyLarge)
                Switch(checked = isChecked, onCheckedChange = { isChecked = it })
            }
            Row(verticalAlignment = Alignment.CenterVertically) {
                Icon(Icons.Filled.Person, "Icon", Modifier.size(32.dp))
                Spacer(Modifier.width(8.dp))
                Text("Demo Icon", style = MaterialTheme.typography.bodyLarge)
            }
            Row(
                Modifier.fillMaxWidth(),
                horizontalArrangement = Arrangement.SpaceBetween
            ) {
                Box(
                    Modifier
                        .width(100.dp)
                        .weight(1f)
                        .height(80.dp)
                        .background(Color.Blue)
                )
                Box(
                    Modifier
                        .width(100.dp)
                        .weight(1f)
                        .height(80.dp)
                        .background(Color.Red)
                )
            }
            Text(
                "Demo Text: Lorem ipsum dolor sit amet, consectetur adipiscing elit," +
                    " sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
                style = MaterialTheme.typography.bodyMedium,
                textAlign = TextAlign.Justify
            )
        }
    }
}

도움말 및 고려사항

더욱 세련되고 접근성이 우수한 환경을 만들려면 다음 권장사항을 고려하세요.

  • 동작이 아닌 배율 컨트롤 제공 고려: 일부 사용자는 동작을 사용하는 데 어려움을 겪을 수 있습니다. 이러한 사용자를 지원하려면 동작에 의존하지 않고 배율을 조정하거나 재설정하는 다른 방법을 제공하는 것이 좋습니다.
  • 모든 크기에 맞게 빌드: 인앱 크기 조정과 시스템 전체 글꼴 또는 디스플레이 설정 모두에 대해 UI를 테스트합니다. 앱의 레이아웃이 콘텐츠를 손상시키거나, 겹치거나, 숨겨진 콘텐츠가 되지 않고 올바르게 조정되는지 확인합니다. 적응형 레이아웃을 빌드하는 방법을 자세히 알아보세요.