Bedienungshilfen in der Funktion „Schreiben“

In Compose geschriebene Anwendungen sollten die Barrierefreiheit für Nutzer mit unterschiedlichen Anforderungen unterstützen. Bedienungshilfen werden verwendet, um die auf dem Bildschirm angezeigten Inhalte an ein besseres Format für Nutzer mit bestimmten Anforderungen anzupassen. Zur Unterstützung von Bedienungshilfen verwenden Apps APIs im Android-Framework, um semantische Informationen zu ihren UI-Elementen bereitzustellen. Das Android-Framework informiert dann Bedienungshilfen über diese semantischen Informationen. Jede Bedienungshilfe kann auswählen, wie die App am besten beschrieben wird. Android bietet verschiedene Bedienungshilfen, darunter TalkBack und den Schalterzugriff.

Semantik

In Compose werden semantische Attribute verwendet, um Informationen an Bedienungshilfen zu übergeben. Semantik-Properties liefern Informationen zu UI-Elementen, die dem Nutzer angezeigt werden. Die meisten integrierten zusammensetzbaren Funktionen wie Text und Button füllen diese semantischen Attribute mit Informationen aus, die aus der zusammensetzbaren Funktion und ihren untergeordneten Elementen abgeleitet werden. Mit Modifikatoren wie toggleable und clickable werden auch bestimmte semantische Attribute festgelegt. Manchmal benötigt das Framework jedoch weitere Informationen, um zu verstehen, wie ein UI-Element für Nutzer beschrieben wird.

In diesem Dokument werden verschiedene Situationen beschrieben, in denen Sie einer zusammensetzbaren Funktion explizit zusätzliche Informationen hinzufügen müssen, damit sie im Android-Framework richtig beschrieben werden kann. Außerdem wird erläutert, wie die Informationen zur Semantik für eine bestimmte zusammensetzbare Funktion vollständig ersetzt werden können. Grundkenntnisse der Bedienungshilfen in Android werden vorausgesetzt.

Gängige Anwendungsfälle

Damit Bedienungshilfen Ihre App erfolgreich verwenden können, sollten Sie die auf dieser Seite beschriebenen Best Practices befolgen.

Mindestgrößen für Berührungszielbereiche berücksichtigen

Jedes Bildschirmelement, das angeklickt, angetippt oder verwendet werden kann, sollte groß genug sein, um eine zuverlässige Interaktion zu ermöglichen. Achten Sie bei der Größenanpassung dieser Elemente darauf, die Mindestgröße auf 48 dp festzulegen, um den Richtlinien für Bedienungshilfen in Material Design zu entsprechen.

Materialkomponenten wie Checkbox, RadioButton, Switch, Slider und Surface legen diese Mindestgröße intern fest, aber nur, wenn die Komponente Nutzeraktionen empfangen kann. Wenn der Parameter onCheckedChange einer Checkbox beispielsweise auf einen Wert ungleich null gesetzt ist, enthält sie einen Innenrand, der eine Breite und Höhe von mindestens 48 dp hat.

@Composable
private fun CheckableCheckbox() {
    Checkbox(checked = true, onCheckedChange = {})
}

Wenn der onCheckedChange-Parameter auf null gesetzt ist, wird kein Abstand eingefügt, da die Komponente nicht direkt interagieren kann.

@Composable
private fun NonClickableCheckbox() {
    Checkbox(checked = true, onCheckedChange = null)
}

Wenn Sie Auswahlsteuerelemente wie Switch, RadioButton oder Checkbox implementieren, wird normalerweise das anklickbare Verhalten auf einen übergeordneten Container angewendet, der Klick-Callback der zusammensetzbaren Funktion auf null festgelegt und der übergeordneten zusammensetzbaren Funktion ein toggleable- oder selectable-Modifikator hinzugefügt.

