Compose의 접근성

Compose로 작성된 앱은 요구사항이 서로 다른 여러 사용자의 접근성을 지원해야 합니다. 접근성 서비스는 화면에 표시된 내용을 특정한 요구사항이 있는 사용자에게 보다 적합한 형식으로 변환하는 데 사용됩니다. 접근성 서비스를 지원하기 위해 앱은 Android 프레임워크의 API를 사용하여 UI 요소에 관한 시맨틱 정보를 노출합니다. 그런 다음 Android 프레임워크에서 이 시맨틱 정보를 접근성 서비스에 전달합니다. 각 접근성 서비스에서 사용자에게 앱을 가장 잘 설명하는 방법을 선택할 수 있습니다. Android는 음성 안내 지원, 스위치 제어 등 여러 접근성 서비스를 제공합니다.

시맨틱

Compose는 시맨틱 속성을 사용하여 접근성 서비스에 정보를 전달합니다 시맨틱 속성은 사용자에게 표시되는 UI 요소에 관한 정보를 제공합니다. TextButton과 같은 대부분의 내장 컴포저블은 이 시맨틱 속성을 컴포저블 및 컴포저블의 하위 요소로부터 추론된 정보로 채웁니다. toggleableclickable과 같은 일부 제어자는 특정 시맨틱 속성도 설정합니다. 하지만 프레임워크에서 사용자에게 UI 요소를 설명하는 방법을 이해하기 위해 더 많은 정보가 필요한 경우도 있습니다.

이 문서에서는 Android 프레임워크에 올바로 설명할 수 있도록 컴포저블에 추가 정보를 명시적으로 추가해야 하는 여러 가지 상황을 설명합니다. 또한 특정 컴포저블의 시맨틱 정보를 완전히 바꾸는 방법을 설명합니다. 여기서는 Android의 접근성을 기본적으로 이해하고 있다고 가정합니다.

일반적인 사용 사례

접근성 기능이 필요한 사용자가 앱을 성공적으로 사용할 수 있도록 지원하려면 앱이 이 페이지에 설명된 권장사항을 따라야 합니다.

시각적 요소 설명

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

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

클릭 가능한 아이콘 표시줄, '공유' 아이콘이 강조 표시됨

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

contentDescription 매개변수는 시각적 요소를 설명하는 데 사용됩니다. 설명은 사용자에게 전달되므로 현지화된 문자열을 사용해야 합니다.

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

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

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

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

지정된 시각적 요소에 contentDescription이 필요한지 여부는 사용자가 결정합니다. 요소에서 사용자가 작업을 처리하는 데 필요한 정보를 전달해야 하는지 자문해 보세요. 그렇지 않으면 설명을 사용하지 않는 것이 좋습니다.

맞춤 콘텐츠 설명 설정

콘텐츠 설명을 명시적으로 설정해야 하는 상황이 있을 수도 있습니다. 이러한 상황에서는 semantics 제어자를 사용하여 콘텐츠 설명을 컴포저블에 직접 할당할 수 있습니다. 예를 들어 맞춤 아이콘을 그려야 할 수도 있습니다. 즉 Icon 컴포저블을 직접 사용할 수 없습니다.

@Composable
fun CustomShareIcon(modifier: Modifier = Modifier) {
  val cd = stringResource(R.string.custom_share_icon_content_description)
  Canvas(modifier.semantics { contentDescription = cd }) {
    /* Draw custom icon here */
  }
}

요소 병합

음성 안내 지원 및 스위치 제어와 같은 접근성 서비스를 사용하면 사용자가 화면의 요소 간에 포커스를 이동할 수 있습니다. 요소의 올바른 세부사항에 초점을 맞춰야 합니다. 화면에서 낮은 수준의 모든 단일 컴포저블에 독립적으로 초점을 맞추는 경우 사용자가 화면 간에 이동하기 위해 많은 상호작용이 필요합니다. 요소가 너무 지나치게 병합된 경우 어떤 요소가 서로 결합되어 있는지 사용자가 알지 못할 수도 있습니다.

컴포저블에 clickable 제어자를 적용하면 Compose가 포함된 모든 요소를 자동으로 병합합니다. ListItem의 경우에도 마찬가지입니다. 목록 항목 내 요소가 병합되며 접근성 서비스에서 하나의 요소로 간주합니다.

컴포저블의 집합이 논리적 그룹을 구성할 수 있지만 이 그룹은 클릭할 수 없거나 목록 항목의 일부가 아닙니다. 접근성 서비스에서 계속 컴포저블을 하나의 요소로 간주하도록 할 수 있습니다. 예를 들어 사용자의 아바타, 이름, 추가 정보를 보여주는 컴포저블이 있다고 가정해 보겠습니다.

사용자 이름이 포함된 UI 요소의 그룹. 이름이 선택됩니다.

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

@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 요소의 그룹. 모든 요소가 함께 선택됩니다.

맞춤 작업 추가

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

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

음성 안내 지원과 같은 스크린 리더를 사용하여 화면에 표시된 내용을 듣는 경우 스크린 리더는 먼저 전체 항목을 선택한 다음 북마크 아이콘을 선택합니다.

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

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

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

@Composable
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 { }
    )
  }
}

요소의 상태 설명

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

@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() }
      )
  ) {
    /* ... */
  }
}

제목 정의

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

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

접근성 기능이 필요한 사용자는 이러한 화면을 탐색하는 데 어려움이 있습니다. 탐색에 도움이 되도록 어느 요소가 제목인지 표시할 수 있습니다. 위의 예에서 각 하위 섹션 제목을 접근성을 위한 제목으로 정의할 수 있습니다. 음성 안내 지원과 같은 일부 접근성 서비스를 사용하면 사용자가 제목 간을 직접 이동할 수 있습니다.

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

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

맞춤 하위 수준 컴포저블 만들기

고급 사용 사례에서는 앱의 특정 머티리얼 구성요소를 맞춤 버전으로 바꿉니다. 이 시나리오에서는 접근성 고려사항을 염두에 두어야 합니다. 머티리얼 Checkbox를 자체 구현으로 바꾼다고 가정해 보겠습니다. 이 구성요소의 접근성 속성을 처리하는 triStateToggleable 제어자를 추가하는 것을 잊어버리기 쉽습니다.

일반적으로 머티리얼 라이브러리에서 구성요소 구현을 확인하고 찾을 수 있는 접근성 동작을 모방해야 합니다. 또한 UI 레벨 제어자가 아니라 기초 제어자를 많이 활용하세요. 이 제어자에는 접근성 고려 사항이 기본적으로 포함되어 있습니다. 여러 접근성 서비스로 커스텀 구성요소 구현을 테스트하여 동작을 확인해야 합니다.