본문 바로가기

Android

안드로이드 접근성 개선기

서론

 다온길 프로젝트는 무장애 여행을 제공하는 것을 목표로 몸이 불편하신 분들도 다온길을 통해 일반인들과 똑같이 여행할 수 있게 하는것을 목표로 하고있습니다. 이를 위한 가장 중요한 요소는 바로 접근성입니다. 오늘은 안드로이드에서 제공하는 다양한 접근성 도구들과 이를 활용해 저희 프로젝트의 접근성을 어떻게 향상시켰는지 알아보겠습니다.

접근성 검사기를 사용해보자


 

접근성 검사기는 어플리케이션의 화면을 스캔하고 앱의 접근성을 개선하기 위한 제안을 제공하는 앱입니다. 콘텐츠 레이블, 클릭 가능한 항목, 대비 등을 검사하여 구체적인 개선 방안을 제시합니다.

 

가장 먼저 접근성 검사기를 실행하면 툴바 메뉴에 설정에서 접근성 검사에 사용될 텍스트와 이미지의 대비율, 터치할 대상 크기를 설정할 수 있습니다. 기존에 설정되어 있는 값들은 모두 안드로이드 접근성 가이드 라인에서 제시한 기준값입니다.

안드로이드 접근성 가이드라인

 

안드로이드 접근성 가이드 라인은 크게 네 가지로 분류됩니다.

  • Increase text visivility (텍스트 가시성 향상)
  • Use Large, simple controls (더 큰 더치영역 사용)
  • Describe each UI Element(UI 요소 설명)
  • 기타 개선 사항

1. Increase text visibility (텍스트 가시성 향상)

텍스트 가시성을 향상 시키키 위해선 색상 대비를 높여야합니다. 색상 대비란, 텍스트 색상과 배경 색상 간의 밝기 차이를 수치로 나타낸것입 니다. 색상 대비는 사용자가 얼마나 쉽게 읽고 이해할 수 있는지에 영향을 줍니다. 또한 코드를 통해 사용하려는 색상의 대비를 계산할 수 도 있습니다.

val foregroundColor = Color.parseColor("#88898C")
val backgroundColor = Color.parseColor("#727272")
val contrast = ColorUtils.calculateContrast(foregroundColor, backgroundColor)



 안드로이드 접근성 고객센터 에선  이를 위해 두 가지 선택 사항을 권장합니다.  

 

색상 대비 - Android 접근성 고객센터

앱 인터페이스에 적용하기 위해 선택한 색상은 사용자가 얼마나 쉽게 읽고 이해할 수 있는지에 영향을 줍니다. 충분한 색상 대비를 사용하면 텍스트와 이미지를 더 쉽게 읽고 이해할 수 있습니

support.google.com

W3C 권장사항
작은 텍스트(18포인트 기본 또는 14포인트 굵게 이하)의 경우 최소 4.5:1
큰 텍스트(18포인트 기본 이상 또는 14포인트 굵게 이상)의 경우 최소 3.0:1.

 

WC3 가이드 라인에 의하면  글자 획이 크고 넓은 텍스트는 오히려 대비가 낮을 때 읽기 더 쉽고 18포인트에서 14포인트의 굵은 텍스트는 더 낮은 대비율이 필요할 만큼 충분하다고 판단된다고 합니다.

 

이를 안드로이드에 대입하면 18sp 이상일 경우 3:1 대비 이상

18sp이하의 볼드 처리되지 않은 텍스트의 경우 4.5 : 1 대비 이상을 권장합니다. 

 

저희 프로젝트에선 앱 초기 설계 단계에서부터 저시력자들을 위해 기존 디자인에서 색상을 반전시킨 고대비 모드를 설계했습니다. 그래서 기존 디자인에 대해선 접근성에 대한 걱정을 하지 않습니다. 하지만 접근성에 대해 공부하면서 기존 디자인에서도 몇 가지 문제점을 발견했습니다. 문제점을 살펴보기에 앞서 먼저 접근성 검사기를 사용해 기존 화면에 대한 접근성을 검사하는 법과 두 번째 가이드라인인 더 큰 터치 영역 사용에 대해 알아보겠습니다.

접근성 검사기 사용법

