앱 접근성 향상 원칙

접근성 기능이 필요한 사용자를 지원할 수 있도록, Android 프레임워크에서는 사용자에게 앱의 콘텐츠를 표시하고 사용자를 대신하여 앱을 조작할 수 있는 접근성 서비스를 만들 수 있습니다.

Android는 다음을 포함하여 여러 시스템 접근성 서비스를 제공합니다.

  • TalkBack: 시력이 약하거나 시각 장애가 있는 사용자를 지원합니다. 합성된 음성을 통해 콘텐츠를 알리고 앱에서 사용자 동작에 응답하여 작업을 실행합니다.
  • 스위치 제어: 거동 장애가 있는 사용자를 지원합니다. 상호작용 요소를 강조표시하고 사용자가 버튼을 누르면 이에 응답하여 작업을 실행합니다. 1개 또는 2개의 버튼만 사용하여 기기를 제어할 수 있게 합니다.

접근성 기능이 필요한 사용자가 앱을 성공적으로 사용하도록 지원하려면 앱이 이 페이지에 설명된 권장사항을 따라야 합니다. 이 권장사항은 더욱 접근성 높은 앱 만들기에 설명된 가이드라인을 기반으로 합니다.

이어지는 섹션에서 설명하는 각 권장사항은 앱의 접근성을 더욱 향상합니다.

요소에 라벨 지정
사용자는 앱 내에 있는 상호작용이 가능한 중요한 각 UI 요소의 콘텐츠 및 목적을 이해할 수 있어야 합니다.
접근성 작업 추가
접근성 작업을 추가하면 접근성 서비스 사용자가 앱에서 중요한 사용자 플로우를 완료하도록 할 수 있습니다.
시스템 위젯 확장
자체 맞춤 뷰를 만드는 대신 프레임워크에 포함된 뷰 요소를 활용하여 빌드합니다. 프레임워크의 뷰 및 위젯 클래스는 이미 앱에 필요한 대부분의 접근성 기능을 제공합니다.
색상 이외의 큐 사용
사용자는 UI의 요소 카테고리를 명확하게 구분할 수 있어야 합니다. 카테고리를 구분하려면 패턴 및 위치를 색상과 함께 사용하여 카테고리의 차이를 표현합니다.
미디어 콘텐츠의 접근성 높이기
앱의 동영상 또는 오디오 콘텐츠를 사용하는 사용자가 시각 또는 청각적인 큐에 전적으로 의존할 필요가 없도록 앱의 동영상 또는 오디오 콘텐츠에 설명을 추가하세요.

요소에 라벨 지정

앱에서 상호작용이 가능한 각 UI 요소에 관한 설명이 포함된 유용한 라벨을 사용자에게 제공하는 것이 중요합니다. 각 라벨은 특정 요소의 의미와 목적을 설명해야 합니다. TalkBack과 같은 스크린 리더는 사용자에게 이러한 라벨을 알릴 수 있습니다.

대부분의 경우 이 요소가 포함된 레이아웃 리소스 파일에 UI 요소의 설명을 지정합니다. 더욱 접근성 높은 앱 만들기 가이드에 설명된 것처럼 대개 contentDescription 속성을 사용하여 라벨을 추가하지만 이어지는 섹션에 설명된 몇 가지 라벨 지정 기법에도 유의해야 합니다.

수정 가능한 요소

EditText 객체와 같은 수정 가능한 요소에 라벨을 지정할 때는 이 텍스트 예를 스크린 리더에서 사용할 수 있게 하는 것 외에도 요소 자체에 올바른 입력의 예를 제공하는 텍스트를 표시하는 것이 좋습니다. 이러한 상황에서는 다음 스니펫에 표시된 대로 android:hint 속성을 사용할 수 있습니다.

<!-- The hint text for en-US locale would be
     "Apartment, suite, or building". -->
<EditText
   android:id="@+id/addressLine2"
   android:hint="@string/aptSuiteBuilding" ... />

이 상황에서 View 객체의 android:labelFor 속성은 EditText 요소의 ID로 설정되어야 합니다. 자세한 내용은 다음 섹션을 참고하세요.

한 요소가 다른 요소를 설명하는 요소 쌍

일반적으로 특정 EditText 요소에는 상응하는 View 객체가 있어 사용자가 EditText 요소 내에 입력해야 하는 콘텐츠를 설명합니다. View 객체의 android:labelFor 속성을 설정하여 이 관계를 나타낼 수 있습니다.

