순회 순서 제어

기본적으로 Compose 앱의 접근성 스크린 리더 동작이 구현됩니다. 읽을 수 있습니다. 일반적으로 왼쪽에서 오른쪽 순으로 읽은 후 위에서 아래로 읽히도록 하겠습니다. 그러나 알고리즘이 결정할 수 없는 앱 레이아웃 유형도 있습니다. 읽기 순서를 변경할 수 있습니다. 뷰 기반 앱에서는 다음 작업을 할 수 있습니다. traversalBeforetraversalAfter 속성을 사용하여 이러한 문제를 해결하세요. Compose 1.5부터 Compose는 똑같이 유연한 API를 제공하지만 새로운 개념 모델입니다.

isTraversalGrouptraversalIndex는 시맨틱 속성입니다. 사용하면 기본 정렬 알고리즘이 적절하지 않습니다. isTraversalGroup는 의미론적으로 중요한 그룹인 반면, traversalIndex는 개별 요소를 구별할 수 있어야 합니다. isTraversalGroup만 단독으로 사용할 수 있습니다. 또는 traversalIndex를 사용하여 추가 맞춤설정을 수행합니다.

다음에서 isTraversalGrouptraversalIndex 사용: 앱을 사용하여 스크린 리더의 순회 순서를 제어할 수 있습니다.

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
        )
    }
}

이 코드는 다음과 비슷한 출력을 생성합니다.

텍스트 2열로 구성된 레이아웃과 왼쪽 열에 '이
  문장이 왼쪽 열에 있는지 오른쪽 열에 'This sentence is on the right'이 표시됩니다
그림 1. 두 문장으로 구성된 레이아웃 (하나는 왼쪽에 1개는 오른쪽 열에 1개).

의미 체계가 설정되지 않았으므로 스크린 리더의 기본 동작은 다음과 같습니다. 왼쪽에서 오른쪽으로, 위에서 아래로 요소를 순회합니다. 그렇기 때문에 기본적으로 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가 설정되어 있지 않으면 아무 효과가 없습니다.

다음 예는 traversalIndexisTraversalGroup 함께 사용할 수 있습니다.

예: 트래버스 시계 페이스

시계 페이스는 표준 순회 순서가 일치하지 않는 일반적인 시나리오입니다. 있습니다 이 섹션의 예는 사용자가 한 번에 이동할 수 있는 시간 선택 도구입니다. 시계 페이스의 숫자를 살펴보고 시간과 분을 나타내는 숫자를 선택하세요. 있습니다.

위에 시간 선택기가 표시된 시계 페이스
그림 2. 시계 페이스 이미지

다음의 단순화된 스니펫에는 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자리 숫자를 읽음 마지막으로, 화면의 다른 모든 요소를 방문한 후에 이는 다른 모든 요소의 기본 traversalIndex0f이고 시계 텍스트 요소는 다른 모든 0f 요소 이후에 읽습니다.

예: 플로팅 작업 버튼의 순회 순서 맞춤설정

이 예에서 traversalIndexisTraversalGroup는 머티리얼 디자인 플로팅 작업 버튼 (FAB)의 순회 순서 지정 기본 레이아웃은 다음과 같습니다.

상단 앱 바, 샘플 텍스트, 플로팅 작업 버튼이 있는 레이아웃
  표시됩니다.
그림 3. 상단 앱 바, 샘플 텍스트, 플로팅 작업 버튼이 있는 레이아웃 하단 앱 바가 있습니다.

기본적으로 이 예의 레이아웃에는 다음과 같은 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")
        }
    }
}

이 스니펫에서는 isTraversalGrouptrue로 설정하고 같은 상자에 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 → 하단 앱 바

추가 리소스