@Composable
private fun CheckableRow() {
    MaterialTheme {
        var checked by remember { mutableStateOf(false) }
        Row(
            Modifier
                .toggleable(
                    value = checked,
                    role = Role.Checkbox,
                    onValueChange = { checked = !checked }
                )
                .padding(16.dp)
                .fillMaxWidth()
        ) {
            Text("Option", Modifier.weight(1f))
            Checkbox(checked = checked, onCheckedChange = null)
        }
    }
}

Wenn die Größe einer anklickbaren zusammensetzbaren Funktion kleiner ist als die Mindestgröße für den Berührungszielbereich, wird die Größe des Berührungszielbereichs trotzdem in der Funktion "Compose" erhöht. Dazu wird die Größe des Berührungszielbereichs außerhalb der Grenzen der zusammensetzbaren Funktion erweitert.

Im folgenden Beispiel wird ein sehr kleines anklickbares Box-Objekt erstellt. Der Berührungszielbereich wird automatisch über die Grenzen von Box hinaus erweitert, sodass das Klickereignis weiterhin durch Tippen neben Box ausgelöst wird.

@Composable
private fun SmallBox() {
    var clicked by remember { mutableStateOf(false) }
    Box(
        Modifier
            .size(100.dp)
            .background(if (clicked) Color.DarkGray else Color.LightGray)
    ) {
        Box(
            Modifier
                .align(Alignment.Center)
                .clickable { clicked = !clicked }
                .background(Color.Black)
                .size(1.dp)
        )
    }
}

Um mögliche Überschneidungen zwischen den Berührungsbereichen verschiedener zusammensetzbarer Funktionen zu vermeiden, sollten Sie immer eine ausreichend große Mindestgröße für die zusammensetzbare Funktion verwenden. In unserem Beispiel würde dies bedeuten, dass mit dem sizeIn-Modifikator die Mindestgröße für die innere Box festgelegt wird:

@Composable
private fun LargeBox() {
    var clicked by remember { mutableStateOf(false) }
    Box(
        Modifier
            .size(100.dp)
            .background(if (clicked) Color.DarkGray else Color.LightGray)
    ) {
        Box(
            Modifier
                .align(Alignment.Center)
                .clickable { clicked = !clicked }
                .background(Color.Black)
                .sizeIn(minWidth = 48.dp, minHeight = 48.dp)
        )
    }
}

Klicklabels hinzufügen

Mit einem Klicklabel können Sie dem Klickverhalten einer zusammensetzbaren Funktion eine semantische Bedeutung verleihen. Klicklabels beschreiben, was passiert, wenn Nutzer mit der zusammensetzbaren Funktion interagieren. Bedienungshilfen verwenden Klicklabels, um die App für Nutzer mit bestimmten Bedürfnissen zu beschreiben.

Übergeben Sie im Modifizierer clickable einen Parameter, um das Klicklabel festzulegen:

@Composable
private fun ArticleListItem(openArticle: () -> Unit) {
    Row(
        Modifier.clickable(
            // R.string.action_read_article = "read article"
            onClickLabel = stringResource(R.string.action_read_article),
            onClick = openArticle
        )
    ) {
        // ..
    }
}

Wenn Sie keinen Zugriff auf den anklickbaren Modifikator haben, können Sie das Klicklabel auch im Modifizierer Semantik festlegen:

@Composable
private fun LowLevelClickLabel(openArticle: () -> Boolean) {
    // R.string.action_read_article = "read article"
    val readArticleLabel = stringResource(R.string.action_read_article)
    Canvas(
        Modifier.semantics {
            onClick(label = readArticleLabel, action = openArticle)
        }
    ) {
        // ..
    }
}

Visuelle Elemente beschreiben

Wenn Sie eine zusammensetzbare Funktion Image oder Icon definieren, kann das Android-Framework nicht automatisch verstehen, was angezeigt wird. Sie müssen eine Textbeschreibung des visuellen Elements übergeben.

Stellen Sie sich einen Bildschirm vor, auf dem Nutzende die aktuelle Seite mit Freunden teilen können. Dieser Bildschirm enthält ein anklickbares Freigabesymbol:

Ein Streifen anklickbarer Symbole mit dem

