Semantik

Neben den primären Informationen, die ein Composeable enthält, z. B. ein Textstring eines Text-Composeables, kann es hilfreich sein, zusätzliche Informationen zu UI-Elementen zu haben.

Informationen zur Bedeutung und Rolle einer Komponente in Compose werden als Semantik bezeichnet. So können Sie Diensten wie Bedienungshilfen, Autofill und Tests zusätzlichen Kontext zu Zusammensetzelementen zur Verfügung stellen. Ein Kamerasymbol ist beispielsweise visuell nur ein Bild, die semantische Bedeutung könnte aber „Foto aufnehmen“ sein.

Wenn Sie die entsprechenden Semantiken mit den entsprechenden Compose APIs kombinieren, stellen Sie den Bedienungshilfen so viele Informationen wie möglich zu Ihrer Komponente zur Verfügung. Diese entscheiden dann, wie sie dem Nutzer angezeigt wird.

Die Material UI- und Compose-Foundation-APIs sowie die Compose-UI-APIs haben integrierte Semantiken, die ihrer jeweiligen Rolle und Funktion entsprechen. Sie können diese Semantiken jedoch auch für vorhandene APIs ändern oder neue für benutzerdefinierte Komponenten festlegen, je nach Ihren spezifischen Anforderungen.

Semantische Eigenschaften

Semantische Eigenschaften vermitteln die Bedeutung des entsprechenden Composeables. Das Text-Komposit enthält beispielsweise das semantische Attribut text, da dies die Bedeutung dieses Komposits ist. Ein Icon enthält die Eigenschaft contentDescription, die (falls vom Entwickler festgelegt) in Text die Bedeutung des Symbols vermittelt.

Überlegen Sie, wie semantische Eigenschaften die Bedeutung eines Composeables vermitteln. Berücksichtigen Sie einen Switch. So sieht es für den Nutzer aus:

Abbildung 1 Ein Switch im Status „An“ und „Aus“.

Die Bedeutung dieses Elements könnte so beschrieben werden: „Das ist ein Schalter, ein umschaltbares Element, das sich im Status ‚An‘ befindet. Sie können darauf klicken, um damit zu interagieren.“

Genau dafür sind die Semantik-Properties gedacht. Der Semantics-Knoten dieses Schalterelements enthält die folgenden Eigenschaften, wie im Layout-Inspektor dargestellt:

Layout Inspector mit den Semantics-Properties eines Switch-Composeables
Abbildung 2. Layout Inspector mit den Semantikeigenschaften einer Switch-Komponente

Role gibt den Elementtyp an. Mit StateDescription wird beschrieben, wie auf den Status „An“ verwiesen werden soll. Standardmäßig ist dies eine lokalisierte Version des Wortes „An“. Je nach Kontext kann dies jedoch konkretisiert werden, z. B. „Aktiviert“. Die ToggleableState ist der aktuelle Status des Schalters. Die Property OnClick verweist auf die Methode, die für die Interaktion mit diesem Element verwendet wird.

Wenn Sie die semantischen Eigenschaften der einzelnen Komponenten in Ihrer App im Blick behalten, eröffnen sich viele nützliche Möglichkeiten:

  • Bedienungshilfen verwenden die Eigenschaften, um die auf dem Bildschirm angezeigte Benutzeroberfläche darzustellen und Nutzern die Interaktion damit zu ermöglichen. Für den Schalter könnte TalkBack beispielsweise ansagen: „An; Schalter; doppeltippen, um zu wechseln“. Der Nutzer kann auf dem Display doppeltippen, um den Schalter auszuschalten.
  • Das Test-Framework verwendet die Eigenschaften, um Knoten zu finden, mit ihnen zu interagieren und Behauptungen aufzustellen:
    val mySwitch = SemanticsMatcher.expectValue(
        SemanticsProperties.Role, Role.Switch
    )
    composeTestRule.onNode(mySwitch)
        .performClick()
        .assertIsOff()

