API 기본값

Material, Compose UI, Foundation API는 기본적으로 접근성 관련 관행을 많이 구현하고 제공합니다. 특정 역할과 기능을 따르는 내장 시맨틱을 포함합니다. 즉, 대부분의 접근성 지원은 추가 작업 없이 제공됩니다.

적절한 용도에 적합한 API를 사용하면 구성요소에 표준 사용 사례를 다루는 사전 정의된 접근성 동작이 함께 제공되는 경우가 많습니다. 하지만 이러한 기본값이 접근성 요구사항에 적합한지 항상 다시 한번 확인하세요. 그렇지 않은 경우 Compose는 더 구체적인 요구사항을 충족하는 방법을 제공합니다.

Compose API의 기본 접근성 시맨틱과 패턴을 이해하면 접근성을 고려하여 이를 사용할 수 있습니다. 또한 더 맞춤설정된 구성요소에서 접근성을 지원하는 데도 도움이 됩니다.

최소 터치 영역 크기

사용자가 클릭, 터치 등의 방법으로 상호작용할 수 있는 화면상의 요소는 안정적으로 상호작용할 수 있도록 충분히 커야 합니다. 이러한 요소의 크기를 조절할 때 Material Design 접근성 가이드라인을 정확히 준수하도록 최소 크기를 48dp로 설정해야 합니다.

Checkbox, RadioButton, Switch, Slider, Surface와 같은 Material 구성요소는 이 최소 크기를 내부적으로 설정합니다. 단, 구성요소가 사용자 작업을 수신할 수 있는 경우에 한합니다. 예를 들어 CheckboxonCheckedChange 매개변수가 null이 아닌 값으로 설정된 경우 체크박스에는 너비와 높이가 최소 48dp인 패딩이 포함됩니다.

@Composable
private fun CheckableCheckbox() {
    Checkbox(checked = true, onCheckedChange = {})
}

너비와 높이가 48dp인 기본 패딩이 있는 체크박스
그림 1. 기본 패딩이 있는 체크박스

onCheckedChange 매개변수가 null로 설정된 경우 구성요소와 직접 상호작용할 수 없으므로 패딩이 포함되지 않습니다.

@Composable
private fun NonClickableCheckbox() {
    Checkbox(checked = true, onCheckedChange = null)
}

패딩이 없는 체크박스
그림 2. 패딩이 없는 체크박스입니다.

Switch, RadioButton, Checkbox와 같은 선택 설정을 구현할 경우 일반적으로 컴포저블에 관한 클릭 콜백을 null로 설정하고 toggleable 또는 selectable 수정자를 상위 컴포저블에 추가하여 클릭 가능한 동작을 상위 컨테이너로 올립니다.

@Composable
private fun CheckableRow() {
    MaterialTheme {
        var checked by remember { mutableStateOf(false) }
        Row(
            Modifier
                .toggleable(
                    value = checked,
                    role = Role.Checkbox,
                    onValueChange = { checked = !checked }
                )
                .padding(16.dp)
                .fillMaxWidth()
        ) {
            Text("Option", Modifier.weight(1f))
            Checkbox(checked = checked, onCheckedChange = null)
        }
    }
}

'옵션' 텍스트 옆에 있는 체크박스가 선택 및 선택 해제됩니다.
그림 3. 클릭 가능한 동작이 있는 체크박스입니다.

클릭 가능한 컴포저블의 크기가 터치 영역 최소 크기보다 작은 경우 Compose는 여전히 터치 영역 크기를 늘립니다. 이 작업은 컴포저블의 경계 밖으로 터치 영역 크기를 확장하여 이뤄집니다.

다음 예에는 클릭 가능한 아주 작은 Box가 포함되어 있습니다. 터치 타겟 영역은 Box의 경계 밖으로 자동으로 확장되므로 Box 옆을 탭하면 여전히 클릭 이벤트가 트리거됩니다.

@Composable
private fun SmallBox() {
    var clicked by remember { mutableStateOf(false) }
    Box(
        Modifier
            .size(100.dp)
            .background(if (clicked) Color.DarkGray else Color.LightGray)
    ) {
        Box(
            Modifier
                .align(Alignment.Center)
                .clickable { clicked = !clicked }
                .background(Color.Black)
                .size(1.dp)
        )
    }
}