Anhand des Symbols allein kann das Android-Framework nicht ermitteln, wie es einem sehbehinderten Nutzer zu beschreiben ist. Das Android-Framework benötigt eine zusätzliche Textbeschreibung des Symbols.

Der Parameter contentDescription wird verwendet, um ein visuelles Element zu beschreiben. Du solltest einen lokalisierten String verwenden, da dies dem Nutzer mitgeteilt wird.

@Composable
private fun ShareButton(onClick: () -> Unit) {
    IconButton(onClick = onClick) {
        Icon(
            imageVector = Icons.Filled.Share,
            contentDescription = stringResource(R.string.label_share)
        )
    }
}

Einige visuelle Elemente sind rein dekorativ und möchten den Nutzern nicht unbedingt vermittelt werden. Wenn Sie den contentDescription-Parameter auf null setzen, geben Sie gegenüber dem Android-Framework an, dass diesem Element keine Aktionen oder Status zugeordnet sind.

@Composable
private fun PostImage(post: Post, modifier: Modifier = Modifier) {
    val image = post.imageThumb ?: painterResource(R.drawable.placeholder_1_1)

    Image(
        painter = image,
        // Specify that this image has no semantic meaning
        contentDescription = null,
        modifier = modifier
            .size(40.dp, 40.dp)
            .clip(MaterialTheme.shapes.small)
    )
}

Du musst entscheiden, ob ein bestimmtes visuelles Element ein contentDescription benötigt. Fragen Sie sich, ob das Element Informationen vermittelt, die die Nutzenden zur Ausführung ihrer Aufgabe benötigen. Andernfalls ist es besser, die Beschreibung wegzulassen.

Elemente zusammenführen

Mit Bedienungshilfen wie TalkBack und Schalterzugriff können Nutzer den Fokus auf Elemente auf dem Bildschirm verschieben. Es ist wichtig, dass die Elemente mit dem richtigen Detaillierungsgrad fokussiert sind. Wenn jede einzelne zusammensetzbare Funktion auf unterer Ebene des Bildschirms unabhängig voneinander ausgerichtet ist, muss ein Nutzer viel interagieren, um sich über den Bildschirm zu bewegen. Wenn Elemente zu aggressiv zusammengeführt werden, verstehen Nutzer möglicherweise nicht, welche Elemente zusammengehören.

Wenn Sie einen clickable-Modifizierer auf eine zusammensetzbare Funktion anwenden, werden alle darin enthaltenen Elemente automatisch zusammengeführt. Dies gilt auch für ListItem. Die Elemente in einem Listenelement werden zusammengeführt und von Bedienungshilfen als ein Element angesehen.

Es ist möglich, eine Reihe von zusammensetzbaren Funktionen zu haben, die eine logische Gruppe bilden. Diese Gruppe ist jedoch nicht anklickbar oder Teil eines Listenelements. Die Bedienungshilfen sollten sie trotzdem als ein Element betrachten. Stellen Sie sich z. B. eine zusammensetzbare Funktion vor, die den Avatar, den Namen und einige zusätzliche Informationen eines Nutzers anzeigt:

Eine Gruppe von UI-Elementen, die den Namen eines Nutzers enthält. Der Name ist ausgewählt.

Mit dem Parameter mergeDescendants im Modifikator semantics können Sie Compose anweisen, diese Elemente zusammenzuführen. Auf diese Weise wählen Bedienungshilfen nur das zusammengeführte Element aus und alle semantischen Attribute der untergeordneten Elemente werden zusammengeführt.

@Composable
private fun PostMetadata(metadata: Metadata) {
    // Merge elements below for accessibility purposes
    Row(modifier = Modifier.semantics(mergeDescendants = true) {}) {
        Image(
            imageVector = Icons.Filled.AccountCircle,
            contentDescription = null // decorative
        )
        Column {
            Text(metadata.author.name)
            Text("${metadata.date} • ${metadata.readTimeMinutes} min read")
        }
    }
}

Bedienungshilfen konzentrieren sich jetzt auf den gesamten Container und führen deren Inhalte zusammen:

