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가 이를 대신 처리하므로 클릭 작업을 두 번 전달할 필요가 없습니다. 이는 병합 로직이 존재하는 정보에 대해 가장 바깥쪽 수정자 라벨과 작업이 실행되도록 하기 때문입니다.

이전 예에서 openArticle() 클릭 작업은 NestedArticleListItem에 의해 자동으로 clickable 시맨틱 아래로 전달되며 두 번째 시맨틱 수정자 작업에서 null로 둘 수 있습니다. 그러나 클릭 라벨은 첫 번째에 없으므로 두 번째 시맨틱 수정자 onClick(label = "Open this article")에서 가져옵니다.

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

맞춤 구성요소

맞춤 구성요소의 경우 일반적으로 Material 라이브러리 또는 다른 Compose 라이브러리에서 유사한 구성요소의 구현을 살펴보고, 적절한 경우 접근성 동작을 모방하거나 수정합니다.

예를 들어 Material Checkbox를 자체 구현으로 대체하는 경우 기존 체크박스 구현을 살펴보면 이 구성요소의 접근성 속성을 처리하는 triStateToggleable 수정자를 추가해야 한다는 것을 알 수 있습니다.

또한 Foundation 수정자를 많이 활용하세요. Foundation 수정자에는 접근성 고려 사항이 기본적으로 포함되어 있으며 이 섹션에서 다루는 기존 Compose 관행도 포함되어 있습니다.

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