Android 앱 접근성(Accessibility) 완벽 가이드 — TalkBack, contentDescription, 글자 크기와 UI 테스트

Android 앱을 개발할 때 화면 디자인과 기능 구현에 집중하다 보면 놓치기 쉬운 부분이 있습니다.

바로 **접근성(Accessibility)**입니다.

예를 들어 조류 도감 앱에서 새 사진 옆에 하트 모양의 좋아요 버튼을 배치했다고 가정하겠습니다.

화면을 보는 사용자는 하트 아이콘의 의미를 쉽게 이해할 수 있습니다. 하지만 TalkBack을 사용하는 사람에게 버튼이 단순히 '버튼'이라고만 읽힌다면 어떤 기능인지 알기 어렵습니다.

또 다른 예로 사용자가 스마트폰의 글자 크기를 크게 설정했는데, 앱에서 TextView의 높이를 고정해 두었다면 글자가 잘리거나 버튼을 누르기 어려워질 수 있습니다.

이런 문제는 장애가 있는 사용자뿐 아니라 고령자, 작은 화면을 사용하는 사람, 한 손으로 스마트폰을 사용하는 사람에게도 영향을 줍니다.

이번 글에서는 Android 앱의 접근성을 개선하기 위해 다음 내용을 살펴보겠습니다.

  • Android 접근성의 기본 개념

  • TalkBack과 접근성 트리

  • contentDescription 설정

  • 아이콘 버튼과 이미지 접근성

  • RecyclerView 항목 및 좋아요 상태 처리

  • BottomNavigationView와 ViewPager2 접근성

  • 글자 크기 확대와 Android 14 비선형 글꼴 크기 조절

  • 최소 터치 영역과 색상 대비

  • 입력 오류 및 상태 변경 안내

  • 접근성 자동화 테스트와 수동 검증

개발 환경: Kotlin, XML View, AndroidX, minSdk 24 이상. 코드 예제는 개별 기능 설명을 위한 것이며, 실제 프로젝트의 ViewBinding 이름과 리소스에 맞게 조정해야 합니다.


1. Android 접근성이란?

접근성은 다양한 사용자가 앱의 기능과 정보를 이용할 수 있도록 설계하는 것입니다.

대표적으로 다음과 같은 상황을 고려합니다.

사용자 상황필요한 접근성
화면을 보기 어려움화면 읽기 기능, 의미 있는 설명
작은 글씨를 읽기 어려움글자 크기 확대
손가락 움직임이 불편함충분한 터치 영역
색상 구분이 어려움색상 외의 상태 표현
청각 정보 이용이 어려움자막과 시각적 안내
키보드나 스위치로 조작논리적인 포커스 순서

Android에서는 TalkBack, Switch Access, 글자 크기 및 화면 확대 등의 접근성 기능을 제공합니다.

앱 개발자는 이러한 기능과 호환되는 UI를 구성해야 합니다.

2. TalkBack이란?

TalkBack은 Android의 화면 읽기 기능입니다.

사용자가 화면의 요소를 탐색하면 해당 요소의 이름, 역할, 상태 등을 음성으로 안내합니다.

예를 들어 다음과 같은 버튼이 있다고 가정하겠습니다.

<ImageButton
    android:id="@+id/buttonFavorite"
    android:layout_width="48dp"
    android:layout_height="48dp"
    android:src="@drawable/ic_favorite" />

화면에는 하트 아이콘이 표시되지만 TalkBack에서는 의미가 충분히 전달되지 않을 수 있습니다.

이때 다음과 같이 설명을 추가할 수 있습니다.

android:contentDescription="@string/favorite_add"

strings.xml에는 다음과 같이 작성합니다.

<string name="favorite_add">좋아요 추가</string>

이제 화면 읽기 사용자는 버튼의 기능을 이해하기 쉬워집니다.


contentDescription 올바르게 사용하기

3. contentDescription의 역할

contentDescription은 화면에 표시되는 시각적 요소의 의미를 접근성 서비스에 전달할 때 사용합니다.

