Compose 접근성 개선을 위한 주요 단계

접근성 기능이 필요한 사용자가 앱을 성공적으로 사용할 수 있도록 주요 접근성 요구사항을 지원해야 합니다.

터치 영역 최소 크기 고려

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

Material 구성요소(예: Checkbox, RadioButton, Switch) SliderSurface - 이 최소 크기를 내부적으로 구성요소가 사용자 작업을 수신할 수 있는 시점입니다. 예를 들어 CheckboxonCheckedChange 매개변수가 null이 아닌 값으로 설정되면 체크박스에는 다음이 포함됩니다. 최소 48dp의 너비와 높이를 갖출 수 있습니다.

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

onCheckedChange 매개변수가 null로 설정된 경우 패딩은 가 포함될 수 있습니다. 구성요소와 직접 상호작용할 수 없기 때문입니다.

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

<ph type="x-smartling-placeholder">
</ph>
그림 1. 패딩이 없는 체크박스

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)
        }
    }
}

클릭 가능한 컴포저블의 크기가 터치 영역 최소 크기보다 작은 경우 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)
        )
    }
}

서로 다른 컴포저블의 터치 영역이 겹치지 않도록 하려면 항상 컴포저블에 충분히 큰 최소 크기를 사용해야 합니다. 이 예에서는 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)
        )
    }
}

클릭 라벨 추가

클릭 라벨을 사용하여 컴포저블의 클릭 동작에 시맨틱 의미를 추가할 수 있습니다. 클릭 라벨은 사용자가 있습니다. 접근성 서비스는 클릭 라벨을 사용하여 특정 니즈가 있는 사용자에게 액세스할 수 있습니다

clickable 수정자에 매개변수를 전달하여 클릭 라벨을 설정합니다.

@Composable
private fun ArticleListItem(openArticle: () -> Unit) {
    Row(
        Modifier.clickable(
            // R.string.action_read_article = "read article"
            onClickLabel = stringResource(R.string.action_read_article),
            onClick = openArticle
        )
    ) {
        // ..
    }
}

또는 클릭 가능한 수정자에 액세스할 수 없는 경우 시맨틱 수정자의 클릭 라벨:

@Composable
private fun LowLevelClickLabel(openArticle: () -> Boolean) {
    // R.string.action_read_article = "read article"
    val readArticleLabel = stringResource(R.string.action_read_article)
    Canvas(
        Modifier.semantics {
            onClick(label = readArticleLabel, action = openArticle)
        }
    ) {
        // ..
    }
}

시각적 요소 설명

Image 또는 Icon 컴포저블을 정의할 때는 앱이 무엇인지 쉽게 이해할 수 있도록 있습니다. 시각적 요소의 텍스트 설명을 전달해야 합니다.

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

클릭 가능한 아이콘 스트립과

아이콘만으로는 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이 필요한지 여부는 개발자가 결정합니다. 요소가 정보 전달을 돕는 정보를 전달하는지 사용자가 작업을 수행해야 합니다. 그렇지 않은 경우 설명하겠습니다.

요소 병합

TalkBack 및 스위치 제어와 같은 접근성 서비스를 사용하면 사용자가 화면의 요소 간에 포커스를 이동할 수 있습니다. 요소의 올바른 세부사항에 포커스를 맞춰야 합니다. 화면의 모든 하위 수준 컴포저블이 사용자는 화면 전체에서 이동하기 위해 많은 상호작용을 해야 합니다. 요소가 너무 공격적으로 합쳐지면 사용자는 어떤 요소가 어떤 것인지 이해하지 못할 수 있습니다. 원소는 서로 붙어 있음

컴포저블에 clickable 수정자를 적용하면 Compose는 컴포저블에 포함된 모든 요소를 자동으로 병합합니다. 이는 또한 ListItem 목록 항목 내 요소가 병합됨 하나의 요소로 볼 수 있습니다

컴포저블의 집합이 논리적 그룹을 구성할 수 있지만 이 그룹은 클릭할 수 없거나 목록 항목의 일부가 아닙니다. 여전히 접근성을 원함 하나의 요소로 볼 수 있습니다. 예를 들어 사용자의 아바타, 이름, 추가 정보가 표시됩니다.

사용자 이름이 포함된 UI 요소의 그룹. 이름이 선택되어 있습니다.

mergeDescendants를 사용하여 Compose가 이러한 요소를 병합하도록 사용 설정할 수 있습니다. semantics 수정자의 매개변수입니다. 이렇게 하면 접근성 서비스가 병합된 요소만 선택하고 하위 요소의 모든 시맨틱 속성을 선택합니다. 병합됩니다.

@Composable
private fun PostMetadata(metadata: Metadata) {
    // Merge elements below for accessibility purposes
    Row(modifier = Modifier.semantics(mergeDescendants = true) {}) {
        Image(
            imageVector = Icons.Filled.AccountCircle,
            contentDescription = null // decorative
        )
        Column {
            Text(metadata.author.name)
            Text("${metadata.date}${metadata.readTimeMinutes} min read")
        }
    }
}