다음 스니펫에서는 이러한 요소 쌍에 라벨을 지정하는 예를 보여줍니다.


<!-- Label text for en-US locale would be "Username:" -->
<TextView
   android:id="@+id/usernameLabel" ...
   android:text="@string/username"
   android:labelFor="@+id/usernameEntry" />

<EditText
   android:id="@+id/usernameEntry" ... />

<!-- Label text for en-US locale would be "Password:" -->
<TextView
   android:id="@+id/passwordLabel" ...
   android:text="@string/password
   android:labelFor="@+id/passwordEntry" />

<EditText
   android:id="@+id/passwordEntry"
   android:inputType="textPassword" ... />

컬렉션의 요소

컬렉션의 요소에 라벨을 추가할 때 각 라벨은 고유해야 합니다. 라벨이 고유해야 라벨을 알릴 때 시스템의 접근성 서비스에서 화면에 표시되는 요소를 정확히 하나만 참조할 수 있습니다. 이러한 대응 관계를 통해 사용자가 UI를 순환했을 때 또는 이미 찾은 요소로 포커스를 이동했을 때 이를 알 수 있습니다.

특히 RecyclerView 객체와 같이 재사용된 레이아웃 내 요소에 추가 텍스트 또는 컨텍스트 정보를 포함하여 각 하위 요소가 고유하게 식별되도록 해야 합니다.

이렇게 하려면 다음 코드 스니펫과 같이 어댑터 구현의 일부로 콘텐츠 설명을 설정합니다.

Kotlin

data class MovieRating(val title: String, val starRating: Integer)

class MyMovieRatingsAdapter(private val myData: Array<MovieRating>):
        RecyclerView.Adapter<MyMovieRatingsAdapter.MyRatingViewHolder>() {

    class MyRatingViewHolder(val ratingView: ImageView) :
            RecyclerView.ViewHolder(ratingView)

    override fun onBindViewHolder(holder: MyRatingViewHolder, position: Int) {
        val ratingData = myData[position]
        holder.ratingView.contentDescription = "Movie ${position}: " +
                "${ratingData.title}, ${ratingData.starRating} stars"
    }
}

Java

public class MovieRating {
    private String title;
    private int starRating;
    // ...
    public String getTitle() { return title; }
    public int getStarRating() { return starRating; }
}

public class MyMovieRatingsAdapter
        extends RecyclerView.Adapter<MyAdapter.MyRatingViewHolder> {
    private MovieRating[] myData;


    public static class MyRatingViewHolder extends RecyclerView.ViewHolder {
        public ImageView ratingView;
        public MyRatingViewHolder(ImageView iv) {
            super(iv);
            ratingView = iv;
        }
    }

    @Override
    public void onBindViewHolder(MyRatingViewHolder holder, int position) {
        MovieRating ratingData = myData[position];
        holder.ratingView.setContentDescription("Movie " + position + ": " +
                ratingData.getTitle() + ", " + ratingData.getStarRating() +
                " stars")
    }
}

관련 콘텐츠 그룹

앱에서 노래의 세부정보나 메시지의 속성과 같이 자연스러운 그룹을 형성하는 여러 UI 요소를 표시하는 경우 대개 ViewGroup의 서브클래스인 컨테이너 내에 이러한 요소를 정렬합니다. 컨테이너 객체의 android:screenReaderFocusable 속성을 true로 설정하고 각 내부 객체의 android:focusable 속성을 false로 설정합니다. 이렇게 설정하면 접근성 서비스에서 내부 요소의 콘텐츠 설명을 하나의 알림에 차례로 표시할 수 있습니다. 이렇게 관련 요소를 통합하면 사용자가 보조 기술을 사용하여 화면에 표시된 정보를 더 효율적으로 찾을 수 있습니다.

다음 스니펫에는 서로 관련된 콘텐츠 부분이 포함되어 있습니다. 즉, 컨테이너 요소(ConstraintLayout의 인스턴스)에는 android:screenReaderFocusable 속성이 true로 설정되어 있으며 내부 TextView 요소에는 각각 android:focusable 속성이 false로 설정되어 있습니다.

<!-- In response to a single user interaction, accessibility services announce
     both the title and the artist of the song. -->