Eine Gruppe von UI-Elementen, die den Namen eines Nutzers enthält. Alle Elemente werden gemeinsam ausgewählt.

Benutzerdefinierte Aktionen hinzufügen

Sehen Sie sich den folgenden Listeneintrag an:

Ein typischer Listeneintrag mit einem Artikeltitel, einem Autor und einem Lesezeichensymbol.

Wenn Sie einen Screenreader wie TalkBack verwenden, um zu hören, was auf dem Bildschirm angezeigt wird, wird zuerst das gesamte Element und dann das Lesezeichensymbol ausgewählt.

Das Listenelement mit allen ausgewählten Elementen.

Den Listeneintrag mit ausgewähltem Lesezeichensymbol

Bei einer langen Liste kann sich das oft wiederholen. Ein besserer Ansatz wäre, eine benutzerdefinierte Aktion zu definieren, mit der Nutzer das Element als Lesezeichen speichern können. Außerdem müssen Sie das Verhalten des Lesezeichensymbols selbst explizit entfernen, damit es nicht von der Bedienungshilfe ausgewählt wird. Dazu verwenden Sie den Modifizierer clearAndSetSemantics:

@Composable
private fun PostCardSimple(
    /* ... */
    isFavorite: Boolean,
    onToggleFavorite: () -> Boolean
) {
    val actionLabel = stringResource(
        if (isFavorite) R.string.unfavorite else R.string.favorite
    )
    Row(
        modifier = Modifier
            .clickable(onClick = { /* ... */ })
            .semantics {
                // Set any explicit semantic properties
                customActions = listOf(
                    CustomAccessibilityAction(actionLabel, onToggleFavorite)
                )
            }
    ) {
        /* ... */
        BookmarkButton(
            isBookmarked = isFavorite,
            onClick = onToggleFavorite,
            // Clear any semantics properties set on this node
            modifier = Modifier.clearAndSetSemantics { }
        )
    }
}

Zustand eines Elements beschreiben

Eine zusammensetzbare Funktion kann ein stateDescription für die Semantik definieren, das vom Android-Framework verwendet wird, um den Status der zusammensetzbaren Funktion auszulesen. Eine umsetzbare zusammensetzbare Funktion kann beispielsweise den Status „Aktiviert“ oder „Nicht aktiviert“ haben. In einigen Fällen müssen Sie möglicherweise die Standardlabels für Statusbeschreibungen überschreiben, die von „Compose“ verwendet werden. Geben Sie dazu explizit die Labels der Statusbeschreibung an, bevor Sie eine zusammensetzbare Funktion als Umschaltbar definieren:

@Composable
private fun TopicItem(itemTitle: String, selected: Boolean, onToggle: () -> Unit) {
    val stateSubscribed = stringResource(R.string.subscribed)
    val stateNotSubscribed = stringResource(R.string.not_subscribed)
    Row(
        modifier = Modifier
            .semantics {
                // Set any explicit semantic properties
                stateDescription = if (selected) stateSubscribed else stateNotSubscribed
            }
            .toggleable(
                value = selected,
                onValueChange = { onToggle() }
            )
    ) {
        /* ... */
    }
}

Überschriften definieren

Apps zeigen manchmal viele Inhalte auf einem Bildschirm in einem scrollbaren Container an. Beispielsweise kann ein Bildschirm den vollständigen Inhalt eines Artikels anzeigen, den der Nutzer gerade liest:

Screenshot eines Blogposts mit dem Artikeltext in einem scrollbaren Container.

Nutzende mit Anforderungen an Bedienungshilfen können sich nur schwer auf einem solchen Bildschirm zurechtfinden. Um die Navigation zu erleichtern, können Sie angeben, welche Elemente Überschriften sind. Im obigen Beispiel könnte jeder Unterabschnittstitel als Überschrift für die Barrierefreiheit definiert werden. Einige Bedienungshilfen wie TalkBack ermöglichen es Nutzern, direkt von Überschrift zu Überschrift zu wechseln.