이제 접근성 서비스가 전체 컨테이너에 한 번에 포커스를 맞추고 다음과 같습니다.

사용자 이름이 포함된 UI 요소의 그룹. 모든 요소가 함께 선택되어 있습니다.

맞춤 작업 추가

다음 목록 항목을 살펴보세요.

일반적인 목록 항목, 기사 제목, 저자, 북마크 아이콘이 포함됨

TalkBack과 같은 스크린 리더를 사용하여 먼저 전체 항목을 선택한 다음 북마크 아이콘을 탭합니다.

목록 항목, 모든 요소가 함께 선택됨

목록 항목, 북마크 아이콘이 선택됨

목록이 길면 이 작업이 매우 반복적일 수 있습니다. 더 나은 접근 방식은 사용자가 항목을 북마크할 수 있는 사용자 지정 작업을 정의합니다. 유의사항 북마크 아이콘의 동작을 명시적으로 제거해야 합니다. 자체를 호출하여 접근성 서비스에서 선택되지 않도록 합니다. 이 clearAndSetSemantics 수정자를 사용하여 실행됩니다.

@Composable
private fun PostCardSimple(
    /* ... */
    isFavorite: Boolean,
    onToggleFavorite: () -> Boolean
) {
    val actionLabel = stringResource(
        if (isFavorite) R.string.unfavorite else R.string.favorite
    )
    Row(
        modifier = Modifier
            .clickable(onClick = { /* ... */ })
            .semantics {
                // Set any explicit semantic properties
                customActions = listOf(
                    CustomAccessibilityAction(actionLabel, onToggleFavorite)
                )
            }
    ) {
        /* ... */
        BookmarkButton(
            isBookmarked = isFavorite,
            onClick = onToggleFavorite,
            // Clear any semantics properties set on this node
            modifier = Modifier.clearAndSetSemantics { }
        )
    }
}

요소의 상태 설명

컴포저블은 컴포저블이 호출되는 시맨틱의 stateDescription를 Android 프레임워크는 컴포저블의 상태를 읽는 데 사용합니다. 대상 예를 들어 전환 가능한 컴포저블은 '선택됨' 상태 또는 또는 '선택 해제됨' 있습니다. 경우에 따라 기본 상태 설명을 재정의해야 할 수 있습니다. 라벨이 지정되어 있습니다. 이렇게 하려면 상태를 명시적으로 지정하면 됩니다. 구성 가능한 함수를 전환 가능으로 정의하기 전에 설명 라벨을 추가합니다.

@Composable
private fun TopicItem(itemTitle: String, selected: Boolean, onToggle: () -> Unit) {
    val stateSubscribed = stringResource(R.string.subscribed)
    val stateNotSubscribed = stringResource(R.string.not_subscribed)
    Row(
        modifier = Modifier
            .semantics {
                // Set any explicit semantic properties
                stateDescription = if (selected) stateSubscribed else stateNotSubscribed
            }
            .toggleable(
                value = selected,
                onValueChange = { onToggle() }
            )
    ) {
        /* ... */
    }
}

제목 정의

앱에서 스크롤 가능한 컨테이너의 한 화면에 많은 콘텐츠를 표시하는 경우가 있습니다. 예를 들어 사용자가 읽고 있는 기사의 전체 내용을 화면에 표시할 수 있습니다.

블로그 게시물의 스크린샷. 스크롤 가능한 컨테이너에 있는 기사 텍스트가 있음

접근성 기능이 필요한 사용자는 이러한 화면을 탐색하는 데 어려움이 있습니다. 보조하다 어느 요소가 제목인지 나타내야 합니다. 앞의 예에서 각 하위 섹션 제목을 접근성을 위한 제목으로 정의할 수 있습니다. 다소 유용함 TalkBack과 같은 접근성 서비스를 사용하면 사용자가 제목으로 이동합니다

Compose에서는 다음을 정의하여 컴포저블이 제목임을 나타냅니다. semantics 속성:

@Composable
private fun Subsection(text: String) {
    Text(
        text = text,
        style = MaterialTheme.typography.headlineSmall,
        modifier = Modifier.semantics { heading() }
    )
}

맞춤 컴포저블 처리

앱의 특정 Material 구성요소를 맞춤으로 대체할 때마다 접근성을 염두에 두어야 합니다.

Material Checkbox를 자체 구현으로 대체한다고 가정해 보겠습니다. triStateToggleable 수정자를 추가하는 것을 잊어버릴 수 있습니다. 이 구성요소의 접근성 속성을 지정합니다.

경험적으로 봤을 때 머티리얼 라이브러리를 사용하고 개발자가 찾을 수 있는 모든 접근성 동작을 모방합니다. 또한 UI 수준 수정자가 아니라 기초 수정자를 많이 활용하세요. 기초 수정자에는 접근성 고려 사항이 기본적으로 포함되어 있습니다.

맞춤 구성요소 구현 테스트 사용하여 동작을 확인할 수 있습니다.

추가 리소스