Kontroluj kolejność przechodzenia

Domyślnie działanie czytnika ekranu w aplikacji do tworzenia wiadomości odbywa się w oczekiwanej kolejności czytania – zazwyczaj od lewej do prawej, a następnie z góry do dołu. W niektórych typach układów aplikacji algorytm nie może jednak określić rzeczywistej kolejności czytania bez dodatkowych wskazówek. W aplikacjach z widokiem danych możesz rozwiązać takie problemy, korzystając z właściwości traversalBefore i traversalAfter. Począwszy od tworzenia wersji 1.5 funkcja Compose udostępnia równie elastyczny interfejs API, ale z nowym modelem koncepcyjnym.

isTraversalGroup i traversalIndex to właściwości semantyczne, które umożliwiają kontrolowanie ułatwień dostępu i kolejności zaznaczenia w TalkBack w sytuacjach, gdy domyślny algorytm sortowania jest nieodpowiedni. isTraversalGroup identyfikuje grupy ważne semantycznie, a traversalIndex dostosowuje kolejność poszczególnych elementów w tych grupach. Możesz użyć samej listy isTraversalGroup lub traversalIndex, aby dodatkowo dostosować działanie.

Użyj isTraversalGroup i traversalIndex w aplikacji, aby kontrolować kolejność przechodzenia między czytnikami ekranu.

Grupuj elementy za pomocą funkcji isTraversalGroup

isTraversalGroup to właściwość wartości logicznej, która określa, czy węzeł semantyka jest grupą przemierzania. Węzeł tego typu służy jako granica przy organizowaniu elementów podrzędnych węzła.

Ustawienie isTraversalGroup = true w węźle oznacza, że wszystkie jego elementy podrzędne są odwiedzane przed przejściem do innych elementów. Możesz ustawić isTraversalGroup w węzłach, które nie można zaznaczyć, przy użyciu czytnika ekranu, takich jak kolumny, wiersze lub pola.

W poniższym przykładzie użyto parametru isTraversalGroup. Emituje on 4 elementy tekstowe. Dwa lewe elementy należą do 1 elementu CardBox, a 2 po prawej – do kolejnego elementu 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
        )
    }
}

Kod wygeneruje dane wyjściowe podobne do tych:

Układ z 2 kolumnami tekstu: lewa kolumna zawiera tekst „To zdanie jest w lewej kolumnie”, a w prawej – „To zdanie jest po prawej stronie”.
Rysunek 1. Układ z 2 zdaniami (jedno w lewej kolumnie, a drugie w prawej).

Ponieważ nie ustawiono żadnej semantyki, czytnik ekranu domyślnie porusza się między elementami od lewej do prawej i z góry na dół. Ze względu na to ustawienie domyślne TalkBack odczytuje fragmenty zdań w niewłaściwej kolejności:

„To zdanie znajduje się w” → „To zdanie to” → „w lewej kolumnie”. → „po prawej”.

Aby prawidłowo uporządkować fragmenty, zmień oryginalny fragment kodu, ustawiając parametr isTraversalGroup na 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 }
        )
    }
}

Ponieważ atrybut isTraversalGroup jest ustawiony specjalnie dla każdego elementu CardBox, podczas sortowania jego elementów obowiązują granice CardBox. W tym przypadku jako pierwszy odczytywany jest tekst CardBox, a po nim prawy CardBox.

Teraz TalkBack odczytuje fragmenty zdań we właściwej kolejności:

„To zdanie znajduje się w” → „lewej kolumnie”. → „To zdanie to” → „po prawej”.

Dostosuj kolejność przechodzenia

traversalIndex to właściwość liczby zmiennoprzecinkowej, która umożliwia dostosowywanie kolejności przechodzenia między elementami TalkBack. Jeśli grupowanie elementów nie wystarcza do prawidłowego działania TalkBack, użyj funkcji traversalIndex w połączeniu z atrybutem isTraversalGroup, by jeszcze bardziej dostosować kolejność czytników ekranu.

Właściwość traversalIndex ma te cechy:

  • Elementy o niższych wartościach traversalIndex mają wyższy priorytet.
  • Może być dodatni lub ujemny.
  • Wartość domyślna to 0f.
  • Ma wpływ tylko na węzły, do których można przejść w czytniku ekranu, takie jak elementy ekranowe takie jak tekst czy przyciski. Na przykład ustawienie w kolumnie tylko wartości traversalIndex nie przyniesie żadnego efektu, chyba że w kolumnie też zostanie ustawiony parametr isTraversalGroup.

Ten przykład ilustruje, jak można używać jednocześnie atrybutów traversalIndex i isTraversalGroup.