특히 다음 요소에 유용합니다.

  • 아이콘만 있는 버튼

  • 의미 있는 이미지

  • 이미지로 표시된 상태

  • 텍스트가 없는 클릭 요소

하지만 모든 View에 무조건 contentDescription을 추가하는 것은 좋은 방법이 아닙니다.

이미 화면에 표시된 텍스트를 TalkBack이 읽을 수 있다면 별도의 설명이 불필요할 수 있습니다.

4. 아이콘 버튼에는 기능을 설명하기

예를 들어 검색 버튼이 있습니다.

<ImageButton
    android:id="@+id/buttonSearch"
    android:layout_width="48dp"
    android:layout_height="48dp"
    android:src="@drawable/ic_search"
    android:background="?attr/selectableItemBackgroundBorderless"
    android:contentDescription="@string/search" />
<string name="search">검색</string>

버튼의 모양보다 실제로 수행하는 동작을 설명하는 것이 좋습니다.

예를 들어 '돋보기 그림'보다는 '검색'이 더 적절합니다.

5. 장식용 이미지에는 설명을 추가하지 않기

모든 이미지가 정보를 전달하는 것은 아닙니다.

예를 들어 카드 배경의 장식 이미지라면 화면 읽기 사용자가 해당 이미지를 탐색할 필요가 없습니다.

<ImageView
    android:layout_width="match_parent"
    android:layout_height="120dp"
    android:src="@drawable/bg_decoration"
    android:contentDescription="@null"
    android:importantForAccessibility="no" />

이렇게 하면 불필요한 접근성 탐색을 줄일 수 있습니다.

단, 이미지 자체가 중요한 정보를 전달한다면 숨겨서는 안 됩니다.

6. 의미 있는 이미지에는 정보를 설명하기

조류 도감 앱에서 새 사진은 중요한 콘텐츠입니다.

예를 들어 다음과 같이 설명할 수 있습니다.

binding.imageBird.contentDescription =
    getString(
        R.string.bird_photo_description,
        bird.nameKor
    )
<string name="bird_photo_description">%1$s 사진</string>

결과적으로 '참새 사진'처럼 읽힐 수 있습니다.

다만 같은 카드에서 이미 '참새'라는 텍스트를 읽고 있다면 중복 설명이 되는지 확인해야 합니다.

이미지가 별도의 정보를 제공하지 않는 경우에는 설명을 줄이거나 카드 전체의 접근성 구성을 조정하는 것이 좋습니다.


동적으로 변하는 상태 처리

7. 좋아요 버튼의 상태를 알려주기

좋아요 버튼은 누를 때마다 상태가 바뀝니다.

따라서 처음에 설정한 설명을 그대로 유지해서는 안 됩니다.

private fun updateFavoriteButton(
    isFavorite: Boolean
) {
    binding.buttonFavorite.isSelected =
        isFavorite

    binding.buttonFavorite.contentDescription =
        getString(
            if (isFavorite) {
                R.string.favorite_remove
            } else {
                R.string.favorite_add
            }
        )
}
<string name="favorite_add">좋아요 추가</string>
<string name="favorite_remove">좋아요 해제</string>

이렇게 하면 현재 버튼을 눌렀을 때 어떤 동작이 수행되는지 알 수 있습니다.

8. stateDescription 활용하기

버튼의 동작과 현재 상태를 구분해서 전달할 수도 있습니다.

ViewCompat.setStateDescription(
    binding.buttonFavorite,
    if (isFavorite) {
        getString(R.string.state_selected)
    } else {
        getString(R.string.state_not_selected)
    }
)
<string name="state_selected">선택됨</string>
<string name="state_not_selected">선택되지 않음</string>

ViewCompat.setStateDescription()은 AndroidX Core에서 제공하는 호환성 API입니다.

사용 중인 AndroidX Core 버전에서 해당 API를 지원하는지 확인해야 합니다.

상태 설명과 콘텐츠 설명이 같은 정보를 지나치게 반복하지 않도록 실제 TalkBack으로 확인하는 것이 좋습니다.


