기본적으로 Compose 앱의 접근성 스크린 리더 동작이 구현됩니다.
읽을 수 있습니다. 일반적으로 왼쪽에서 오른쪽 순으로 읽은 후 위에서 아래로 읽히도록 하겠습니다.
그러나 알고리즘이 결정할 수 없는 앱 레이아웃 유형도 있습니다.
읽기 순서를 변경할 수 있습니다. 뷰 기반 앱에서는 다음 작업을 할 수 있습니다.
traversalBefore
및 traversalAfter
속성을 사용하여 이러한 문제를 해결하세요.
Compose 1.5부터 Compose는 똑같이 유연한 API를 제공하지만
새로운 개념 모델입니다.
isTraversalGroup
및 traversalIndex
는 시맨틱 속성입니다.
사용하면
기본 정렬 알고리즘이 적절하지 않습니다. isTraversalGroup
는
의미론적으로 중요한 그룹인 반면, traversalIndex
는
개별 요소를 구별할 수 있어야 합니다. isTraversalGroup
만 단독으로 사용할 수 있습니다.
또는 traversalIndex
를 사용하여 추가 맞춤설정을 수행합니다.
다음에서 isTraversalGroup
및 traversalIndex
사용:
앱을 사용하여 스크린 리더의 순회 순서를 제어할 수 있습니다.
isTraversalGroup
를 사용하여 요소 그룹화
isTraversalGroup
는 시맨틱
순회 그룹입니다. 이 유형의 노드는
노드의 하위 요소를 구성할 때 경계 또는 경계로 사용됩니다.
노드에서 isTraversalGroup = true
를 설정하면 해당 노드의 모든 하위 요소가
다른 요소로 이동하기 전에 사용자가 방문했는지 확인합니다. 다음에서 isTraversalGroup
설정 가능:
스크린 리더가 아닌 포커스 가능 노드(예: 열, 행, 상자)
다음 예에서는 isTraversalGroup
를 사용합니다. 4개의 텍스트 요소를 내보냅니다. 이
왼쪽 두 요소는 하나의 CardBox
요소에 속하고, 오른쪽 두 요소는
다른 CardBox
요소에 속할 수 있습니다.
// CardBox() function takes in top and bottom sample text. @Composable fun CardBox( topSampleText: String, bottomSampleText: String, modifier: Modifier = Modifier ) { Box(modifier) { Column { Text(topSampleText) Text(bottomSampleText) } } } @Composable fun TraversalGroupDemo() { val topSampleText1 = "This sentence is in " val bottomSampleText1 = "the left column." val topSampleText2 = "This sentence is " val bottomSampleText2 = "on the right." Row { CardBox( topSampleText1, bottomSampleText1 ) CardBox( topSampleText2, bottomSampleText2 ) } }
이 코드는 다음과 비슷한 출력을 생성합니다.
의미 체계가 설정되지 않았으므로 스크린 리더의 기본 동작은 다음과 같습니다. 왼쪽에서 오른쪽으로, 위에서 아래로 요소를 순회합니다. 그렇기 때문에 기본적으로 TalkBack은 문장 조각을 잘못된 순서로 읽습니다.
'This sentence is in' → 'This sentence is' → '왼쪽 열' → 되었습니다."라고 말합니다.
프래그먼트를 올바르게 정렬하려면 원래 스니펫을 수정하여
isTraversalGroup
에서 true
로:
@Composable fun TraversalGroupDemo2() { val topSampleText1 = "This sentence is in " val bottomSampleText1 = "the left column." val topSampleText2 = "This sentence is" val bottomSampleText2 = "on the right." Row { CardBox( // 1, topSampleText1, bottomSampleText1, Modifier.semantics { isTraversalGroup = true } ) CardBox( // 2, topSampleText2, bottomSampleText2, Modifier.semantics { isTraversalGroup = true } ) } }
isTraversalGroup
가 각 CardBox
에 구체적으로 설정되므로 CardBox
는
경계가 적용됩니다. 이 경우 왼쪽
CardBox
를 먼저 읽은 후 오른쪽 CardBox
를 읽습니다.
이제 TalkBack이 문장 조각을 올바른 순서로 읽습니다.
'This sentence is in' → '왼쪽 열' → 'This sentence is' → 되었습니다."라고 말합니다.
순회 순서 추가 맞춤설정
traversalIndex
는 TalkBack을 맞춤설정할 수 있는 부동 소수점 속성입니다.
있습니다. 요소를 그룹화하는 것만으로는 TalkBack이
traversalIndex
를 다음과 함께 사용하세요.
isTraversalGroup
: 스크린 리더 순서를 추가로 맞춤설정합니다.
traversalIndex
속성에는 다음과 같은 특성이 있습니다.
traversalIndex
값이 낮은 요소부터 우선순위가 부여됩니다.- 양수 또는 음수가 될 수 있습니다.
- 기본값은
0f
입니다. - 화면 내 요소와 같이 스크린 리더에 포커스를 맞출 수 있는 노드만
만들 수 있습니다. 예를 들어 열에
traversalIndex
만 설정하면 열에isTraversalGroup
가 설정되어 있지 않으면 아무 효과가 없습니다.
다음 예는 traversalIndex
및
isTraversalGroup
함께 사용할 수 있습니다.
예: 트래버스 시계 페이스
시계 페이스는 표준 순회 순서가 일치하지 않는 일반적인 시나리오입니다. 있습니다 이 섹션의 예는 사용자가 한 번에 이동할 수 있는 시간 선택 도구입니다. 시계 페이스의 숫자를 살펴보고 시간과 분을 나타내는 숫자를 선택하세요. 있습니다.
다음의 단순화된 스니펫에는 12의 값이 있는 CircularLayout
가 있습니다.
12부터 시작하여 원 주위를 시계 방향으로 이동하는 숫자가 그려집니다.
@Composable fun ClockFaceDemo() { CircularLayout { repeat(12) { hour -> ClockText(hour) } } } @Composable private fun ClockText(value: Int) { Box(modifier = Modifier) { Text((if (value == 0) 12 else value).toString()) } }
시계 페이스는 기본값인 왼쪽에서 오른쪽 방향으로 논리적으로 읽히지 않기 때문입니다. 위에서 아래로 정렬하면 TalkBack에서 숫자를 순서에 맞지 않게 읽습니다. 정정하다 다음 스니펫과 같이 증분 카운터 값을 사용합니다.
@Composable fun ClockFaceDemo() { CircularLayout(Modifier.semantics { isTraversalGroup = true }) { repeat(12) { hour -> ClockText(hour) } } } @Composable private fun ClockText(value: Int) { Box(modifier = Modifier.semantics { this.traversalIndex = value.toFloat() }) { Text((if (value == 0) 12 else value).toString()) } }
순회 순서를 올바르게 설정하려면 먼저 CircularLayout
를
순회 그룹을 사용하고 isTraversalGroup = true
를 설정합니다. 그런 다음 각 시계 텍스트가
레이아웃에 그려지고 상응하는 traversalIndex
를 카운터로 설정
값으로 사용됩니다.
카운터 값이 계속 증가하기 때문에 각 시계 값의
traversalIndex
은 화면에 숫자가 추가됨(시계 값 0)
traversalIndex
는 0이고 시계 값 1의 traversalIndex
는 1입니다.
이렇게 하면 TalkBack에서 읽는 순서가 설정됩니다. 이 수치는
CircularLayout
내부를 예상한 순서대로 읽음
설정된 traversalIndexes
는
색인을 생성할 때 화면 순서의 나머지 부분은
보존됩니다. 즉, 위 코드에 표시된 시맨틱 변경사항은
스니펫은
isTraversalGroup = true
설정됨.
CircularLayout's
시맨틱스를 isTraversalGroup =
true
로 설정하지 않아도 traversalIndex
변경사항이 계속 적용됩니다. 그러나
CircularLayout
를 사용하여 바인딩하면 시계 페이스의 12자리 숫자를 읽음
마지막으로, 화면의 다른 모든 요소를 방문한 후에 이는
다른 모든 요소의 기본 traversalIndex
가 0f
이고
시계 텍스트 요소는 다른 모든 0f
요소 이후에 읽습니다.
예: 플로팅 작업 버튼의 순회 순서 맞춤설정
이 예에서 traversalIndex
와 isTraversalGroup
는
머티리얼 디자인 플로팅 작업 버튼 (FAB)의 순회 순서 지정 기본
레이아웃은 다음과 같습니다.
기본적으로 이 예의 레이아웃에는 다음과 같은 TalkBack 순서가 있습니다.
상단 앱 바 → 샘플 텍스트 0~6 → 플로팅 작업 버튼 (FAB) → 하단 앱 바
스크린 리더가 먼저 FAB에 포커스를 맞추는 것이 좋습니다. 설정
FAB와 같은 Material 요소에 traversalIndex
를 추가하려면 다음 단계를 따르세요.
@Composable fun FloatingBox() { Box(modifier = Modifier.semantics { isTraversalGroup = true; traversalIndex = -1f }) { FloatingActionButton(onClick = {}) { Icon(imageVector = Icons.Default.Add, contentDescription = "fab icon") } } }
이 스니펫에서는
isTraversalGroup
를 true
로 설정하고 같은 상자에 traversalIndex
를 설정합니다.
(-1f
이 기본값 0f
보다 작음) 플로팅 상자는
화면의 다른 모든 요소보다 앞에 옵니다.
다음으로 플로팅 상자와 기타 요소를 Scaffold에 넣을 수 있습니다. Material Design 레이아웃 구현:
@OptIn(ExperimentalMaterial3Api::class) @Composable fun ColumnWithFABFirstDemo() { Scaffold( topBar = { TopAppBar(title = { Text("Top App Bar") }) }, floatingActionButtonPosition = FabPosition.End, floatingActionButton = { FloatingBox() }, content = { padding -> ContentColumn(padding = padding) }, bottomBar = { BottomAppBar { Text("Bottom App Bar") } } ) }
TalkBack은 다음 순서로 요소와 상호작용합니다.
FAB → 상단 앱 바 → 샘플 텍스트 0~6 → 하단 앱 바
추가 리소스
- 접근성: 필수 개념 및 모든 Android 앱 개발에 공통으로 적용되는 기법
- 액세스 가능한 앱 빌드: 주요 단계 앱의 접근성을 높이기 위해
- 앱 개선 원칙 접근성: 앱의 접근성을 높이기 위해 작업할 때 명심하세요
- 접근성 테스트: Android 접근성 관련 테스트 원칙 및 도구