Bei Composables und Modifikatoren, die auf der Grundlagenbibliothek von Compose basieren, werden die relevanten Eigenschaften standardmäßig für Sie festgelegt. Optional können Sie diese Eigenschaften manuell ändern, um die Barrierefreiheit für bestimmte Anwendungsfälle zu verbessern oder die Merge- oder Clear-Strategie Ihrer Composeables zu ändern.

Sie können eine Vielzahl verschiedener Semantiken anwenden, um den Inhaltstyp Ihrer Komponente für Dienste zur Barrierefreiheit zu signalisieren. Diese Ergänzungen unterstützen die vorhandenen semantischen Informationen und helfen den Diensten zur Barrierefreiheit, die Darstellung, Ansage oder Interaktion mit Ihrer Komponente zu optimieren.

Eine vollständige Liste der semantischen Properties finden Sie im Objekt SemanticsProperties. Eine vollständige Liste der möglichen Bedienungshilfenaktionen finden Sie im Objekt SemanticsActions.

Überschriften

Apps enthalten häufig Bildschirme mit textreichen Inhalten, z. B. lange Artikel oder Nachrichtenseiten, die in der Regel in verschiedene Abschnitte mit Überschriften unterteilt sind:

Ein Blogpost mit Artikeltext in einem scrollbaren Container.
Abbildung 3 Ein Blogpost mit dem Artikeltext in einem scrollbaren Container.

Nutzer mit Beeinträchtigungen haben möglicherweise Schwierigkeiten, sich auf einem solchen Bildschirm zurechtzufinden. Einige Bedienungshilfen ermöglichen eine direkte Navigation zwischen Abschnitten oder Überschriften, um die Navigation zu verbessern. Geben Sie dazu an, dass es sich bei Ihrer Komponente um eine heading handelt, indem Sie die entsprechende Semantikeigenschaft definieren:

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

Benachrichtigungen und Pop-ups

Wenn es sich bei Ihrer Komponente um eine Benachrichtigung oder ein Pop-up handelt, z. B. eine Snackbar, sollten Sie den Diensten zur Barrierefreiheit signalisieren, dass eine neue Struktur oder Aktualisierungen der Inhalte an die Nutzer weitergegeben werden können.

Komponenten, die Benachrichtigungen ähneln, können mit der semantischen Eigenschaft liveRegion gekennzeichnet werden. So können Dienste zur Barrierefreiheit den Nutzer automatisch über Änderungen an dieser Komponente oder ihren untergeordneten Elementen informieren:

PopupAlert(
    message = "You have a new message",
    modifier = Modifier.semantics {
        liveRegion = LiveRegionMode.Polite
    }
)

Sie sollten liveRegionMode.Polite in den meisten Fällen verwenden, wenn die Aufmerksamkeit der Nutzer nur kurz auf Benachrichtigungen oder wichtige sich ändernde Inhalte auf dem Bildschirm gelenkt werden soll.

Verwenden Sie liveRegion.Assertive sparsam, um störendes Feedback zu vermeiden. Sie sollten in Situationen verwendet werden, in denen Nutzer unbedingt auf zeitkritische Inhalte hingewiesen werden müssen:

PopupAlert(
    message = "Emergency alert incoming",
    modifier = Modifier.semantics {
        liveRegion = LiveRegionMode.Assertive
    }
)

Live-Bereiche sollten nicht für Inhalte verwendet werden, die häufig aktualisiert werden, z. B. für Countdown-Timer, um Nutzer nicht mit ständigem Feedback zu überfordern.

Fensterähnliche Komponenten

Fensterähnliche benutzerdefinierte Komponenten wie ModalBottomSheet benötigen zusätzliche Signale, um sie von den umgebenden Inhalten zu unterscheiden. Dazu können Sie die paneTitle-Semantik verwenden, damit alle relevanten Fenster- oder Bereichsänderungen von den Bedienungshilfen zusammen mit den wichtigsten semantischen Informationen korrekt dargestellt werden:

