앱 접근성 향상 원칙

Android 기기를 사용하는 일부 사용자는 여타 사용자와는 다른 접근성 기능을 필요로 합니다. 동일한 접근성 기능을 필요로 하는 특정 사용자 그룹을 지원하기 위해 Android 프레임워크는 개발자가 접근성 서비스를 만들 수 있는 기능을 제공합니다. 이 접근성 서비스는 개발자에게 앱의 콘텐츠를 제공하고 자동으로 앱을 운영합니다.

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

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

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

다음 각 권장사항은 앱의 접근성을 더욱 향상할 수 있습니다.

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

요소에 라벨 지정

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

대부분의 경우 이 요소가 포함된 레이아웃 리소스 파일에 특정 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 요소 내에 입력해야 하는 콘텐츠를 설명합니다. Android 4.2(API 수준 17) 이상을 실행하는 기기에서는 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"
    }
}

자바

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>

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

맞춤 그룹 라벨

원하는 경우 그룹 자체에 관한 콘텐츠 설명을 제공하여 플랫폼의 기본 그룹화 및 그룹의 내부 요소 설명 순서를 재정의할 수 있습니다.

다음 스니펫에서는 맞춤설정된 그룹 설명의 예를 보여줍니다.

<!-- In response to a single user interaction, accessibility services
     announce the custom content description for the group. -->
<ConstraintLayout
    android:id="@+id/song_data_container" ...
    android:screenReaderFocusable="true"
    android:contentDescription="@string/title_artist_best_song">

    <TextView
        android:id="@+id/song_title" ...

        <!-- Content ignored by accessibility services -->
        android:text="@string/my_song_title" />
    <TextView
        android:id="@+id/song_artist"

        <!-- Content ignored by accessibility services -->
        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:contentDescription 속성을 "null"로 설정합니다.

앱에서 Android 4.1(API 수준 16) 이상을 실행하는 기기만 지원한다면 대신 이렇게 순전히 장식적인 요소의 android:importantForAccessibility 속성을 "no"로 설정할 수 있습니다.

시스템 위젯 확장

핵심 사항: 앱의 UI를 디자인할 때 가능한 한 Android 클래스 계층 구조의 매우 아래에 있는 시스템 제공 위젯을 사용하거나 확장합니다. 계층 구조의 매우 아래에 있는 시스템 제공 위젯에는 앱에 필요한 대부분의 접근성 기능이 이미 있습니다. 더 일반적인 View, ViewCompat, CanvasCanvasCompat 클래스에서 고유한 위젯을 생성하는 것보다 이러한 시스템 제공 위젯을 확장하는 것이 더 쉽습니다.

고도로 맞춤설정된 환경이나 게임 레벨에 필요할 수 있는 View 또는 Canvas를 직접 확장해야 한다면 맞춤 뷰의 접근성 높이기를 참조하세요.

이 섹션에서는 TriSwitch라는 특수한 유형의 Switch를 구현하는 방법을 설명합니다. 각 TriSwitch 인스턴스를 통해 사용자가 3가지의 가능한 상태 간에 전환할 수 있다는 점을 제외하면 TriSwitch 객체는 Switch 객체와 유사하게 작동합니다.

클래스 계층 구조 매우 아래에서 확장

다음과 같이 Switch 객체는 계층 구조의 여러 프레임워크 UI 클래스에서 상속됩니다.

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

자바

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 요소를 만드는 예(왼쪽)와 색상, 도형, 텍스트를 사용하여 UI 요소를 만드는 예(오른쪽)

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

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

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

참고 자료

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

Codelab

블로그 게시물