Android

안드로이드 접근성 - 대체 텍스트를 적용해보자!

MJ핫산 2022. 11. 22. 22:46

📱 접근성이란

모바일 접근성이란 웹 접근성과 같은 개념으로 장애를 가진 사람이든 일반인이든 구애를 받지 않고 동등하게 정보에 접근할 수 있도록 설계하고 디자인/개발하는 것을 의미한다. 즉, 모두에게 유용하게 사용되는 앱이 되기 위해서는 접근성 처리가 필요하다.

 

안드로이드에서는 “TalkBack”, “시인성 향상”, “청각보조” 등 다양한 방법으로 접근성을 제공하고 있는데

이번 글에서는 그 중에서도 대체 텍스트(talkback)를 통해 음성 피드백을 제공할 수 있는 방법을 알아보겠다. ꈍꈊꈍ

 

🗣 TalkBack이란?

TalkBack은 Android 기기에 포함된 Google 스크린 리더입니다. TalkBack을 사용하면 기기를 보지 않고도 제어할 수 있습니다.
출처: https://support.google.com/accessibility/android/answer/6283677?hl=ko

즉, 시력이 좋지 않은 사람들을 위해 글자나 화면을 음성으로 설명해주는 기능이다.

TalkBack(스크린 리더)은 기본적인 텍스트는 읽지만 이미지, 토글 등의 유틸을 읽을 수는 없다. 그래서 이런 유틸들에 대하여 대체 텍스트를 지정해줘야 한다.

 

🧚‍♀️ 대체 텍스트를 지정해보자!

contentDescription

contentDescription을 사용하면 ImageView, ImageButton, CheckBox 또는 시각적으로 정보를 전달하는 View를 사용할 때 해당 View에 대한 설명을 제공할 수 있다.

 

예를 들어 현재 내 위치로 이동하는 ImageButton(GPS 아이콘)이 있다면, “현재 내 위치로 이동” 이런 식으로 contentDescription을 설정할 수 있다. 그러면 버튼이기 때문에 talkback은 “현재 내 위치로 이동, 버튼, 활성화 하려면 두 번 탭하세요.”라고 읽어주게 된다.

깨알 Tips

  • Button과 ClickListener가 붙은 ImageView에는 시스템에서 자동적으로 “${contentDescription}, 버튼”이라고 읽어준다.
  • 그 외 View에서 액션이 이루어지는 상황이라면 명시적으로 “버튼”이나 액션을 설명하는 문구를 추가할 수 있다.
  • 안드로이드 API 22부터는 AccessibilityNodeInfoCompat을 통해 “활성화하려면 두 번 탭하세요” 같은 추가 설명을 쉽게 전달할 수도 있다.

 

텍스트를 읽게 하고싶지 않다면

importantForAccessibility=”no”를 적용하면 talkback이 해당 뷰를 읽지 않는다.

만약 읽고 싶다면 importantForAccessibility=”yes”를 사용하면 된다.

 

특정 레이아웃들을 한 번에 묶어서 읽고자 한다면

따로 그린 View를 한 번에 읽도록 하고 싶다면 그 View들을 별도의 레이아웃으로 감싸줘야 한다.

예를 들어서 [시민들이 추천한 장소][n] 이렇게 두개의 TextView로 구성된 ui인데 한 번에 읽고 싶다면 [[시민들이 추천한 장소][n]] 이렇게 감싸준 후 바깥의 레이아웃에 importantForAccessibility=”yes”(또는 focusable=”true”)와 contentDescription을 지정해주면 된다.

// <string name="map_place_count_description">시민들이 추천한 장소 %s개</string>

<androidx.constraintlayout.widget.ConstraintLayout
	android:id="@+id/placeCountLayout"
	android:layout_height="wrap_content"
	android:layout_width="wrap_content"
	android:focusable="true"
	android:contentDescription="@{@string/map_place_count_description(item.placeCount)}">

		<TextView
			text="시민들이 추천한 장소"
			...

		<TextView
			text="@{item.placeCount}"
			...