RecyclerView 접근성

9. RecyclerView에서 발생하는 문제

RecyclerView는 여러 항목을 반복해서 표시합니다.

예를 들어 조류 도감 앱의 목록이 다음과 같다고 가정하겠습니다.

참새
Passer montanus
♡ 좋아요

직박구리
Hypsipetes amaurotis
♥ 좋아요

각 항목 안에 이미지, 이름, 학명, 버튼이 들어갑니다.

이때 접근성 구조가 잘못되면 사용자가 하나의 항목을 탐색하기 위해 불필요하게 많은 포커스 이동을 해야 할 수 있습니다.

반대로 카드 전체를 하나의 요소로 묶어 버리면 좋아요 버튼을 개별적으로 누르기 어려워질 수도 있습니다.

10. 카드 전체 클릭과 내부 버튼 구분하기

카드 전체를 누르면 상세 화면으로 이동하고, 내부 하트 버튼은 좋아요를 변경하는 구조를 생각해 보겠습니다.

조류 카드
 ├── 사진
 ├── 국명
 ├── 학명
 └── 좋아요 버튼

이 경우 카드와 좋아요 버튼이 각각 의미 있는 동작을 수행합니다.

따라서 내부 버튼까지 무조건 접근성 탐색에서 제외해서는 안 됩니다.

카드에 요약 설명을 제공하면서 좋아요 버튼은 독립적인 접근성 요소로 유지하는 방식이 좋습니다.

11. RecyclerView ViewHolder 예제

fun bind(bird: BirdItem) {

    binding.textNameKor.text =
        bird.nameKor

    binding.textNameScientific.text =
        bird.scientificName

    binding.root.contentDescription =
        context.getString(
            R.string.bird_item_description,
            bird.nameKor,
            bird.scientificName
        )

    binding.buttonFavorite.contentDescription =
        context.getString(
            if (bird.isFavorite) {
                R.string.favorite_remove
            } else {
                R.string.favorite_add
            }
        )

    ViewCompat.setStateDescription(
        binding.buttonFavorite,
        context.getString(
            if (bird.isFavorite) {
                R.string.state_selected
            } else {
                R.string.state_not_selected
            }
        )
    )
}
<string name="bird_item_description">%1$s, 학명 %2$s</string>

위 코드는 기본적인 설명 예제입니다.

실제 프로젝트에서는 카드 루트가 접근성 요소로 합쳐지는 방식, 자식 요소의 탐색 가능 여부, 클릭 처리 등을 함께 확인해야 합니다.

특히 카드에 contentDescription을 추가하는 것만으로 항상 원하는 포커스 구조가 만들어지는 것은 아닙니다.


12. RecyclerView 항목 갱신 시 주의사항

좋아요 상태가 변경되면 RecyclerView 항목이 다시 바인딩될 수 있습니다.

이때 다음 문제가 발생할 수 있습니다.

  • TalkBack 포커스가 예상치 못한 위치로 이동

  • 전체 목록이 다시 그려짐

  • 상태 변경이 음성으로 전달되지 않음

가능하면 DiffUtil과 ListAdapter를 이용해 변경된 항목만 갱신하는 것이 좋습니다.

또한 단순한 상태 변경을 위해 매번 notifyDataSetChanged()를 호출하는 방식은 피하는 것이 좋습니다.


BottomNavigationView 접근성

13. 하단 내비게이션의 접근성

Android 앱에서는 BottomNavigationView를 자주 사용합니다.

예를 들어 다음과 같은 메뉴입니다.

홈 | 검색 | 좋아요 | 설정

Material Components의 BottomNavigationView는 기본적으로 접근성 기능을 제공합니다.

따라서 일반적인 텍스트 메뉴에 불필요하게 별도의 contentDescription을 추가할 필요는 없습니다.

대신 다음 사항을 확인해야 합니다.

  • 메뉴 이름이 명확한지

  • 선택된 메뉴 상태가 전달되는지

  • 아이콘만으로 의미를 전달하지 않는지

  • 글자 크기를 키워도 메뉴가 정상적으로 표시되는지