클릭 가능한 매우 작은 상자로, 상자 옆을 탭하면 더 큰 터치 타겟으로 확장됩니다.
그림 4. 더 큰 터치 타겟으로 확장되는 매우 작은 클릭 가능한 상자

서로 다른 컴포저블의 터치 영역이 서로 겹치지 않도록 항상 컴포저블에 충분히 큰 최소 크기를 사용하세요. 이 예에서는 sizeIn 수정자를 사용하여 내부 상자의 최소 크기를 설정하는 것을 의미합니다.

@Composable
private fun LargeBox() {
    var clicked by remember { mutableStateOf(false) }
    Box(
        Modifier
            .size(100.dp)
            .background(if (clicked) Color.DarkGray else Color.LightGray)
    ) {
        Box(
            Modifier
                .align(Alignment.Center)
                .clickable { clicked = !clicked }
                .background(Color.Black)
                .sizeIn(minWidth = 48.dp, minHeight = 48.dp)
        )
    }
}

이전 예의 아주 작은 상자 크기가 더 큰 터치 타겟을 만들 수 있도록 늘어납니다.
그림 5. 더 큰 상자 터치 타겟

그래픽 요소

Image 또는 Icon 컴포저블을 정의하는 경우 Android 프레임워크에서 앱에 표시되는 내용을 자동으로 파악할 수 없습니다. 그래픽 요소의 텍스트 설명을 전달해야 합니다.

사용자가 현재 페이지를 친구와 공유할 수 있는 화면이 있다고 가정해 보겠습니다. 이 화면에는 클릭 가능한 공유 아이콘이 포함되어 있습니다.

클릭 가능한 아이콘 4개가 표시줄에 나열되어 있으며 '공유' 아이콘이 강조 표시되어 있습니다.
그림 6. 클릭 가능한 아이콘 행, '공유' 아이콘이 선택되어 있습니다.

Android 프레임워크는 아이콘만 가지고는 시각 장애를 가진 사용자에게 아이콘에 대해 설명할 수 없습니다. Android 프레임워크에는 아이콘의 추가 텍스트 설명이 필요합니다.

contentDescription 매개변수는 그래픽 요소를 설명합니다. 사용자에게 표시되므로 현지화된 문자열을 사용합니다.

@Composable
private fun ShareButton(onClick: () -> Unit) {
    IconButton(onClick = onClick) {
        Icon(
            imageVector = Icons.Filled.Share,
            contentDescription = stringResource(R.string.label_share)
        )
    }
}

일부 그래픽 요소는 완전히 장식용이므로 사용자에게 전달하지 않아도 됩니다. contentDescription 매개변수를 null로 설정하면 Android 프레임워크에 이 요소에 연결된 작업 또는 상태가 없음을 나타냅니다.

@Composable
private fun PostImage(post: Post, modifier: Modifier = Modifier) {
    val image = post.imageThumb ?: painterResource(R.drawable.placeholder_1_1)

    Image(
        painter = image,
        // Specify that this image has no semantic meaning
        contentDescription = null,
        modifier = modifier
            .size(40.dp, 40.dp)
            .clip(MaterialTheme.shapes.small)
    )
}

contentDescription은 주로 이미지와 같은 그래픽 요소에 사용됩니다. Button 또는 Text과 같은 Material 구성요소와 clickable 또는 toggleable과 같은 실행 가능한 동작에는 내재된 동작을 설명하는 다른 사전 정의된 시맨틱이 함께 제공되며 다른 Compose API를 통해 변경할 수 있습니다.

상호작용 요소

Material 및 Foundation Compose API는 사용자가 clickabletoggleable 수정자 API를 통해 상호작용할 수 있는 UI 요소를 만듭니다. 상호작용 가능한 구성요소는 여러 요소로 구성될 수 있으므로 clickabletoggleable는 기본적으로 하위 요소의 시맨틱을 병합하여 구성요소가 하나의 논리적 항목으로 처리되도록 합니다.

예를 들어 Material Button는 하위 아이콘과 일부 텍스트로 구성될 수 있습니다. Material Button는 기본적으로 하위 요소 시맨틱을 병합하여 접근성 서비스가 적절하게 그룹화할 수 있도록 합니다.

병합되지 않은 하위 요소 시맨틱과 병합된 하위 요소 시맨틱이 있는 버튼
그림 7. 병합되지 않은 하위 요소 시맨틱과 병합된 하위 요소 시맨틱이 있는 버튼