<ConstraintLayout
    android:id="@+id/song_data_container" ...
    android:screenReaderFocusable="true">

    <TextView
        android:id="@+id/song_title" ...
        android:focusable="false"
        android:text="@string/my_song_title" />
    <TextView
        android:id="@+id/song_artist"
        android:focusable="false"
        android:text="@string/my_songwriter" />
</ConstraintLayout>

접근성 서비스는 내부 요소의 설명을 하나의 표현으로 알려주기 때문에 각 설명을 최대한 짧게 유지하면서 요소의 의미를 전달하는 것이 중요합니다.

참고: 일반적으로 그룹의 텍스트 설명을 집계하여 그룹의 콘텐츠 설명을 만들지 않아야 합니다. 이렇게 하면 그룹의 설명이 불안정해지고 하위 요소의 텍스트가 변경될 때 그룹의 설명이 더 이상 표시되는 텍스트와 일치하지 않을 수 있습니다.

목록 또는 그리드 컨텍스트에서 스크린 리더는 목록 또는 그리드 요소의 하위 텍스트 노드의 텍스트를 통합할 수 있습니다. 이 공지사항을 수정하지 않는 것이 좋습니다.

중첩된 그룹

앱 인터페이스에 축제 이벤트의 일별 목록과 같은 다차원 정보가 표시되는 경우 내부 그룹 컨테이너에 android:screenReaderFocusable 속성을 사용합니다. 이러한 라벨 지정 방식은 화면의 콘텐츠를 발견하는 데 필요한 알림의 수와 각 알림의 길이 간에 적절한 균형을 제공합니다.

다음 코드 스니펫에서는 대규모 그룹 내에서 그룹에 라벨을 지정하는 한 가지 방법을 보여줍니다.

<!-- In response to a single user interaction, accessibility services
     announce the events for a single stage only. -->
<ConstraintLayout
    android:id="@+id/festival_event_table" ... >
    <ConstraintLayout
        android:id="@+id/stage_a_event_column"
        android:screenReaderFocusable="true">

        <!-- UI elements that describe the events on Stage A. -->

    </ConstraintLayout>
    <ConstraintLayout
        android:id="@+id/stage_b_event_column"
        android:screenReaderFocusable="true">

        <!-- UI elements that describe the events on Stage B. -->

    </ConstraintLayout>
</ConstraintLayout>

텍스트 내 제목

일부 앱에서는 제목을 사용해 화면에 표시되는 텍스트 그룹을 요약합니다. 특정 View 요소에서 제목을 표현하는 경우 요소의 android:accessibilityHeading 속성을 true로 설정하여 접근성 서비스의 용도를 나타낼 수 있습니다.

접근성 서비스 사용자는 단락 또는 단어가 아닌 제목 사이를 탐색하도록 선택할 수 있습니다. 이러한 유연성을 통해 텍스트 탐색 환경이 개선됩니다.

접근성 창(pane) 제목

Android 9(API 수준 28) 이상에서는 화면의 창(pane)에 접근성을 고려한 제목을 제공할 수 있습니다. 접근성을 위해 창(pane)은 프래그먼트 콘텐츠와 같이 시각적으로 구분되는 창(window)의 부분입니다. 접근성 서비스에서 창(window)과 유사한 창(pane) 동작을 이해할 수 있도록 앱의 창(pane)에 내용을 잘 나타내는 제목을 제공해야 합니다. 그러면 접근성 서비스에서 창(pane)의 모양이나 콘텐츠가 변경될 때 더 세분화된 정보를 사용자에게 제공할 수 있습니다.

창(pane)의 제목을 지정하려면 다음 스니펫과 같이 android:accessibilityPaneTitle 속성을 사용합니다.

<!-- Accessibility services receive announcements about content changes
     that are scoped to either the "shopping cart view" section (top) or
     "browse items" section (bottom) -->
<MyShoppingCartView
     android:id="@+id/shoppingCartContainer"
     android:accessibilityPaneTitle="@string/shoppingCart" ... />

<MyShoppingBrowseView
     android:id="@+id/browseItemsContainer"
     android:accessibilityPaneTitle="@string/browseProducts" ... />

장식 요소

UI의 요소가 시각적 간격용 또는 시각적 표시용으로만 존재하면 android:importantForAccessibility 속성을 "no"로 설정합니다.

접근성 작업 추가