In „Compose“ geben Sie an, dass eine zusammensetzbare Funktion eine Überschrift ist, indem Sie ihr Semantikattribut definieren:

@Composable
private fun Subsection(text: String) {
    Text(
        text = text,
        style = MaterialTheme.typography.headlineSmall,
        modifier = Modifier.semantics { heading() }
    )
}

Automatisiertes Testen von Bedienungshilfen

Wenn Sie die semantischen Eigenschaften Ihrer Anwendung anpassen, z. B. bei den oben aufgeführten Anwendungsfällen, können Sie mithilfe automatisierter UI-Tests deren Richtigkeit überprüfen und Regressionen verhindern.

Wenn Sie beispielsweise testen möchten, ob das Klicklabel eines Elements richtig festgelegt ist, können Sie den folgenden Code verwenden:

@Test
fun test() {
    composeTestRule
        .onNode(nodeMatcher)
        .assert(
            SemanticsMatcher("onClickLabel is set correctly") {
                it.config.getOrNull(SemanticsActions.OnClick)?.label == "My Click Label"
            }
        )
}

Benutzerdefinierte zusammensetzbare Funktionen auf unterer Ebene erstellen

In einem komplexeren Anwendungsfall müssen bestimmte Material-Komponenten in der App durch benutzerdefinierte Versionen ersetzt werden. In diesem Szenario ist es sehr wichtig, die Barrierefreiheit zu berücksichtigen. Angenommen, Sie ersetzen das Material Checkbox durch Ihre eigene Implementierung. Es wäre wirklich leicht, den Modifizierer triStateToggleable hinzuzufügen, der die Bedienungshilfen dieser Komponente verwaltet.

Als Faustregel gilt, dass Sie sich die Implementierung der Komponente in der Material-Bibliothek ansehen und alle verfügbaren Bedienungshilfen nachahmen sollten. Außerdem solltest du im Gegensatz zu Modifizierern auf UI-Ebene intensiv die Foundation-Modifikatoren einsetzen, da diese die Barrierefreiheit bereits standardmäßig berücksichtigen. Testen Sie die Implementierung Ihrer benutzerdefinierten Komponente mit mehreren Bedienungshilfen, um ihr Verhalten zu überprüfen.

Durchquerungsreihenfolge mit isTraversalGroup und traversalIndex ändern

Standardmäßig wird das Screenreader-Verhalten des Screenreaders für Bedienungshilfen in einer App zum Schreiben in der erwarteten Lesereihenfolge implementiert, also von links nach rechts und dann von oben nach unten. Es gibt jedoch einige Arten von App-Layouts, bei denen der Algorithmus die tatsächliche Lesereihenfolge nicht ohne zusätzliche Hinweise ermitteln kann. In ansichtsbasierten Apps können solche Probleme mit den Attributen traversalBefore und traversalAfter behoben werden. Ab Compose 1.5 stellt Compose eine ebenso flexible API bereit, jedoch mit einem neuen konzeptionellen Modell.

isTraversalGroup und traversalIndex sind semantische Eigenschaften, mit denen Sie die Bedienungshilfen und die TalkBack-Fokusreihenfolge in Szenarien steuern können, in denen der Standardsortieralgorithmus nicht geeignet ist. isTraversalGroup identifiziert semantisch wichtige Gruppen, während traversalIndex die Reihenfolge einzelner Elemente innerhalb dieser Gruppen anpasst. Sie können isTraversalGroup allein oder zur weiteren Anpassung mit traversalIndex verwenden.

Auf dieser Seite wird beschrieben, wie Sie isTraversalGroup und traversalIndex in Ihrer App verwenden können, um die Durchlaufreihenfolge von Screenreadern zu steuern.

Elemente mit isTraversalGroup gruppieren

isTraversalGroup ist ein boolesches Attribut, das definiert, ob ein Knoten Semantik eine Durchlaufgruppe ist. Dieser Knotentyp dient dazu, bei der Organisation der untergeordneten Elemente als Begrenzung oder Rahmen zu dienen.