접근성 검사기 어플리케이션에서 초기 화면에 플로팅 버튼을 클릭하면 다양한 접근성을 검사할 수 있는 확면으로 이동합니다. 이 중 가장 하단의 설치된 서비스를 클릭하면 접근성 검사기를 활성화 할 수 있습니다.

 

접근성 검사기를 활성화하면 핸드폰 상단에 버튼이 활성화됩니다. 이 버튼을 클릭 후 스냅샷을 선택후 개발중인 어플리케이션에서 접근성을 검사하고 싶은 화면을 열면 자동으로 캡쳐하고 캡쳐된 이미지를 검사해 접근성 검사기 앱에서 분석 내용을 확인할 수 있습니다.

 

2. Use Large, simple controls (더 큰 더치영역 사용)

 

안드로이선 기본적으로 터치 요소는 minWidth, minHeight를 48dp로 할 것을 권장하고 있습니다. 48dp는 가로 세로 패딩을 포함한 사이즈며 아이콘 같은 요소는 24x24dp로 표시될 수 있으나 요소 주변의 패딩은 48x48dp로, 터치 영역 전체를 포함합니다. 또한 뷰의 사이즈를 변경해서 터치 영역을 개선할 때 커스텀 뷰의 사이즈 직접 조절이 어려운 경우 코드를 통해 직접 터치 영역을 조작할 수 있습니다.

private fun View.expandTouchArea(size: Int){
    // 1. 부모 뷰를 가져온다
    val parent = this.parent as? View

    parent?.touchDelegate = TouchDelegate(
        Rect().apply {
            // 2. 현재 뷰의 터치 영역을 가져온다.
            getHitRect(this)
            // 3. 터치 영역을 확장한다.
            inset(size, size)
        },
        this
    )
}

 

위와 같이 버튼의 사이즈를 48dp 이하로 설정할 경우 IDE에서 터치 타겟의 사이즈가 너무 작다는 Warning을 발생시키며 48dp 이상으로 사이즈를 키울 것을 권장하고 있습니다.

 

이 때 버튼의 다른 속성은 그대로 유지하고 버튼의 위치만 화면 가장자리로 이동해보겠습니다. 어라 ? 분명 가이드라인에선 48dp를 권장한다고 했는데 왜 갑자기 32dp 이상을 권장할까요 ?

 

해당 코드는 구글의 Accessbility 코드입니다. 해당 코드를 보면 기본적으로 권장하는 버튼의 최소/최대 크기는 48dp입니다.

출처 : https://speakerdeck.com/nanamare/droid-knights-2024-accessibility-in-android-ed460750-9a06-4e7e-b156-5decf7596817?slide=32

 

바로 아래의 ON_EDGE가 하이라이팅된 영역을 보시면 32dp로 선언되어있습니다. 이는 버튼이 화면의 가장자리에 있을 경우 터치 가능한 영역이 제한적이기 때문에 32dp를 권장하는 것입니다.

출처 : https://speakerdeck.com/nanamare/droid-knights-2024-accessibility-in-android-ed460750-9a06-4e7e-b156-5decf7596817?slide=32

 

이번엔 동일한 조건에서 위 아래 마진을 20dp씩 주었습니다. 이 경우, 가장자리에서 벗어나 마진 만큼의 터치 가능한 영역이 늘어나기 때문에 48dp를 권장하는 것을 볼 수 있습니다.

 

기존 프로젝트에서 버튼이 있는 화면에서 접근성 검사기로 검사를 실행해봤습니다. 해당 화면에선 두 가지 개선사항이 필요했습니다. 다음과 같이 버튼 크기가 48dp 미만이기 때문에 터치 영역을 늘리고 불필요한 contentDescription을 제거할 것을 권장했습니다. contentDescription에 대한 내용은 뒤에서 더 알아보겠습니다.

 

 

해당 권장 사항들을 적용하고 다시 접근성 검사기로 검사해보니 아까 봤던 권장 사항들이 사라진 것을 볼 수 있습니다.

 

다음으로 알아볼 가이드 라인은 UI 요소 설명입니다.

2. UI 요소 설명

