핀치 투 줌 동작을 구현하여 앱에서 확장 가능한 콘텐츠를 지원하세요. 이는 접근성을 개선하는 표준적이고 플랫폼 일관적인 방법으로, 사용자가 필요에 맞게 텍스트와 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 ) } }
글꼴 크기 조정
이 전략은 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 ) } }
공유 데모 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를 테스트합니다. 앱의 레이아웃이 콘텐츠를 깨거나, 겹치거나, 숨기지 않고 올바르게 적응하는지 확인합니다. 적응형 레이아웃을 빌드하는 방법을 자세히 알아보세요.