14. 메뉴 제목은 간결하게 작성하기

<item
    android:id="@+id/menu_search"
    android:icon="@drawable/ic_search"
    android:title="@string/menu_search" />
<string name="menu_search">검색</string>

메뉴 제목은 '돋보기'보다 '검색'처럼 기능을 설명하는 표현이 좋습니다.


ViewPager2 접근성

15. 이미지 슬라이드 접근성

조류 상세 화면에서 ViewPager2를 이용해 여러 사진을 보여준다고 가정하겠습니다.

참새 사진 1 / 3
참새 사진 2 / 3
참새 사진 3 / 3

이미지를 넘길 때 현재 몇 번째 사진인지 알려주면 화면 읽기 사용자가 위치를 파악하기 쉬워집니다.

16. 이미지 설명에 순서 포함하기

val description = context.getString(
    R.string.bird_gallery_image,
    birdName,
    position + 1,
    totalCount
)

binding.imageBird.contentDescription =
    description
<string name="bird_gallery_image">%1$s 사진, %2$d장 중 %3$d번째</string>

이 예제는 이미지 자체가 접근성 포커스를 받는 경우에 유용합니다.

사진 전환마다 별도의 음성 안내를 강제로 발생시키는 방식은 사용자에게 방해가 될 수 있으므로 신중하게 적용해야 합니다.


글자 크기 확대 대응

17. dp와 sp의 차이

Android에서는 일반적으로 화면 요소 크기에 dp, 텍스트 크기에 sp를 사용합니다.

android:layout_width="48dp"
android:textSize="16sp"

sp는 사용자가 설정한 글자 크기를 반영하는 데 사용합니다.

텍스트 크기를 dp로 지정하면 사용자의 글자 크기 설정을 적절하게 반영하지 못할 수 있습니다.

따라서 텍스트에는 sp를 사용하는 것이 기본 원칙입니다.

18. TextView 높이를 고정하지 않기

다음과 같은 레이아웃을 생각해 보겠습니다.

<TextView
    android:layout_width="match_parent"
    android:layout_height="40dp"
    android:textSize="18sp"
    android:text="@string/bird_description" />

글자 크기가 커지면 텍스트가 잘릴 수 있습니다.

가능하면 다음과 같이 작성합니다.

<TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:textSize="18sp"
    android:text="@string/bird_description" />

단, wrap_content만 사용한다고 모든 문제가 해결되는 것은 아닙니다.

부모 레이아웃의 높이, 제약 조건, 스크롤 가능 여부도 함께 확인해야 합니다.

19. Android 14의 비선형 글꼴 크기 조절

Android 14(API 34)에서는 큰 글꼴 크기에 대한 비선형 글꼴 스케일링이 도입되었습니다.

이 기능은 작은 글자와 큰 글자의 크기 변화가 동일한 비율로 적용되지 않을 수 있다는 의미입니다.

따라서 다음과 같이 단순 계산하는 방식은 주의해야 합니다.

val scaledSize =
    textSize * resources.configuration.fontScale

특히 글꼴 크기를 수동으로 계산하거나 sp를 임의로 dp로 변환하는 로직이 있다면 실제 기기에서 확인해야 합니다.

기본적으로는 Android의 텍스트 크기 처리 API와 sp 단위를 활용하는 것이 좋습니다.


터치 영역과 색상 대비

20. 최소 터치 영역 48dp

Android 접근성 설계에서는 일반적으로 48dp × 48dp 이상의 터치 영역을 권장합니다.

예를 들어 아이콘 자체는 24dp여도 버튼의 터치 영역은 더 크게 구성할 수 있습니다.

<ImageButton
    android:id="@+id/buttonFavorite"
    android:layout_width="48dp"
    android:layout_height="48dp"
    android:padding="12dp"
    android:src="@drawable/ic_favorite"
    android:contentDescription="@string/favorite_add" />

이렇게 하면 시각적인 아이콘은 작게 유지하면서 누르기 쉬운 영역을 확보할 수 있습니다.