</androidx.constraintlayout.widget.ConstraintLayout>

 

포커스를 이동/해제해야 한다면

// 포커스 이동(선택)
binding.____.performAccessibilityAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null)

// 포커스 제거
binding.____.performAccessibilityAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null) 

 

AccessibilityEvent를 주고 싶다면

이런 상황이 생길 수 있다.

사용자가 글을 쓰다가 글자 수 제한을 넘겼을 경우, 시력이 잘 보이는 사용자에게는 ui로 “100자 이하로 작성해주세요.”라고 안내할 수 있지만 talkback을 사용하는 사람에게는 음성으로 안내를 해줘야 한다.

 

그럴 때 쓸 수 있는 방법이 sendAccessibilityEvent이다.

아래처럼 사용할 수 있는데 text length를 체크하다가 sendAccessibilityEvent(”100자 이하로 작성해주세요.”) 이렇게 실행하면 talkback음성이 메시지를 읽어준다.

fun sendAccessibilityEvent(message: String) {
        val event = AccessibilityEvent.obtain()
        event.eventType = AccessibilityEvent.TYPE_ANNOUNCEMENT
        event.className = this.javaClass.name
        event.packageName = this.packageName
        event.text.add(message)

        try {
            val manager: AccessibilityManager = getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
            manager.sendAccessibilityEvent(event)
        } catch (e: Exception) {
						...
        }
    }

 

컨텐츠를 읽어주는 순서를 변경하고 싶다면

 

talkback은 기본적으로 위→아래, 왼쪽→오른쪽 순서로 전개되지만 기획상의 이유나, 사정으로 순서를 변경하고 싶어질 수도 있다.

이럴 때 사용할 수 있는 방법이 "android:accessibilityTraversalAfter"과 "android:accessibilityTraversalBefore"이다.

  • android:accessibilityTraversalAfter = @id/___ : 이 속성이 적용된 레이아웃은 지정한 id 다음에 읽어짐
  • android:accessibilityTraversalBefore = @id/___ : 이 속성이 적용된 레이아웃은 지정한 id 전에 읽어짐

출처:&nbsp;https://nuli.navercorp.com/community/article/1133154

 

 

만약 1 - 2 - 3 - 4 - 5 순서대로 읽어지는 ui라고 했을 때
binding.second.accessibilityTraversalAfter = binding.third.id

이렇게 설정하면 2가 3 후(after)에 실행된다.

 

즉 순서가 1 - 3 - 2 - 4 - 5 이렇게 실행된다.

binding.third.accessibilityTraversalBefore = binding.second.id

이렇게 설정하면 3이 2 전에 실행되기 때문에 똑같이 1 - 3 - 2 - 4 - 5 순으로 실행된다.

 

 

android:accessibilityTraversalAfter과 android:accessibilityTraversalBefore의 문제적 상황…

accessibilityTraversalAfter와 accessibilityTraversalBefore에 대상을 id로 지정하는데 리사이클러뷰 안에 있는 ViewHolder 같은 경우는 뷰의 아이디가 같을 수도 있어서, 이럴 경우 내 의도대로 동작하지 않는 상황이 생길 수도 있다.

이럴 때는 id가 아닌 View 자체를 대상으로 지정할 수 있다.

setAccessibilityTravelBefore(binding.cardArea, binding.deleteButton)

ViewCompat.setAccessibilityDelegate(binding.cardArea, object: AccessibilityDelegateCompat() {
    override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfoCompat) {
        info.setTraversalBefore(binding.deleteButton)
        super.onInitializeAccessibilityNodeInfo(host, info)
    }
})

 

⚙️ talkback 작업하면서 유용/편리했던 설정들

1. 접근성 버튼 talkback으로 적용하기

  • 설정 > 접근성 > 고급설정 > 접근성버튼

2. 자막 켜기

  • 접근성 > talkback > 설정 > 고급설정 > 개발자 설정 > 음성출력표시

 

☕️ 마무리

.