ShareSheet(
    message = "Choose how to share this photo",
    modifier = Modifier
        .fillMaxWidth()
        .align(Alignment.TopCenter)
        .semantics { paneTitle = "New bottom sheet" }
)

Weitere Informationen finden Sie unter paneTitle in Material 3.

Fehlerkomponenten

Bei anderen Inhaltstypen, z. B. fehlerhaften Komponenten, sollten Sie die wichtigsten semantischen Informationen für Nutzer mit Beeinträchtigungen erweitern. Wenn Sie Fehlerstatus definieren, können Sie die Dienste zur Barrierefreiheit über die error-Semantik informieren und erweiterte Fehlermeldungen bereitstellen.

In diesem Beispiel liest TalkBack den Hauptfehlertext, gefolgt von zusätzlichen, erweiterten Meldungen:

Error(
    errorText = "Fields cannot be empty",
    modifier = Modifier
        .semantics {
            error("Please add both email and password")
        }
)

Komponenten für die Fortschrittsüberwachung

Bei benutzerdefinierten Komponenten, die den Fortschritt verfolgen, sollten Sie Nutzer über Änderungen des Fortschritts informieren, einschließlich des aktuellen Fortschrittswerts, seines Bereichs und der Schrittweite. Sie können dies mithilfe der progressBarRangeInfo-Semantik tun. So werden Dienste zur Barrierefreiheit über Änderungen am Fortschritt informiert und können Nutzer entsprechend aktualisieren. Verschiedene Hilfstechnologien können auch unterschiedliche Möglichkeiten haben, auf eine zunehmende oder abnehmende Progression hinzuweisen.

ProgressInfoBar(
    modifier = Modifier
        .semantics {
            progressBarRangeInfo =
                ProgressBarRangeInfo(
                    current = progress,
                    range = 0F..1F
                )
        }
)

Listen- und Artikelinformationen

Bei benutzerdefinierten Listen und Rastern mit vielen Elementen kann es für Dienste zur Barrierefreiheit hilfreich sein, auch detailliertere Informationen zu erhalten, z. B. die Gesamtzahl der Elemente und Indexe.

Durch die Verwendung der Semantik collectionInfo und collectionItemInfo für die Liste bzw. die Elemente können Dienste zur Barrierefreiheit Nutzer in dieser langen Liste zusätzlich zu den semantischen Textinformationen darüber informieren, welchen Artikelindex sie in der gesamten Sammlung haben:

MilkyWayList(
    modifier = Modifier
        .semantics {
            collectionInfo = CollectionInfo(
                rowCount = milkyWay.count(),
                columnCount = 1
            )
        }
) {
    milkyWay.forEachIndexed { index, text ->
        Text(
            text = text,
            modifier = Modifier.semantics {
                collectionItemInfo =
                    CollectionItemInfo(index, 0, 0, 0)
            }
        )
    }
}

Statusbeschreibung

Eine zusammensetzbare Funktion kann ein stateDescription für die Semantik definieren, das vom Android-Framework verwendet wird, um den Status der zusammensetzbaren Funktion auszulesen. Ein umschaltbares Composeable kann beispielsweise den Status „angeklickt“ oder „nicht angeklickt“ haben. In einigen Fällen möchten Sie die Standardlabels für Statusbeschreibungen in Compose überschreiben. Dazu müssen Sie die Labels für die Zustandsbeschreibungen explizit angeben, bevor Sie ein Element 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() }
            )
    ) {
        /* ... */
    }
}

Benutzerdefinierte Aktionen

Benutzerdefinierte Aktionen können für komplexere Touchscreen-Gesten wie Wischen zum Schließen oder Ziehen und Droppen verwendet werden, da diese für Nutzer mit motorischen Beeinträchtigungen oder anderen Behinderungen eine Herausforderung darstellen können.