터치 영역이 충분하지 않으면 작은 버튼을 정확하게 누르기 어려울 수 있습니다.

21. TouchDelegate 활용

디자인상 버튼의 실제 크기를 늘리기 어렵다면 TouchDelegate를 이용해 터치 영역을 확장할 수 있습니다.

binding.buttonFavorite.post {

    val rect = Rect()

    binding.buttonFavorite.getHitRect(rect)

    val extra = 12.dpToPx()

    rect.inset(-extra, -extra)

    binding.parentLayout.touchDelegate =
        TouchDelegate(
            rect,
            binding.buttonFavorite
        )
}

위 코드는 dpToPx() 확장 함수가 구현되어 있다고 가정합니다.

또한 TouchDelegate는 부모 View의 터치 처리 범위 안에서 동작하므로 부모의 크기와 다른 클릭 요소와의 충돌을 고려해야 합니다.

가능하면 먼저 레이아웃 자체에서 충분한 터치 영역을 확보하는 것이 좋습니다.


22. 색상만으로 상태를 표현하지 않기

좋아요 버튼이 선택되었을 때 빨간색으로 바뀐다고 가정하겠습니다.

선택 안 됨 → 회색 하트
선택됨    → 빨간색 하트

하지만 색상 구분이 어려운 사용자는 상태를 이해하기 어려울 수 있습니다.

따라서 다음과 같은 요소를 함께 활용하는 것이 좋습니다.

  • 채워진 하트와 빈 하트의 모양 차이

  • 상태 설명

  • 선택 여부를 나타내는 텍스트

  • 충분한 색상 대비

색상은 정보를 보조하는 수단으로 사용하고, 유일한 정보 전달 수단으로 사용하지 않는 것이 좋습니다.

23. 텍스트 색상 대비

WCAG의 일반적인 AA 기준에서는 다음 대비 비율을 참고할 수 있습니다.

구분권장 최소 대비
일반 텍스트4.5:1
큰 텍스트3:1
의미 있는 UI 구성 요소3:1

큰 텍스트의 기준은 WCAG에서 별도로 정의합니다.

또한 실제 접근성은 색상 대비만으로 결정되지 않습니다.

글자 크기, 글꼴 두께, 배경 이미지, 터치 영역 등을 함께 고려해야 합니다.


입력 화면 접근성

24. EditText에는 명확한 레이블 제공

예를 들어 조류 검색 화면의 입력창이 있다고 가정하겠습니다.

<EditText
    android:id="@+id/editSearch"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:hint="@string/search_bird_hint"
    android:inputType="text"
    android:imeOptions="actionSearch" />
<string name="search_bird_hint">새 이름을 입력하세요</string>

힌트는 입력 예시를 제공하는 데 유용합니다.

하지만 입력값이 채워졌을 때도 필드의 의미가 명확해야 하는 화면에서는 별도의 레이블을 사용하는 것이 좋습니다.

25. labelFor 사용하기

<TextView
    android:id="@+id/labelBirdName"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/bird_name_label"
    android:labelFor="@id/editBirdName" />

<EditText
    android:id="@+id/editBirdName"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:inputType="text" />
<string name="bird_name_label">새 이름</string>

labelFor는 레이블과 입력 요소 사이의 관계를 접근성 서비스에 전달하는 데 도움이 됩니다.


26. 입력 오류를 명확하게 안내하기

사용자가 필수 입력값을 누락했다고 가정하겠습니다.

단순히 입력창 테두리를 빨간색으로 바꾸는 것만으로는 충분하지 않습니다.

if (birdName.isBlank()) {

    binding.editBirdName.error =
        getString(
            R.string.error_bird_name_required
        )

    binding.editBirdName.requestFocus()

    return
}
<string name="error_bird_name_required">새 이름을 입력해 주세요.</string>

이렇게 하면 오류 내용을 텍스트로 제공할 수 있습니다.

다만 오류 안내가 실제 TalkBack에서 어떻게 읽히는지는 별도로 테스트해야 합니다.