Wenn Sie isTraversalGroup = true für einen Knoten festlegen, werden alle untergeordneten Elemente dieses Knotens besucht, bevor zu anderen Elementen gewechselt wird. Sie können isTraversalGroup auf Knoten festlegen, die nicht für den Screenreader fokussierbar sind, z. B. Spalten, Zeilen oder Felder.

In diesem Beispiel wird ein Snippet so geändert, dass es isTraversalGroup verwendet. Das folgende Snippet gibt vier Textelemente aus. Die linken beiden Elemente gehören zu einem CardBox-Element, die beiden rechten zu einem anderen CardBox-Element:

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

Die Ausgabe des Codes sieht in etwa so aus:

Layout mit zwei Spalten mit Text, wobei in der linken Spalte „Dieser Satz befindet sich in der linken Spalte“ und in der rechten Spalte „Dieser Satz befindet sich in der rechten Spalte“ steht.
Abbildung 1: Ein Layout mit zwei Sätzen (einer in der linken Spalte und einer in der rechten Spalte).

Da keine Semantik festgelegt wurde, durchsucht der Screenreader Elemente standardmäßig von links nach rechts und von oben nach unten. Aufgrund dieser Standardeinstellung liest TalkBack die Satzfragmente in der falschen Reihenfolge vor:

„Dieser Satz ist in“ → „Dieser Satz ist“ → „die linke Spalte“. → „auf der rechten Seite“.

Damit die Fragmente richtig sortiert werden, ändern Sie das ursprüngliche Snippet so, dass isTraversalGroup auf true gesetzt wird:

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

Da isTraversalGroup speziell für jede CardBox festgelegt wird, werden die CardBox-Grenzen beim Sortieren der Elemente berücksichtigt. In diesem Fall wird zuerst die linke CardBox gelesen, gefolgt vom rechten CardBox.

Jetzt liest TalkBack die Satzfragmente in der richtigen Reihenfolge vor:

„Dieser Satz ist in“ → „die linke Spalte“. → „Dieser Satz befindet sich“ → „auf der rechten Seite“.

Mit „traversalIndex“ die Durchlaufreihenfolge weiter anpassen

traversalIndex ist eine Gleitkommazahl, mit der Sie die Durchlaufreihenfolge von TalkBack anpassen können. Wenn es nicht ausreicht, die Elemente einfach zu gruppieren, damit TalkBack richtig funktioniert, können Sie traversalIndex in Verbindung mit isTraversalGroup verwenden, um die Reihenfolge des Screenreaders weiter anzupassen.

Das Attribut traversalIndex hat die folgenden Eigenschaften:

  • Elemente mit niedrigeren traversalIndex-Werten werden zuerst priorisiert.
  • Kann positiv oder negativ sein.
  • Der Standardwert ist 0f.
  • Betrifft nur Knoten, die für Screenreader fokussiert werden können, z. B. Bildschirmelemente wie Text oder Schaltflächen. Wenn Sie beispielsweise nur traversalIndex für eine Spalte festlegen, hat dies keine Auswirkungen, es sei denn, für die Spalte ist auch isTraversalGroup festgelegt.

Das folgende Beispiel zeigt, wie Sie traversalIndex und isTraversalGroup zusammen verwenden können.

Beispiel: Durchlaufendes Zifferblatt

Ein Zifferblatt ist ein gängiges Szenario, in dem die Standarddurchlaufreihenfolge nicht funktioniert. Das Beispiel in diesem Abschnitt basiert auf einer Zeitauswahl, mit der ein Nutzer durch die Zahlen auf einem Ziffernblatt blättern und Ziffern für die Stunden- und Minutenabschnitte auswählen kann.

Ein Zifferblatt mit einer Zeitauswahl darüber.
Abbildung 2: Ein Bild eines Ziffernblatts.

Im folgenden vereinfachten Snippet gibt es eine CircularLayout, in der 12 Zahlen gezeichnet sind, von denen 12 beginnt und sich im Uhrzeigersinn um den Kreis bewegt:

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