이미지같은 그래픽 요소들에는 컨텐츠 라벨이라는 요소가 제공됩니다. 컨텐츠 라벨은 화면에 보이는 UI를 설명하는 텍스트 입니다. 시각장애인 분들의 경우 이미지 보지 못하실 수 도 있기 때문에 TallBack을 사용해 화면에 보이는 UI 요소를 설명해주어야 합니다. 이 때 사용되는 컨텐츠 라벨은 contentDescription, hint, lableFor 등이 포함됩니다.

 

TallBack이란 Android 기기에서 제공하는 Google 스크린 리더입니다. 말 그대로 화면에 있는 요소들을 음성으로 읽어주는 역할을 합니다. TallBack은 설정 -> 접근성 또는 접근성 검사기에서 다음과 같이 설정할 수 있습니다.

 

TabllBack을 위한 설정들

1. ContentDescription

TextView 같은 경우엔 그 자체로 읽어줄 텍스트가 존재하지만 ImageView, ImageButton 같은 요소들은 시각장애인들을 위해 TallBack이 설명을 제공하도록 속성을 지정해줘야 합니다. 다음과 같이 android:contentDescription에 속성을 지정해주면 이미지를 터치했을 때 contentDescription을 읽어줍니다.

 

2. labelFor

아래와 같은 화면이 있을 때 tallback은 각각의 요소들을 설명할 때 각 View의 단일 요소만 읽어줍니다. 하지만 아래 그림 같이 생년월일, 혈액형 등등의 요소들은 각 요소의 이름만 선택했을 때 그에 해당하는 데이터도 같이 읽어주는 것이 더 좋은 접근성을 만들겠죠. 이 때 사용되는 속성이 바로 labelFor 입니다. 만약, 여러개의 View를 연속해서 읽고 싶다면 해당 View를 하나의 ViewGroup으로 묶어야 하지만 지금 처럼 단일 속성들은 간단하게 labelFor 속성을 사용해 지정할 수 있습니다.



 

 

이 경우 19990908을 클릭 했을 때 생년월일을 같이 읽어 결과적으로 "생년월일 19990908"을 읽게 됩니다. 

 

반대로 생년월일을 선택했을 때 생년월일 숫자를 읽게 하기위해 lableFor 속성을 반대로 지정할 수 도 있습니다. 하지만 저는 이 부분을 활용할 수 없었습니다. labelFor 속성은 XML 레이아웃 상에서 지정된 텍스트만 읽어줍니다.

 

현재 사용자의 정보는 서버에서 받아오는 데이터입니다. labelFor는 동적으로 변경되는 텍스트는 읽어주지 않습니다. 즉, 서버에서 받아온 데이터를 텍스트 뷰에 표시하더라도 android:labelFor 속성은 XML에 정의된 텍스트만 읽어줍니다. 이 문제를 해결하기 위해 저는두 가지 과정을 거쳐 항목의 타이틀을 선택했을 때 TallBack에서 읽어줄 텍스트를 직접 지정해주었습니다.

 

첫 번째로 TallBack이 활성화 되었는지에 따라 로직을 분기하기 위해 TallBack의 활성화 여부를 반환하는 확장 함수를 만들었습니다.

/**
 * TalkBack이 활성화되어 있는지 확인합니다.
 *
 * @return TalkBack이 활성화되어 있으면 true, 그렇지 않으면 false
 */
fun Context.isTalkbackEnabled(): Boolean {
    // 접근성 서비스 관리자
    val accessibilityManager = getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
    // 터치 탐색 기능 활성화 여부 반환 (TalkBack 활성화 시 터치 탐색 기능도 활성화됨)
    return accessibilityManager.isTouchExplorationEnabled
}

 

두 번째로 AccessibilityDelegate를 사용해 원하는 텍스트를 읽도록 커스텀해줍니다. AccessibilityDelegate는 뷰의 접근성 관련 동작을 커스터마이징할 수 있도록 하는 클래스로 AccessibilityNodeInfo는 뷰의 접근성 정보를 담고 있는 객체입니다. 이 객체에 접근해 원하는 텍스트를 읽도록 할 수 있습니다. 

 

이 때, hintText를 null로 만드는 이유는 TextView에 Hint가 설정되어 있을 경우 우리가 읽고자 하는 새로운 텍스트를 읽은 후 힌트도 같이 읽어주기 때문에 이러한 TallBack의 기본 동작을 막기 위함입니다.