상태 변경 알림

27. 접근성 Live Region

화면 일부가 동적으로 변경될 때 접근성 서비스에 해당 변경을 알리고 싶을 수 있습니다.

예를 들어 검색 결과 수가 변경되는 경우입니다.

<TextView
    android:id="@+id/textResultCount"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:accessibilityLiveRegion="polite" />
binding.textResultCount.text =
    getString(
        R.string.search_result_count,
        resultCount
    )
<string name="search_result_count">검색 결과 %1$d건</string>

polite는 접근성 서비스가 적절한 시점에 변경 내용을 알릴 수 있도록 합니다.

다만 검색어를 입력할 때마다 지나치게 많은 변경을 발생시키면 사용자가 불편할 수 있습니다.

검색 결과가 안정된 시점에 안내하도록 조정하는 것이 좋습니다.


다이얼로그 접근성

28. AlertDialog의 제목과 버튼

다이얼로그에는 사용자가 무엇을 결정해야 하는지 명확하게 표시해야 합니다.

MaterialAlertDialogBuilder(requireContext())
    .setTitle("메모 삭제")
    .setMessage(
        "선택한 메모를 삭제하시겠습니까?"
    )
    .setNegativeButton(
        "취소",
        null
    )
    .setPositiveButton(
        "삭제"
    ) { _, _ ->

        deleteMemo()
    }
    .show()

버튼 이름을 단순히 '확인'으로 작성하기보다 '삭제'처럼 실제 동작을 설명하는 것이 좋습니다.

또한 다이얼로그가 표시되었을 때 TalkBack 포커스가 적절한 위치로 이동하는지 확인해야 합니다.


WebView 접근성

29. WebView에서도 접근성이 중요하다

WebView를 사용하는 앱이라면 Android XML뿐 아니라 웹페이지의 HTML 접근성도 고려해야 합니다.

예를 들어 웹페이지의 이미지에는 적절한 alt 속성을 제공해야 합니다.

<img
  src="sparrow.jpg"
  alt="나뭇가지에 앉아 있는 참새">

버튼에는 명확한 이름을 사용해야 합니다.

<button type="button">
  검색
</button>

또한 HTML 제목 구조, 입력 레이블, 키보드 조작, 포커스 순서 등을 확인해야 합니다.

Android 앱에서 WebView를 사용한다고 해서 웹페이지의 접근성 문제가 자동으로 해결되지는 않습니다.


접근성 자동화 테스트

30. Espresso 접근성 검사

Android UI 테스트에서는 Espresso 접근성 검사 기능을 사용할 수 있습니다.

테스트 의존성을 추가합니다.

androidTestImplementation(
    "androidx.test.espresso:espresso-accessibility:<version>"
)

테스트 코드에서 다음과 같이 활성화할 수 있습니다.

import androidx.test.espresso.accessibility.AccessibilityChecks

@Before
fun setup() {

    AccessibilityChecks.enable()
}

접근성 검사는 UI 테스트 중 특정 접근성 문제를 감지하는 데 도움을 줍니다.

하지만 자동화 검사만으로 모든 접근성 문제가 발견되는 것은 아닙니다.

특히 실제 음성 안내의 자연스러움이나 복잡한 화면의 탐색 경험은 수동 테스트가 필요합니다.


31. TalkBack 수동 테스트

TalkBack을 활성화한 상태에서 다음 항목을 확인합니다.

  1. 앱 첫 화면에서 포커스가 올바르게 이동하는지

  2. 아이콘 버튼의 기능이 읽히는지

  3. 좋아요 상태가 전달되는지

  4. RecyclerView 항목 탐색이 자연스러운지

  5. 상세 화면으로 이동한 뒤 제목을 파악할 수 있는지

  6. 이미지 갤러리의 순서가 전달되는지

  7. 다이얼로그의 제목과 버튼이 읽히는지

  8. 입력 오류가 안내되는지

  9. 하단 내비게이션의 선택 상태를 알 수 있는지

  10. 뒤로 가기 이후 포커스가 적절한 위치로 돌아오는지