마찬가지로 clickable 수정자를 사용하면 컴포저블이 하위 요소의 시맨틱스를 단일 항목으로 병합하여 해당 작업 표현과 함께 접근성 서비스로 전송됩니다.

Row(
    // Uses `mergeDescendants = true` under the hood
    modifier = Modifier.clickable { openArticle() }
) {
    Icon(
        painter = painterResource(R.drawable.ic_logo),
        contentDescription = "Open",
    )
    Text("Accessibility in Compose")
}

상위 클릭 가능 항목에 특정 onClickLabel를 설정하여 접근성 서비스에 추가 정보를 제공하고 작업의 더 세련된 표현을 제공할 수도 있습니다.

Row(
    modifier = Modifier
        .clickable(onClickLabel = "Open this article") {
            openArticle()
        }
) {
    Icon(
        painter = painterResource(R.drawable.ic_logo),
        contentDescription = "Open"
    )
    Text("Accessibility in Compose")
}

TalkBack을 예로 들면 이 clickable 특수키와 클릭 라벨을 통해 TalkBack은 더 일반적인 기본 피드백인 '더블 탭하여 활성화' 대신 '더블 탭하여 이 도움말 열기'라는 작업 힌트를 제공할 수 있습니다.

이 의견은 작업 유형에 따라 달라집니다. 길게 클릭하면 TalkBack 힌트로 '두 번 탭한 후 길게 눌러'가 표시되고 그 뒤에 라벨이 표시됩니다.

Row(
    modifier = Modifier
        .combinedClickable(
            onLongClickLabel = "Bookmark this article",
            onLongClick = { addToBookmarks() },
            onClickLabel = "Open this article",
            onClick = { openArticle() },
        )
) {}

clickable 수정자에 직접 액세스할 수 없는 경우 (예: 하위 중첩 레이어의 어딘가에 설정된 경우)에도 기본값에서 알림 라벨을 변경하고 싶을 수 있습니다. 이렇게 하려면 semantics 수정자를 사용하여 공지사항을 수정하고 클릭 라벨을 설정하는 것에서 clickable 설정을 분할하여 작업 표현을 수정하세요.

@Composable
private fun ArticleList(openArticle: () -> Unit) {
    NestedArticleListItem(
        // Clickable is set separately, in a nested layer:
        onClickAction = openArticle,
        // Semantics are set here:
        modifier = Modifier.semantics {
            onClick(
                label = "Open this article",
                action = {
                    // Not needed here: openArticle()
                    true
                }
            )
        }
    )
}

클릭 작업을 두 번 전달할 필요가 없습니다. clickable 또는 Button과 같은 기존 Compose API가 이를 처리합니다. 병합 로직은 가장 바깥쪽의 수정자 라벨과 작업이 표시된 정보에 대해 수행되는지 확인합니다. 이전 예에서 NestedArticleListItemopenArticle() 클릭 작업을 clickable 시맨틱에 자동으로 전달합니다. 두 번째 시맨틱 수정자 작업에서 클릭 작업을 null로 둘 수 있습니다. 하지만 클릭 라벨은 두 번째 시맨틱 수정자 onClick(label = "Open this document")에서 가져옵니다. 첫 번째에는 없었기 때문입니다.

하위 요소 시맨틱이 상위 요소에 병합될 것으로 예상했지만 병합되지 않는 시나리오가 발생할 수 있습니다. 자세한 내용은 병합 및 지우기를 참고하세요.

맞춤 구성요소

맞춤 구성요소를 빌드할 때는 Material 라이브러리나 다른 Compose 라이브러리에서 유사한 구성요소의 구현을 검토하세요. 그런 다음 접근성 동작을 적절하게 모방하거나 수정합니다. 예를 들어 Material Checkbox를 자체 구현으로 바꾸는 경우 기존 Checkbox 구현을 살펴보면 구성요소의 접근성 속성을 처리하는 triStateToggleable 수정자를 추가해야 한다는 것을 알 수 있습니다. 또한 기초 수정자를 많이 활용하세요. 기초 수정자에는 접근성 고려 사항과 이 섹션에서 다루는 기존 Compose 관행이 포함되어 있습니다.

시맨틱 지우기 및 설정 섹션에서 맞춤 전환 버튼 구성요소의 예시를 확인할 수 있으며, API 가이드라인에서 맞춤 구성요소의 접근성을 지원하는 방법에 관한 자세한 정보를 확인할 수 있습니다.