접근성 서비스 사용자가 앱에서 모든 사용자 플로우를 쉽게 실행할 수 있도록 하는 것이 중요합니다. 예를 들어, 사용자가 목록에서 항목을 스와이프할 수 있다면 이 사용자가 또 다른 방법으로 동일한 사용자 플로우를 완료할 수 있도록 이 작업을 접근성 서비스에 노출할 수 있습니다.

모든 작업의 접근성 향상

TalkBack, 음성 액세스 또는 스위치 제어 사용자는 앱에서 특정 사용자 플로우를 완료할 또 다른 방법이 필요할 수 있습니다. 드래그 앤 드롭, 스와이프와 같은 동작에 연결된 작업의 경우, 접근성 서비스 사용자가 쉽게 사용할 수 있도록 앱에서 이러한 동작을 노출할 수 있습니다.

앱은 접근성 동작을 사용하여 사용자가 특정 작업을 완료할 또 다른 방법을 제공할 수 있습니다.

예를 들어, 앱에서 사용자가 항목을 스와이프할 수 있다면 다음과 같은 사용자설정 접근성 작업을 통해 이 기능을 노출할 수 있습니다.

Kotlin

ViewCompat.addAccessibilityAction(
    // View to add accessibility action
    itemView,
    // Label surfaced to user by an accessibility service
    getText(R.id.archive)
) { _, _ ->
    // Same method executed when swiping on itemView
    archiveItem()
    true
}

Java

ViewCompat.addAccessibilityAction(
    // View to add accessibility action
    itemView,
    // Label surfaced to user by an accessibility service
    getText(R.id.archive),
    (view, arguments) -> {
        // Same method executed when swiping on itemView
        archiveItem();
        return true;
    }
);

With the custom accessibility action implemented, users can access the action through the actions menu.

Make available actions understandable

When a view supports actions such as touch & hold, an accessibility service such as TalkBack announces it as "Double tap and hold to long press."

This generic announcement doesn't give the user any context about what a touch & hold action does.

To make this announcement more descriptive, you can replace the accessibility action’s announcement like so:

Kotlin

ViewCompat.replaceAccessibilityAction(
    // View that contains touch & hold action
    itemView,
    AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_LONG_CLICK,
    // Announcement read by TalkBack to surface this action
    getText(R.string.favorite),
    null
)

Java

ViewCompat.replaceAccessibilityAction(
    // View that contains touch & hold action
    itemView,
    AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_LONG_CLICK,
    // Announcement read by TalkBack to surface this action
    getText(R.string.favorite),
    null
);

This results in TalkBack announcing "Double tap and hold to favorite," helping users understand the purpose of the action.

Extend system widgets

Note: When you design your app's UI, use or extend system-provided widgets that are as far down Android's class hierarchy as possible. System-provided widgets that are far down the hierarchy already have most of the accessibility capabilities your app needs. It's easier to extend these system-provided widgets than to create your own from the more generic View, ViewCompat, Canvas, and CanvasCompat classes.

If you must extend View or Canvas directly, which might be necessary for a highly customized experience or a game level, see Make custom views more accessible.

This section uses the example of implementing a special type of Switch called TriSwitch while following best practices around extending system widgets. A TriSwitch object works similarly to a Switch object, except that each instance of TriSwitch allows the user to toggle among three possible states.

Extend from far down the class hierarchy

The Switch object inherits from several framework UI classes in its hierarchy:

View
↳ TextView
  ↳ Button
    ↳ CompoundButton
      ↳ Switch

TriSwitch 클래스는 Switch 클래스에서 직접 확장하는 것이 가장 좋습니다. 이렇게 하면 Android 접근성 프레임워크TriSwitch 클래스에 필요한 대부분의 접근성 기능을 제공합니다.

  • 접근성 작업: 접근성 서비스가 TriSwitch 객체에서 실행되는 각각의 가능한 사용자 입력을 에뮬레이션하는 방법을 시스템에 알립니다. (View에서 상속됨)
  • 접근성 이벤트: 화면을 새로 고치거나 업데이트할 때 TriSwitch 객체의 모양이 변경될 수 있는 모든 가능한 방법에 관해 접근성 서비스에 알립니다. (View에서 상속됨)
  • 특성: 표시되는 텍스트의 콘텐츠와 같은 각 TriSwitch 객체에 관한 세부정보입니다. (TextView에서 상속됨)
  • 상태 정보: '선택됨' 또는 '선택 해제됨'과 같은 TriSwitch 객체의 현재 상태에 관한 설명입니다. (CompoundButton에서 상속됨)
  • 상태에 관한 텍스트 설명: 각 상태가 나타내는 사항에 관한 텍스트 기반 설명입니다. (Switch에서 상속됨)