가능하면 실제 스마트폰에서 TalkBack을 켜고 앱을 처음 사용하는 사람의 입장에서 테스트하는 것이 좋습니다.


32. Accessibility Scanner 활용

Google의 Accessibility Scanner는 Android 화면의 일부 접근성 문제를 확인하는 데 사용할 수 있는 도구입니다.

대표적으로 다음 항목을 점검하는 데 도움이 됩니다.

  • 터치 영역 크기

  • 텍스트 레이블

  • 이미지 설명

  • 색상 대비

하지만 Scanner가 모든 문제를 발견하는 것은 아닙니다.

특히 화면 간 이동 흐름, 동적 상태 변경, TalkBack 음성 안내의 자연스러움은 별도로 확인해야 합니다.


33. 글자 크기 200% 테스트

접근성 테스트에서는 글자 크기를 크게 설정한 상태도 확인하는 것이 좋습니다.

특히 다음 화면에서 문제가 발생하기 쉽습니다.

  • BottomNavigationView

  • 고정 높이 버튼

  • 가로로 배치된 입력창

  • RecyclerView 카드

  • Dialog

  • 날짜 선택 화면

  • 긴 설명이 포함된 상세 화면

텍스트가 잘리는 경우에는 다음 항목을 확인합니다.

  • layout_height 고정 여부

  • wrap_content 사용 여부

  • 부모 레이아웃의 제약

  • 스크롤 가능 여부

  • maxLines 설정

  • ellipsize 설정

특히 중요한 정보가 ellipsize로 잘려 사라지지 않도록 주의해야 합니다.


34. Switch Access와 키보드 조작

접근성은 TalkBack만을 의미하지 않습니다.

Switch Access나 외부 키보드를 사용하는 사용자도 고려해야 합니다.

예를 들어 다음 사항을 확인합니다.

  • 버튼을 순서대로 탐색할 수 있는지

  • 포커스 위치가 시각적으로 구분되는지

  • 모든 주요 기능을 터치 없이 사용할 수 있는지

  • 화면 이동 후 포커스가 예상 가능한 위치에 있는지

특히 커스텀 View나 복잡한 터치 제스처를 사용하는 앱에서는 별도의 대체 조작 방법이 필요할 수 있습니다.


실무 적용 전략

35. 기존 앱의 접근성을 한 번에 개선해야 할까?

이미 출시한 앱이라면 모든 화면을 한 번에 수정하기 어려울 수 있습니다.

이 경우 사용 빈도가 높은 화면부터 개선하는 것이 좋습니다.

예를 들어 조류 도감 앱이라면 다음 순서로 접근할 수 있습니다.

우선순위화면주요 점검 항목
1메인 화면하단 메뉴, 탐색 순서
2검색 화면입력 레이블, 결과 안내
3목록 화면카드 탐색, 좋아요 버튼
4상세 화면이미지 설명, 제목 구조
5메모 화면입력 오류, 저장·삭제
6설정 화면스위치 상태, 글자 크기

기능별로 개선하고 실제 기기에서 검증하는 방식이 효율적입니다.


36. 접근성 문자열을 별도 관리하기

접근성 설명도 일반 문자열과 마찬가지로 strings.xml에서 관리하는 것이 좋습니다.

<resources>

    <string name="favorite_add">좋아요 추가</string>

    <string name="favorite_remove">좋아요 해제</string>

    <string name="search">검색</string>

    <string name="bird_photo_description">%1$s 사진</string>

    <string name="search_result_count">검색 결과 %1$d건</string>

</resources>

이렇게 하면 다국어 지원과 유지보수가 쉬워집니다.

Kotlin 코드에 한국어 문자열을 직접 하드코딩하는 방식은 가급적 피하는 것이 좋습니다.


37. 접근성 개선 전후 비교

예를 들어 좋아요 버튼을 생각해 보겠습니다.

개선 전

<ImageButton
    android:layout_width="32dp"
    android:layout_height="32dp"
    android:src="@drawable/ic_favorite" />