Da das Ziffernblatt mit der Standardreihenfolge von links nach rechts und von oben nach unten nicht logisch gelesen wird, liest TalkBack die Zahlen in falscher Reihenfolge vor. Um dies zu beheben, verwenden Sie den sich erhöhenden Zählerwert, wie im folgenden Snippet gezeigt:

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

Damit die Durchlaufreihenfolge richtig festgelegt werden kann, müssen Sie CircularLayout als Durchlaufgruppe festlegen und isTraversalGroup = true festlegen. Wenn dann jeder Uhrtext in das Layout gezeichnet wird, setze die entsprechende traversalIndex auf den Zählerwert.

Da der Zählerwert kontinuierlich erhöht wird, ist der traversalIndex jedes Taktwerts größer, wenn dem Bildschirm Zahlen hinzugefügt werden – der Taktwert 0 hat einen traversalIndex von 0, der Taktwert 1 hat eine traversalIndex von 1 usw. Auf diese Weise wird die Reihenfolge festgelegt, in der TalkBack vorliest. Jetzt werden die Zahlen in CircularLayout in der erwarteten Reihenfolge gelesen.

Da die festgelegten traversalIndexes nur relativ zu anderen Indizes innerhalb derselben Gruppierung sind, wurde die restliche Bildschirmreihenfolge beibehalten. Mit anderen Worten: Die im obigen Code-Snippet gezeigten semantischen Änderungen ändern nur die Reihenfolge innerhalb des Ziffernblatts, für das isTraversalGroup = true festgelegt ist.

Auch wenn Sie die CircularLayout's-Semantik auf isTraversalGroup = true festlegen, gelten die Änderungen an traversalIndex weiterhin. Ohne CircularLayout zum Binden werden die zwölf Ziffern des Ziffernblatts jedoch zuletzt gelesen, nachdem alle anderen Elemente auf dem Bildschirm aufgerufen wurden. Das liegt daran, dass alle anderen Elemente die Standard-traversalIndex von 0f haben und die Uhrtextelemente nach allen anderen 0f-Elementen gelesen werden.

Beispiel: Durchlaufreihenfolge für unverankerte Aktionsschaltfläche anpassen

In diesem Beispiel verwenden Sie traversalIndex und isTraversalGroup, um die Durchquerungsreihenfolge einer unverankerten Material Design-Aktionsschaltfläche (UAS) zu steuern. Dieses Beispiel basiert auf dem folgenden Layout:

Ein Layout mit einer oberen App-Leiste, Beispieltext, einer unverankerten Aktionsschaltfläche und einer unteren App-Leiste.
Abbildung 3: Layout mit einer oberen App-Leiste, Beispieltext, einer unverankerten Aktionsschaltfläche und einer unteren App-Leiste.

Standardmäßig gilt für das obige Layout die folgende TalkBack-Reihenfolge:

Obere App-Leiste → Beispieltexte 0 bis 6 → unverankerte Aktionsschaltfläche (FAB) → Untere App-Leiste

Vielleicht möchten Sie, dass sich der Screenreader zuerst auf den FAB konzentriert. So legen Sie eine traversalIndex für ein Material-Element wie einen FAB fest:

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

Wenn in diesem Snippet ein Feld erstellt wird, bei dem isTraversalGroup auf true gesetzt ist und ein traversalIndex für dasselbe Feld festgelegt wird (-1f ist niedriger als der Standardwert von 0f), wird das unverankerte Feld vor allen anderen Elementen auf dem Bildschirm angezeigt.

Als Nächstes können Sie den unverankerten Kasten und andere Elemente in einem Gerüst platzieren, das ein einfaches Material Design-Layout implementiert:

@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 interagiert in folgender Reihenfolge mit den Elementen:

FAB → Obere App-Leiste → Beispieltexte 0 bis 6 → Untere App-Leiste

Weitere Informationen

Weitere Informationen zur Unterstützung von Bedienungshilfen in Ihrem Compose-Code finden Sie im Codelab zu Bedienungshilfen im Jetpack Compose-Code.