Switch 및 슈퍼클래스의 이러한 동작은 TriSwitch 객체의 동작과 거의 비슷합니다. 따라서 구현 시 가능한 상태 수를 2개에서 3개로 확장하는 데 집중할 수 있습니다.

맞춤 이벤트 정의

시스템 위젯을 확장할 때 사용자가 그 위젯과 상호작용하는 방식의 측면을 변경할 수 있습니다. 접근성 서비스가 마치 사용자가 위젯과 직접 상호작용한 것처럼 앱의 위젯을 업데이트할 수 있도록 이러한 상호작용 변경사항을 정의하는 작업이 중요합니다.

일반적인 가이드라인은 재정의하는 모든 뷰 기반 콜백에 대해 ViewCompat.replaceAccessibilityAction()을 재정의하여 상응하는 접근성 작업을 다시 정의해야 한다는 것입니다. 앱의 테스트에서 ViewCompat.performAccessibilityAction()을 호출하여 이러한 재정의된 작업의 동작을 검증할 수 있습니다.

이 원칙이 TriSwitch 객체에서 작동하는 방식

일반적인 Switch 객체와 달리 TriSwitch 객체를 탭하면 3가지의 가능한 상태가 순환됩니다. 따라서 상응하는 ACTION_CLICK 접근성 작업을 다음과 같이 업데이트해야 합니다.

Kotlin

class TriSwitch(context: Context) : Switch(context) {
    // 0, 1, or 2
    var currentState: Int = 0
        private set

    init {
        updateAccessibilityActions()
    }

    private fun updateAccessibilityActions() {
        ViewCompat.replaceAccessibilityAction(this, ACTION_CLICK,
            action-label) {
            view, args -> moveToNextState()
        })
    }

    private fun moveToNextState() {
        currentState = (currentState + 1) % 3
    }
}

Java

public class TriSwitch extends Switch {
    // 0, 1, or 2
    private int currentState;

    public int getCurrentState() {
        return currentState;
    }

    public TriSwitch() {
        updateAccessibilityActions();
    }

    private void updateAccessibilityActions() {
        ViewCompat.replaceAccessibilityAction(this, ACTION_CLICK,
            action-label, (view, args) -> moveToNextState());
    }

    private void moveToNextState() {
        currentState = (currentState + 1) % 3;
    }
}

색상 이외의 큐 사용

색약이 있는 사용자를 지원하려면 색상 이외의 큐를 사용해 앱 화면 내에 있는 UI 요소를 구별하세요. 이러한 기법에는 다양한 모양 또는 크기를 사용하거나 텍스트 또는 시각적 패턴을 제공하거나 요소의 차이점을 표시하는 오디오 기반 또는 터치 기반(햅틱) 반응을 추가하는 것이 포함될 수 있습니다.

그림 1에서는 활동의 두 가지 버전을 보여줍니다. 한 버전에서는 워크플로에서 실행 가능한 두 작업을 구분하는 데 색상만 사용합니다. 다른 버전에서는 두 옵션의 차이를 강조표시하기 위해 색상 외에 도형 및 텍스트를 포함하는 권장사항을 사용합니다.

그림 1. 색상만 사용하여 UI 요소를 만드는 예(왼쪽)와 색상, 도형, 텍스트 (오른쪽)를 사용하는 예

미디어 콘텐츠의 접근성 높이기

동영상 클립 또는 오디오 녹음과 같은 미디어 콘텐츠가 포함된 앱을 개발한다면 다양한 유형의 접근성이 필요한 사용자가 이 자료를 이해할 수 있도록 지원해 보세요. 특히 다음과 같은 작업을 하는 것이 좋습니다.

  • 사용자가 미디어를 일시중지 또는 중지하고 볼륨을 변경하며 자막을 전환할 수 있는 컨트롤을 포함합니다.
  • 동영상에서 워크플로를 완료하는 데 필수적인 정보를 표시하는 경우 동일한 콘텐츠를 스크립트와 같은 대체 형식으로 제공합니다.

추가 리소스

앱의 접근성을 높이는 방법을 자세히 알아보려면 다음 추가 리소스를 참조하세요.

Codelab

블로그 게시물