문제점은 다음과 같습니다.

  • 버튼 기능 설명 없음

  • 터치 영역이 작음

  • 현재 좋아요 상태 전달 부족

개선 후

<ImageButton
    android:id="@+id/buttonFavorite"
    android:layout_width="48dp"
    android:layout_height="48dp"
    android:src="@drawable/ic_favorite"
    android:contentDescription="@string/favorite_add" />

그리고 Kotlin에서 상태를 업데이트합니다.

ViewCompat.setStateDescription(
    binding.buttonFavorite,
    getString(
        if (isFavorite) {
            R.string.state_selected
        } else {
            R.string.state_not_selected
        }
    )
)

이렇게 하면 버튼의 기능, 터치 영역, 상태 전달을 함께 개선할 수 있습니다.


38. Android 접근성 체크리스트

  • 아이콘 버튼에 의미 있는 설명 제공

  • 장식용 이미지의 불필요한 접근성 탐색 제거

  • 중요한 이미지에 적절한 설명 제공

  • 중복된 음성 안내 방지

  • 동적 버튼 상태 전달

  • RecyclerView 탐색 순서 확인

  • 카드 클릭과 내부 버튼 조작 구분

  • BottomNavigationView 메뉴 이름 확인

  • ViewPager2 이미지 순서 안내

  • 텍스트 크기에 sp 사용

  • 글자 크기 확대 시 레이아웃 확인

  • 고정 높이로 인한 텍스트 잘림 방지

  • 충분한 터치 영역 확보

  • 색상만으로 상태를 표현하지 않음

  • 텍스트와 배경의 대비 확인

  • 입력 필드에 명확한 레이블 제공

  • 입력 오류를 텍스트로 안내

  • 동적 상태 안내가 과도하지 않은지 확인

  • 다이얼로그 제목과 버튼 의미 확인

  • WebView 내부 HTML 접근성 점검

  • Espresso 접근성 검사 적용

  • Accessibility Scanner 활용

  • TalkBack 수동 테스트

  • 글자 크기 확대 테스트

  • Switch Access 및 키보드 탐색 테스트


39. 마무리

Android 접근성은 특정 사용자만을 위한 부가 기능이 아닙니다.

앱의 정보를 명확하게 전달하고 다양한 입력 방식에 대응하며, 누구나 주요 기능을 사용할 수 있도록 만드는 기본적인 품질 요소입니다.

특히 Kotlin + XML 기반 앱에서는 비교적 작은 수정으로도 접근성을 개선할 수 있습니다.

예를 들어 다음과 같은 작업입니다.

  1. 아이콘 버튼에 적절한 contentDescription을 추가합니다.

  2. 좋아요와 같은 동적 상태를 명확하게 전달합니다.

  3. RecyclerView의 탐색 순서를 확인합니다.

  4. 텍스트에는 sp를 사용합니다.

  5. 글자 크기를 확대해도 레이아웃이 깨지지 않도록 합니다.

  6. 터치 영역을 충분히 확보합니다.

  7. 색상 외의 방법으로 상태를 표현합니다.

  8. 입력 오류를 텍스트로 안내합니다.

  9. 자동화 검사와 TalkBack 테스트를 병행합니다.

  10. 실제 기기에서 사용 흐름을 검증합니다.

접근성을 개선할 때 가장 중요한 것은 코드에 속성을 추가하는 것 자체가 아닙니다.

사용자가 화면을 보지 않거나 일반적인 터치 조작을 하지 않더라도 앱의 주요 기능을 이해하고 사용할 수 있는지 확인하는 것입니다.

이러한 관점에서 접근성을 점검하면 앱의 완성도와 사용성을 함께 높일 수 있습니다.


참고 자료

태그: Android, Kotlin, Accessibility, TalkBack, contentDescription, RecyclerView, ViewPager2, BottomNavigationView, XML, Android 접근성, 글자 크기, 터치 영역, 색상 대비, Espresso, Accessibility Scanner, Android 앱 개발