[Android View Accessibility] 목록 총 항목 수 및 현재 항목 순서 정보 전달하기
Android는 목록 내 접근할 때 뿐만 아니라, 각 항목마다 목록 내 항목이 총 몇 개인지, 현재 몇번째 항목에 사용자가 초점을 보냈는지 알려줍니다. 데이터를 주로 담는 목록으로는 (구)ListView와 RecyclerView 등이 있으며, TabLayout또한 이에 해당합니다.
그런데, 단순히 LinearLayout같은 레이아웃 요소로 나열하게 된다면, TalkBack 사용자는 위와 같은 유용항 정보를 듣지 못하게 됩니다.
눈으로 보는 사람도 어처피 순서나 개수는 세야 하는데, 뭐가 문제냐고요? 역차별 아니냐고 생각하실 수 있겠는데요. 맞는 말씀이십니다.
…그런데, 그래도 줬다 뺐는건 기분이(?)가 나쁘잖아요 :) 그래서, LinearLayout을 Kotlin으로 가져와서 AccessibilityNodeInfo를 통해 CollectionInfo와 CollectionItemInfo를 수정하는 방법을 소개해볼까 해요.
목록 컨테이너 구현하기
먼저, XML부터 만듭시다.
<!--activity_main.xml-->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World"
android:id="@+id/hw_text"
android:layout_marginEnd="10sp"
android:padding="4sp"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="안녕 세계"
android:id="@+id/hw_text_ko"
android:layout_marginEnd="10sp"
android:padding="4sp"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="こんにちは、世界"
android:id="@+id/hw_text_ja"
android:layout_marginEnd="10sp"
android:padding="4sp"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hallo Welt"
android:id="@+id/hw_text_de"
android:padding="4sp"
/>
</LinearLayout>
이제, 이 LinearLayout에 들어있는 요소에 접근했을 때, 목록 정보를 전달받을 수 있도록 해보자고요.
// MainActivity.kt (1), import문 생략
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val helloWorldLayout:LinearLayout = findViewById(R.id.main)
helloWorldLayout.accessibilityDelegate = object : View.AccessibilityDelegate() {
override fun onInitializeAccessibilityNodeInfo(
host: View,
info: AccessibilityNodeInfo
) {
super.onInitializeAccessibilityNodeInfo(host, info) // (1)
val infoCompat = AccessibilityNodeInfoCompat.wrap(info) // (3)
val collectionInfo: CollectionInfoCompat = CollectionInfoCompat.obtain( // (3)
helloWorldLayout.childCount, // (3-1)
1, //(3-2)
false // (3-3)
)
infoCompat.setCollectionInfo(collectionInfo) // (4)
}
helloWorldLayout.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES // (5)
//...
}
}
}
자, 우선 목록 컨테이너가 될 LinearLayout부터 손봅시다.
-
LinearLayout을 불러와서 accessibilityDelegate를 초기화하고, onInitializeAccessibilityNodeInfo를 override합니다.
-
범용적으로 구형 디바이스또한 목록 정보를 주기 위해서는 일반적인 AccessibilityNodeInfo 대신 AccessibilityNodeInfoCompat을 사용해야 합니다. AccessibilityNodeInfoCompat.wrap(info)를 할당한 infoCompat 상수를 만들어줍시다.
-
CollectionInfoCompat을 할당한 collectionInfo 상수를 선언합니다. CollectionInfoCompat과 뒤에 나올 CollectionItemInfoCompat은 obtain으로 초기화합니다. CollectionInfoCompat.obtain에는 3개의 필수 매개변수가 필요합니다.
-
첫번째 매개변수는 총 행 개수 Int 입니다. 세로 레이아웃이면 이곳에 항목 개수를 넣습니다. 가로 레이아웃이면 1입니다.
-
두번째 매개변수는 총 열 개수 Int 입니다. 가로 레이아웃이면 이곳에 항목 개수를 넣습니다. 세로 레이아웃이면 1입니다.
-
세번째 매개변수는 계층구조가 나눠져있는지를 설정하는 Boolean입니다.
-
또한 Tab 요소와 같이 선택 가능한 요소인 경우 네번쨰 매개변수인 selectionMode를 설정할 수 있습니다.
-
-
CollectionInfoCompat이 담긴 collectionInfo 상수를 infoCompat.setCollectionInfo로 전달합니다.
-
helloWorldLayout.importantForAccessibility를 View.IMPORTANT_FOR_ACCESSIBILITY_YES로 설정합니다. 이 부분을 빼먹어서는 안 됩니다. LinearLayout은 화면의 배치를 담당하는 요소이기 때문에 importantForAccessibility의 기본 상태가 IMPORTANT_FOR_ACCESSIBILITY_NO로 동작합니다.
목록 항목 구현하기
다음은, 목록 항목을 구현할 차래입니다. MainActivity에서 이어서 작업합니다.
// MainActivity.kt (2), import문 생략
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
//...이전 내용 생략
for( index in 0 until helloWorldLayout.childCount ) {
val child = helloWorldLayout.getChildAt(index)
child.accessibilityDelegate = object : View.AccessibilityDelegate() {
override fun onInitializeAccessibilityNodeInfo(
host: View,
info: AccessibilityNodeInfo
) {
super.onInitializeAccessibilityNodeInfo(host, info)
val infoCompat = AccessibilityNodeInfoCompat.wrap(info)
val collectionItemInfo: CollectionItemInfoCompat = CollectionItemInfoCompat
.obtain(
index,
1,
0,
1,
false,
false
)
infoCompat.setCollectionItemInfo(collectionItemInfo)
}
}
}
}
}
목록을 구현할때와 비슷하게 진행됩니다. 다른점은, CollectionInfo가 아닌 CollectionItemInfo를 설정해야 합니다.
CollectionItemInfo의 설정 방법또한 CollectionInfo와 거의 유사합니다. 역시나 obtain으로 초기화합니다. 매개변수는 다음과 같습니다.
- 첫번째 매개변수는 rowIndex입니다. 필수 매개변수이며 Int를 요구합니다. 세로 레이아웃이라면 여기에 index를 전달합니다. 가로 레이아웃이라면 0을 전달합니다.
- 두번째 매개변수는 rowSpan입니다. 필수 매개변수이며 Int를 요구합니다. 그리드에서 합쳐진 셀을 나타낼때 쓰는 것 같습니다. 1차원 목록에서는 1을 전달합니다.
- 세번째 매개변수는 columnIndex입니다. 필수 매개변수이며 Int를 요구합니다. 가로 레이아웃이라면 여기에 index를 넣
- 네번째 매개변수는 columnSpan입니다. 필수 매개변수이며 Int를 요구합니다. 마찬가지로 그리드에서 합쳐진 셀을 나타내는 데 씁니다. 1차원 목록에서는 1을 전달합니다.
- 다섯번째 매개변수는 heading입니다. 필수 매개변수이며 요구값은 Boolean입니다. 이 항목을 제목요소로 설정합니다.
- 여섯번쨰 매개변수는 selected입니다. 필수 매개변수가 아니기 때문에 생략할 수 있으며 요구값은 Boolean입니다. 이 항목이 선택됨으로 설정합니다. 범용적으로 사용되지 않으며 View 또는 AccessibilityNodeInfo의isSelected 코틀린 프로퍼티 또는 setSelected 자바 메소드를 쓰는 것이 더 범용적입니다. API 버전 33 이상에서 동작하지 않을 수 있습니다.
자, 이제, TalkBack이 목록정보를 출력할 것입니다.