Przykład: Przesuwanie tarczy zegara

Tarcza zegara to typowy scenariusz, w którym nie działa standardowa kolejność przechodzenia między elementami. Przykład w tej sekcji to selektor godziny, w którym użytkownik może przeglądać liczby na tarczy zegara i wybierać cyfry dla przedziałów godzin i minut.

Tarcza zegara z selektorem godziny nad nią.
Rysunek 2. Obraz tarczy zegara.

W tym uproszczonym fragmencie znajduje się CircularLayout, w którym narysowanych zostanie 12 liczb, zaczynając od 12 i poruszając się w prawo po okręgu:

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

Ponieważ tarcza zegara nie jest logicznie odczytywana przy domyślnej kolejności od lewej do prawej i od góry do dołu, TalkBack odczytuje liczby w niewłaściwej kolejności. Aby to skorygować, użyj przyrostowej wartości licznika, jak w tym fragmencie:

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

Aby poprawnie ustawić kolejność przechodzenia między elementami, najpierw utwórz grupę przemierzania CircularLayout i ustaw element isTraversalGroup = true. Następnie, gdy każdy tekst zegara jest rysowany w układzie, ustaw odpowiadający mu tekst traversalIndex na wartość licznika.

Ponieważ wartość licznika stale rośnie, każda wartość zegara traversalIndex zwiększa się w miarę dodawania liczb na ekranie — wartość zegara 0 ma traversalIndex wartość 0, a wartość zegara 1 ma wartość traversalIndex równy 1. Dzięki temu zostanie ustalona kolejność ich odczytywania przez TalkBack. Teraz liczby w elemencie CircularLayout są odczytywane w oczekiwanej kolejności.

Ustawione elementy traversalIndexes są powiązane tylko z innymi indeksami w tej samej grupie, więc pozostała część kolejności ekranu została zachowana. Oznacza to, że zmiany semantyczne widoczne w poprzednim fragmencie kodu modyfikują tylko tę kolejność w obrębie tarczy zegara, która ma ustawioną wartość isTraversalGroup = true.

Pamiętaj, że jeśli nie ustawisz semantyki CircularLayout's na isTraversalGroup = true, zmiany traversalIndex nadal będą obowiązywać. Jeśli jednak nie da się ich powiązać za pomocą CircularLayout, 12 cyfr na tarczy zegara jest odczytywanych na końcu, gdy wszystkie pozostałe elementy na ekranie zostały wyświetlone. Dzieje się tak dlatego, że wszystkie pozostałe elementy mają domyślną wartość traversalIndex wynoszącą 0f, a teksty zegara są odczytywane po wszystkich pozostałych elementach 0f.

Przykład: dostosowywanie kolejności przechodzenia w przypadku pływającego przycisku polecenia

W tym przykładzie elementy traversalIndex i isTraversalGroup określają kolejność przechodzenia pomiędzy elementami pływającego przycisku polecenia Material Design (FAB). Podstawą tego przykładu jest taki układ:

Układ z górnym paskiem aplikacji, przykładowym tekstem, pływającym przyciskiem polecenia i dolnym paskiem aplikacji.
Rysunek 3. Układ z górnym paskiem aplikacji, przykładowym tekstem, pływającym przyciskiem polecenia i dolnym paskiem aplikacji.

Domyślnie układ w tym przykładzie ma taką kolejność TalkBack:

Górny pasek aplikacji → Przykładowe teksty 0–6 → pływający przycisk polecenia (FAB) → Dolny pasek aplikacji

Czytnik ekranu może najpierw skupić się na przycisku typu FAB. Aby ustawić traversalIndex w elemencie Material, takim jak przycisk typu FAB, wykonaj te czynności:

@Composable
fun FloatingBox() {
    Box(modifier = Modifier.semantics { isTraversalGroup = true; traversalIndex = -1f }) {
        FloatingActionButton(onClick = {}) {
            Icon(imageVector = Icons.Default.Add, contentDescription = "fab icon")
        }
    }
}

W tym fragmencie kodu utworzenie pola z parametrem isTraversalGroup ustawionym na true i ustawieniem w tym samym polu elementu traversalIndex (wartość -1f jest niższa niż wartość domyślna 0f) oznacza, że pole pływające będzie wyświetlać się przed wszystkimi innymi elementami ekranowymi.

Następnie możesz umieścić pływające pole i inne elementy w rusztowaniu, które stosuje układ 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 współdziała z elementami w następującej kolejności:

FAB → Górny pasek aplikacji → Przykładowe teksty 0–6 → Dolny pasek aplikacji

Dodatkowe materiały