Wenn Sie die Wischgeste zum Schließen barrierefreier gestalten möchten, können Sie sie mit einer benutzerdefinierten Aktion verknüpfen und die Aktion zum Schließen und das Label dort übergeben:

SwipeToDismissBox(
    modifier = Modifier.semantics {
        // Represents the swipe to dismiss for accessibility
        customActions = listOf(
            CustomAccessibilityAction(
                label = "Remove article from list",
                action = {
                    removeArticle()
                    true
                }
            )
        )
    },
    state = rememberSwipeToDismissBoxState(),
    backgroundContent = {}
) {
    ArticleListItem()
}

Eine Bedienungshilfe wie TalkBack hebt dann die Komponente hervor und weist darauf hin, dass im Menü weitere Aktionen verfügbar sind, die die Wischgeste zum Schließen darstellen:

Visualisierung des TalkBack-Aktionsmenüs
Abbildung 4: Visualisierung des TalkBack-Aktionsmenüs.

Ein weiterer Anwendungsfall für benutzerdefinierte Aktionen sind lange Listen mit Elementen, für die mehrere Aktionen verfügbar sind. Es kann nämlich mühsam sein, für jedes einzelne Element alle Aktionen durchzugehen:

=Visualisierung der Navigation im Schalterzugriff auf dem Bildschirm
Abbildung 5: Visualisierung der Navigation im Schalterzugriff auf dem Bildschirm.

Sie können benutzerdefinierte Aktionen für den Container verwenden, um Aktionen aus der individuellen Navigation in ein separates Aktionsmenü zu verschieben. Das ist besonders hilfreich für interaktionsbasierte Hilfstechnologien wie Switch Access oder Voice Access.

ArticleListItemRow(
    modifier = Modifier
        .semantics {
            customActions = listOf(
                CustomAccessibilityAction(
                    label = "Open article",
                    action = {
                        openArticle()
                        true
                    }
                ),
                CustomAccessibilityAction(
                    label = "Add to bookmarks",
                    action = {
                        addToBookmarks()
                        true
                    }
                ),
            )
        }
) {
    Article(
        modifier = Modifier.clearAndSetSemantics { },
        onClick = openArticle,
    )
    BookmarkButton(
        modifier = Modifier.clearAndSetSemantics { },
        onClick = addToBookmarks,
    )
}

In diesen Fällen müssen Sie die ursprünglichen Semantiken der untergeordneten Elemente manuell mit dem Modifikator clearAndSetSemantics löschen, da Sie sie in benutzerdefinierte Aktionen verschieben.

Am Beispiel des Schalterzugriffs: Wenn Sie den Container auswählen, wird das Menü geöffnet und die verfügbaren verschachtelten Aktionen werden dort aufgelistet:

Switch Access-Highlight des Artikellistenelements
Abbildung 6: Schalterzugriff-Highlight des Artikellistenelements.
Visualisierung des Menüs „Aktionen“ für den Schalterzugriff
Abbildung 7 Visualisierung des Aktionsmenüs für den Schalterzugriff.

Semantische Struktur

Eine Komposition beschreibt die Benutzeroberfläche Ihrer App und wird durch Ausführen von Composeables erstellt. Die Komposition ist eine Baumstruktur, die aus den Kompositionen besteht, die Ihre Benutzeroberfläche beschreiben.

Neben der Komposition gibt es einen parallelen Baum, der Semantikbaum genannt wird. Dieser Baum beschreibt Ihre Benutzeroberfläche auf eine alternative Weise, die für Bedienungshilfen und das Testframework verständlich ist. Bedienungshilfen verwenden den Baum, um die App für Nutzer mit bestimmten Anforderungen zu beschreiben. Das Test-Framework verwendet den Baum, um mit Ihrer App zu interagieren und Aussagen dazu zu treffen. Der Semantikbaum enthält keine Informationen dazu, wie Sie Ihre Composeables zeichnen, sondern Informationen zur semantischen Bedeutung Ihrer Composeables.