/**
 * TextView의 접근성 텍스트를 설정합니다.
 *
 * @param newText 접근성 텍스트로 설정할 새로운 텍스트
 */
fun TextView.setAccessibilityText(newText: CharSequence) {
    // 접근성 위임 객체 설정
    accessibilityDelegate = object : View.AccessibilityDelegate() {
        /**
         * 접근성 노드 정보를 초기화합니다.
         * 접근성 서비스가 뷰에 대한 정보를 요청할 때 호출됩니다.
         *
         * @param host 접근성 정보를 요청하는 뷰
         * @param info 접근성 노드 정보 객체
         */
        override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo) {
            super.onInitializeAccessibilityNodeInfo(host, info)
            // 호스트가 TextView인 경우 접근성 텍스트 설정
            if (host is TextView) {
                info.hintText = null
                info.text = newText
            }
        }
    }
}

 

 

이제 이 코드를 Fragment에서 적용해보겠습니다. 아래와 같이 함수를 적용해주면 "닉네임" 을 보여주는 TextView를 클릭하면 TallBack은 서버에서 저장된 사용자 닉네임이 있다면 "닉네임 (사용자닉네임)"을 읽어주고 만약 없다면 "닉네임 닉네임을 입력해주세요"를 읽게 됩니다.

 

4. 기타 개선 사항( 더 나은 접근성을 위한 고민)

시각장애인 분들이 TallBack을 사용할 때 어떻게 하면 더 편하게 사용할까를 고민하면서 시각장애인분들이 실제로 스마트폰을 사용하는 방법을 찾아봤습니다. 아래 영상 링크에서 보시면 기본 동작은 왼쪽에서 오른쪽으로 위에서 아래로 화면을 스와이프 할 때 마다 포커싱이 이동하며 화면의 구성요소들을 읽어줍니다. 

https://www.youtube.com/watch?v=qmGnWoo6q7U&t=234s

 

이 동작도 물론 TallBack이 기본적으로 제공하는 좋은 기능이지만 화면의 구성 요소가 많을 경우 화면 전체에 대한 구조를 파악하기 위해 매번 스와이프를 해야하고 이에 따른 피로도와 불편함이 많을 것이라고 생각했습니다. 그래서 화면에 대한 구성 요소를 한번에 파악하기 위한 방법으로 화면이 전환 되었을 때 화면에 대한 정보를 사용자에게 설명하도록 만들어보겠습니다.

/**
 * 주어진 텍스트를 접근성 서비스(예: TalkBack)를 통해 읽어줍니다.
 *
 * @param text 접근성 서비스를 통해 읽어줄 텍스트
 */
fun Context.announceForAccessibility(text: String) {
    val accessibilityManager = getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
    // 새로운 접근성 이벤트 객체 생성
    val event = AccessibilityEvent.obtain()

    // 접근성 서비스가 텍스트를 읽어주도록 하는 이벤트 유형
    event.eventType = AccessibilityEvent.TYPE_ANNOUNCEMENT

    // 이벤트의 클래스 이름을 TextView로 설정
    // 이는 이벤트가 TextView에서 발생한 것처럼 보이도록 하기 위함
    event.className = TextView::class.java.name
    event.packageName = packageName

    //이벤트의 텍스트 목록에 text를 추가
    event.text.add(text)

    // 접근성 서비스에 이벤트를 전달
    accessibilityManager.sendAccessibilityEvent(event)
}

 

announceForAccessibility 함수는 개발자가 원하는 텍스트를 읽어주도록 만들어주는 함수입니다. 이 함수를 활용해서 

화면이 전환되었을 때 TallBack이 활성화 되었을 때 화면에 대한 정보를 읽어주게 했습니다.

<string name="text_script_guide_for_my_info">
    나의정보화면입니다.
</string>
<string name="text_script_read_all_text">
    화면에 대한 설명을 듣고 싶으시다면 화면 최상단 오른쪽에 위치한 화면정보읽기버튼을 눌러주세요
</string>

 

그리고 이 화면에 대한 정보를 읽어주기 위한 ToolBar Menu에 두 가지 메뉴를 만들고 각 메뉴 버튼을 클릭했을 때 화면에 대한 정보와 저장된 개인 정보를 읽게 만들었습니다.

 

결과

https://youtu.be/oYFIWzqoDro