Eine typische UI-Hierarchie und ihr semantisches Baumdiagramm
Abbildung 8. Eine typische UI-Hierarchie und ihr Semantikbaum.

Wenn Ihre App aus Compose-Grundlagen und Materialbibliotheks-Komponenten und ‑Modifizierern besteht, wird der Semantics-Baum automatisch für Sie ausgefüllt und generiert. Wenn Sie jedoch benutzerdefinierte Low-Level-Kompositen hinzufügen, müssen Sie deren Semantik manuell angeben. Es kann auch vorkommen, dass Ihr Baum die Bedeutung der Elemente auf dem Bildschirm nicht richtig oder vollständig darstellt. In diesem Fall können Sie den Baum anpassen.

Betrachten Sie beispielsweise diese benutzerdefinierte Kalender-Komposition:

Ein benutzerdefinierter Kalender mit auswählbaren Tageselementen
Abbildung 9. Ein benutzerdefinierter Kalender, der mit auswählbaren Tageselementen erstellt werden kann.

In diesem Beispiel wird der gesamte Kalender als einzelnes Low-Level-Komposit implementiert. Dabei wird das Layout-Komposit verwendet und direkt auf die Canvas gezeichnet. Andernfalls erhalten Dienste zur Barrierefreiheit nicht genügend Informationen zum Inhalt des Composeables und zur Auswahl des Nutzers im Kalender. Wenn ein Nutzer beispielsweise auf den Tag mit dem 17. Klickt, erhält das Framework für Barrierefreiheit nur die Beschreibungsinformationen für das gesamte Kalenderkontrollelement. In diesem Fall würde der TalkBack-Bedienungshilfendienst „Kalender“ oder, nur geringfügig besser, „Kalender für April“ ansagen und der Nutzer müsste sich fragen, welcher Tag ausgewählt wurde. Wenn Sie diese Komponente barrierefreier gestalten möchten, müssen Sie semantische Informationen manuell hinzufügen.

Zusammengeführter und nicht zusammengeführter Stammbaum

Wie bereits erwähnt, können für jedes Kompositionsfähige im UI-Baum null oder mehr semantische Eigenschaften festgelegt sein. Wenn für ein composable keine Semantikeigenschaften festgelegt sind, wird es nicht in den Semantikbaum aufgenommen. So enthält der Semantikbaum nur die Knoten, die tatsächlich semantische Bedeutung haben. Manchmal ist es jedoch sinnvoll, bestimmte untergeordnete Knotenbäume zusammenzuführen und als einen einzigen zu behandeln, um die Bedeutung der auf dem Bildschirm angezeigten Inhalte richtig zu vermitteln. So können Sie eine Gruppe von Knoten als Ganzes betrachten, anstatt jeden untergeordneten Knoten einzeln zu behandeln. Als Faustregel gilt: Jeder Knoten in diesem Baum stellt ein Element dar, das bei Verwendung von Diensten zur Barrierefreiheit fokussiert werden kann.

Ein Beispiel für ein solches Composeable ist Button. Sie können eine Schaltfläche als einzelnes Element betrachten, auch wenn sie mehrere untergeordnete Knoten enthält:

Button(onClick = { /*TODO*/ }) {
    Icon(
        imageVector = Icons.Filled.Favorite,
        contentDescription = null
    )
    Spacer(Modifier.size(ButtonDefaults.IconSpacing))
    Text("Like")
}

Im semantischen Baum werden die Eigenschaften der Nachkommen der Schaltfläche zusammengeführt und die Schaltfläche wird als einzelner Endknoten im Baum dargestellt:

Zusammengeführte Darstellung der Semantik eines einzelnen Blatts
Abbildung 10. Zusammengeführte Darstellung der Semantik eines einzelnen Blattes.

Mit Modifier.semantics (mergeDescendants = true) {} können Composables und Modifikatoren angeben, dass sie die semantischen Eigenschaften ihrer Nachkommen zusammenführen möchten. Wenn Sie diese Property auf true festlegen, werden die semantischen Properties zusammengeführt. Im Button-Beispiel verwendet das Button-kompositible Element intern den clickable-Modifikator, der diesen semantics-Modifikator enthält. Daher werden die untergeordneten Knoten der Schaltfläche zusammengeführt. In der Dokumentation zu Bedienungshilfen erfahren Sie, wann Sie das Verhalten beim Zusammenführen in Ihrem Composeable ändern sollten.

Diese Eigenschaft ist für mehrere Modifikatoren und Composeables in den Foundation- und Material Compose-Bibliotheken festgelegt. So werden beispielsweise die untergeordneten Elemente der Modifikatoren clickable und toggleable automatisch zusammengeführt. Außerdem werden die Nachkommen des ListItem-Kompositions-Elements zusammengeführt.

Baumstruktur prüfen

Der semantische Baum besteht aus zwei verschiedenen Bäumen. Es gibt einen zusammengeführten Semanticsbaum, in dem untergeordnete Knoten zusammengeführt werden, wenn mergeDescendants auf true gesetzt ist. Es gibt auch einen nicht zusammengeführten Semantics-Baum, bei dem die Zusammenführung nicht angewendet wird, aber alle Knoten intakt bleiben. Dienste für Barrierefreiheit verwenden den nicht zusammengeführten Baum und wenden ihre eigenen Zusammenführungsalgorithmen an, wobei die Eigenschaft mergeDescendants berücksichtigt wird. Das Test-Framework verwendet standardmäßig den zusammengeführten Baum.

Mit der printToLog()-Methode können Sie beide Bäume prüfen. Wie in den vorherigen Beispielen wird der zusammengeführte Baum standardmäßig protokolliert. Wenn Sie stattdessen den nicht zusammengeführten Baum drucken möchten, setzen Sie den Parameter useUnmergedTree des onRoot()-Abgleichs auf true:

composeTestRule.onRoot(useUnmergedTree = true).printToLog("MY TAG")

Im Layout-Inspektor können Sie sowohl den zusammengeführten als auch den nicht zusammengeführten Semantics-Baum anzeigen lassen, indem Sie im Ansichtsfilter die gewünschte Option auswählen:

Layout-Inspektionsansichtsoptionen, mit denen sowohl der zusammengeführte als auch der nicht zusammengeführte Semantikbaum angezeigt werden kann
Abbildung 11. Ansichtsoptionen für den Layout-Inspektor, mit denen sowohl der zusammengeführte als auch der nicht zusammengeführte Semantics-Baum angezeigt werden kann.

Für jeden Knoten im Baum werden im Layout-Inspektor sowohl die zusammengeführten als auch die für diesen Knoten festgelegten Semantiken im Bereich „Properties“ angezeigt:

Semantische Properties wurden zusammengeführt und festgelegt.
Abbildung 12. Semantische Eigenschaften wurden zusammengeführt und festgelegt.

Standardmäßig verwenden die Matcher im Test-Framework den zusammengeführten Semantics-Baum. Sie können mit einem Button interagieren, indem Sie den darin angezeigten Text abgleichen:

composeTestRule.onNodeWithText("Like").performClick()

Sie können dieses Verhalten überschreiben, indem Sie den Parameter useUnmergedTree der Übereinstimmungsregeln auf true festlegen, wie beim onRoot-Abgleich.

Stammbaum anpassen

Wie bereits erwähnt, können Sie bestimmte semantische Eigenschaften überschreiben oder löschen oder das Zusammenführungsverhalten des Baums ändern. Das ist besonders relevant, wenn Sie eigene benutzerdefinierte Komponenten erstellen. Wenn Sie die richtigen Eigenschaften und das Zusammenführungsverhalten nicht festlegen, ist Ihre App möglicherweise nicht barrierefrei und Tests verhalten sich möglicherweise anders als erwartet. Weitere Informationen zu Tests finden Sie im Testleitfaden.