Semantik

Neben den primären Informationen, die ein Compose-Element enthält, z. B. ein Textstring eines Text-Compose-Elements, 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. Sie bieten zusätzlichen Kontext zu Compose-Elementen für Dienste wie Bedienungshilfen, Autofill und Tests. Ein Kamerasymbol ist beispielsweise visuell nur ein Bild, aber die semantische Bedeutung könnte „Foto aufnehmen“ sein.

Wenn Sie die entsprechende Semantik mit den entsprechenden Compose APIs kombinieren, stellen Sie Bedienungshilfen so viele Informationen wie möglich über Ihre Komponente zur Verfügung. Diese entscheiden dann, wie sie dem Nutzer präsentiert wird.

Die Material- und Compose-UI- und Foundation-APIs enthalten integrierte Semantik, die ihrer spezifischen Rolle und Funktion entspricht. Sie können diese Semantik 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 Composables. Die zusammensetzbare Funktion Text enthält beispielsweise das semantische Attribut text, da dies die Bedeutung dieser zusammensetzbaren Funktion ist. Ein Icon enthält ein contentDescription-Attribut (falls vom Entwickler festgelegt), das die Bedeutung des Symbols in Textform wiedergibt.

Überlegen Sie, wie Semantik-Properties die Bedeutung einer komponierbaren Funktion vermitteln. Ziehen Sie einen Switch in Betracht. So sieht es für den Nutzer aus:

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

Um die Bedeutung dieses Elements zu beschreiben, könnten Sie Folgendes sagen: „Dies ist ein Schalter, ein umschaltbares Element im Zustand ‚Ein‘. Sie können darauf klicken, um damit zu interagieren.

Genau dafür werden die Semantik-Eigenschaften verwendet. Der Semantikknoten dieses Switch-Elements enthält die folgenden Attribute, wie im Layout Inspector dargestellt:

Layout Inspector mit den Semantikattributen eines Switch-Composables
Abbildung 2: Layout Inspector mit den Semantikattributen eines Switch-Composables.

Role gibt den Typ des Elements an. Der StateDescription beschreibt, wie auf den Status „An“ verwiesen werden soll. Standardmäßig ist dies eine lokalisierte Version des Wortes „An“, kann aber je nach Kontext spezifischer formuliert werden (z. B. „Aktiviert“). ToggleableState ist der aktuelle Status des Schalters. Das Attribut OnClick verweist auf die Methode, die für die Interaktion mit diesem Element verwendet wird.

Wenn Sie die Semantik-Eigenschaften der einzelnen komponierbaren Funktionen in Ihrer App im Blick behalten, eröffnen sich Ihnen viele leistungsstarke Möglichkeiten:

  • Bedienungshilfen verwenden die Eigenschaften, um die auf dem Bildschirm angezeigte Benutzeroberfläche darzustellen und Nutzern die Interaktion mit ihr zu ermöglichen. Für das Switch-Composable könnte TalkBack Folgendes ansagen: „An; Schalter; zum Umschalten doppeltippen“. Der Nutzer kann doppelt auf das Display tippen, um den Schalter zu deaktivieren.
  • Das Test-Framework verwendet die Attribute, um Knoten zu finden, mit ihnen zu interagieren und Assertions zu erstellen:
    val mySwitch = SemanticsMatcher.expectValue(
        SemanticsProperties.Role, Role.Switch
    )
    composeTestRule.onNode(mySwitch)
        .performClick()
        .assertIsOff()

Bei Composables und Modifikatoren, die auf der Foundation-Bibliothek von Compose basieren, werden die relevanten Properties standardmäßig festgelegt. Optional können Sie diese Eigenschaften manuell ändern, um die Unterstützung der Bedienungshilfen für bestimmte Anwendungsfälle zu verbessern, oder die Strategie zum Zusammenführen oder Löschen Ihrer Composables ändern.

Wenn Sie Bedienungshilfen den spezifischen Inhaltstyp Ihrer Komponente signalisieren möchten, können Sie verschiedene Semantiken anwenden. Diese Ergänzungen unterstützen die vorhandenen semantischen Hauptinformationen und helfen Bedienungshilfen, die Darstellung, Ansage oder Interaktion mit Ihrer Komponente zu optimieren.

Eine vollständige Liste der Semantik-Properties finden Sie im Objekt SemanticsProperties. Eine vollständige Liste der möglichen Aktionen für Bedienungshilfen finden Sie im Objekt SemanticsActions.

Überschriften

Apps enthalten oft Bildschirme mit Rich Content, z. B. lange Artikel oder Nachrichtenseiten, die normalerweise in verschiedene Unterabschnitte 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 besonderen Anforderungen an die Barrierefreiheit haben möglicherweise Schwierigkeiten, sich auf einem solchen Bildschirm zurechtzufinden. Um die Navigation zu verbessern, ermöglichen einige Bedienungshilfen eine einfachere Navigation direkt zwischen Abschnitten oder Überschriften. Dazu müssen Sie angeben, dass Ihre Komponente ein heading ist, indem Sie die Semantik-Property definieren:

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

Warnungen und Pop-ups

Wenn es sich bei der Komponente um eine Benachrichtigung oder ein Pop-up handelt, z. B. ein Snackbar, sollten Sie Bedienungshilfen signalisieren, dass eine neue Struktur oder Aktualisierungen von Inhalten an Nutzer weitergegeben werden können.

Meldungsähnliche Komponenten können mit der semantischen Eigenschaft liveRegion gekennzeichnet werden. So können Bedienungshilfen den Nutzer automatisch über Änderungen an dieser Komponente oder ihren untergeordneten Elementen benachrichtigen:

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

In den meisten Fällen, in denen die Aufmerksamkeit der Nutzer nur kurz auf Warnungen oder wichtige sich ändernde Inhalte auf dem Bildschirm gelenkt werden soll, solltest du liveRegionMode.Polite verwenden.

Sie sollten liveRegion.Assertive sparsam verwenden, um störendes Feedback zu vermeiden. Sie sollte in Situationen verwendet werden, in denen es wichtig ist, dass Nutzer auf zeitkritische Inhalte aufmerksam gemacht werden:

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. Countdown-Timer, um Nutzer nicht mit ständigem Feedback zu überfordern.

Fensterähnliche Komponenten

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

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

Weitere Informationen finden Sie unter So verwendet Material 3 paneTitle für seine Komponenten.

Fehlerkomponenten

Bei anderen Inhaltstypen, z. B. fehlerähnlichen Komponenten, sollten Sie die wichtigsten semantischen Informationen für Nutzer mit besonderen Anforderungen an die Barrierefreiheit erweitern. Wenn Sie Fehlerstatus definieren, können Sie die Bedienungshilfen über die Semantik von error informieren und erweiterte Fehlermeldungen bereitstellen.

In diesem Beispiel liest TalkBack die Hauptfehlertextinformationen und dann zusätzliche, erweiterte Nachrichten vor:

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

Komponenten zur Fortschrittsüberwachung

Bei benutzerdefinierten Komponenten, die den Fortschritt verfolgen, möchtest du Nutzer möglicherweise über Änderungen am Fortschritt informieren, einschließlich des aktuellen Fortschrittswerts, des Bereichs und der Schrittgröße. Dazu können Sie die Semantik von progressBarRangeInfo verwenden. So werden Bedienungshilfen über Fortschrittsänderungen informiert und können Nutzer entsprechend informieren. Verschiedene Hilfstechnologien können auch unterschiedliche Möglichkeiten haben, auf eine zunehmende und abnehmende Progression hinzuweisen.

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

Listen- und Artikelinformationen

In benutzerdefinierten Listen und Rastern mit vielen Elementen kann es für Bedienungshilfen hilfreich sein, detailliertere Informationen zu erhalten, z. B. die Gesamtzahl der Elemente und Indexe.

Wenn Sie die Semantik collectionInfo und collectionItemInfo für die Liste und die Elemente in dieser langen Liste verwenden, können Bedienungshilfen die Nutzer zusätzlich zu den textuellen semantischen Informationen darüber informieren, an welchem Elementindex sie sich in der gesamten Sammlung befinden:

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 eine stateDescription für die Semantik definieren, die das Android-Framework verwendet, um den Status der zusammensetzbaren Funktion auszulesen. Beispielsweise kann eine umschaltbare komponierbare Funktion entweder den Zustand „Aktiviert“ oder „Deaktiviert“ haben. In einigen Fällen möchten Sie möglicherweise die von Compose verwendeten Standardlabels für die Statusbeschreibung überschreiben. Dazu können Sie die Labels für die Statusbeschreibung explizit angeben, 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() }
            )
    ) {
        /* ... */
    }
}

Benutzerdefinierte Aktionen

Benutzerdefinierte Aktionen können für komplexere Touchscreen-Gesten wie „Wischen zum Schließen“ oder „Drag & Drop“ verwendet werden, da diese für Nutzer mit motorischen Einschränkungen oder anderen Behinderungen schwierig sein können.

Damit die Wischgeste zum Schließen leichter zugänglich ist, kannst du sie mit einer benutzerdefinierten Aktion verknüpfen und die Schließaktion 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()
}

Ein Bedienungshilfendienst wie TalkBack hebt die Komponente dann hervor und weist darauf hin, dass in ihrem Menü weitere Aktionen verfügbar sind, die die Aktion „Zum Schließen wischen“ 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 mehr Aktionen verfügbar sind. Es kann für Nutzer mühsam sein, jede Aktion für jedes einzelne Element einzeln durchzugehen:

=Visualisierung der Navigation mit Schalterzugriff auf dem Bildschirm
Abbildung 5. Visualisierung der Navigation auf dem Bildschirm mit Schalterzugriff.

Wenn Sie die Navigation verbessern möchten, was besonders für interaktionsbasierte Hilfstechnologien wie Schalterzugriff oder Voice Access hilfreich ist, können Sie benutzerdefinierte Aktionen für den Container verwenden, um Aktionen aus der einzelnen Durchquerung in ein separates Aktionsmenü zu verschieben:

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üngliche Semantik der untergeordneten Elemente manuell mit dem Modifikator clearAndSetSemantics löschen, da Sie sie in benutzerdefinierte Aktionen verschieben.

Am Beispiel des Schalterzugriffs wird das Menü geöffnet, wenn der Container ausgewählt wird. Dort werden die verfügbaren verschachtelten Aktionen aufgelistet:

Schalterzugriff-Hervorhebung des Artikellistenelements
Abbildung 6: Hervorhebung des Artikellistenelements durch den Schalterzugriff.
Visualisierung des Aktionsmenüs für den Schalterzugriff.
Abbildung 7. Visualisierung des Aktionsmenüs für den Schalterzugriff.

Semantikbaum

Eine Composition beschreibt die Benutzeroberfläche Ihrer App und wird durch das Ausführen von Composables erstellt. Die Composition ist eine Baumstruktur, die aus den Composables besteht, die Ihre UI beschreiben.

Neben der Komposition gibt es einen parallelen Baum, den sogenannten Semantikbaum. Dieser Baum beschreibt Ihre Benutzeroberfläche auf eine alternative Weise, die für Bedienungshilfen und das Test-Framework verständlich ist. Bedienungshilfen verwenden die Baumstruktur, um die App für Nutzer mit besonderen Anforderungen zu beschreiben. Das Test-Framework verwendet die Baumstruktur, um mit Ihrer App zu interagieren und Aussagen über sie zu treffen. Der Semantikbaum enthält keine Informationen darüber, wie Ihre Composables gezeichnet werden, sondern Informationen zur semantischen Bedeutung Ihrer Composables.

Eine typische UI-Hierarchie und der zugehörige Semantikbaum
Abbildung 8. Eine typische UI-Hierarchie und der zugehörige Semantikbaum.

Wenn Ihre App aus Composeables und Modifikatoren aus der Compose Foundation- und Material-Bibliothek besteht, wird der Semantikbaum automatisch für Sie ausgefüllt und generiert. Wenn Sie jedoch benutzerdefinierte Low-Level-Kompositionen hinzufügen, müssen Sie die Semantik manuell angeben. Es kann auch Situationen geben, in denen der Baum die Bedeutung der Elemente auf dem Bildschirm nicht korrekt oder vollständig darstellt. In diesem Fall können Sie den Baum anpassen.

Sehen Sie sich zum Beispiel diese benutzerdefinierte Kalender-Komposition an:

Ein benutzerdefiniertes Kalender-Composable mit auswählbaren Tageselementen
Abbildung 9. Ein benutzerdefiniertes Kalender-Composable mit auswählbaren Tageselementen.

In diesem Beispiel wird der gesamte Kalender als einzelnes Low-Level-Composable implementiert. Dazu wird das Layout-Composable verwendet und direkt in das Canvas gezeichnet. Wenn du nichts weiter unternimmst, erhalten Bedienungshilfen nicht genügend Informationen über den Inhalt der zusammensetzbaren Funktion und die Auswahl des Nutzers im Kalender. Wenn ein Nutzer beispielsweise auf den Tag mit der Zahl 17 klickt, erhält das Framework für Barrierefreiheit im Internet nur die Beschreibungsinformationen für das gesamte Kalendersteuerelement. In diesem Fall würde der TalkBack-Bedienungshilfe-Dienst „Kalender“ oder, nur geringfügig besser, „April-Kalender“ ansagen und der Nutzer müsste sich fragen, welcher Tag ausgewählt wurde. Damit diese zusammensetzbare Funktion barrierefreier wird, müssen Sie semantische Informationen manuell hinzufügen.

Zusammengeführter und nicht zusammengeführter Baum

Wie bereits erwähnt, können für jede komponierbare Funktion im UI-Baum null oder mehr Semantikattribute festgelegt sein. Wenn für eine komponierbare Funktion keine Semantikattribute festgelegt sind, wird sie nicht in den Semantikbaum aufgenommen. So enthält der Semantikbaum nur die Knoten, die tatsächlich semantische Bedeutung haben. Manchmal ist es jedoch hilfreich, bestimmte Unterbäume von Knoten zusammenzuführen und als einen Knoten zu behandeln, um die richtige Bedeutung des auf dem Bildschirm Angezeigten zu vermitteln. So können Sie eine Gruppe von Knoten als Ganzes betrachten, anstatt jeden untergeordneten Knoten einzeln zu behandeln. Als Faustregel gilt, dass jeder Knoten in diesem Baum ein fokussierbares Element darstellt, wenn Bedienungshilfen verwendet werden.

Ein Beispiel für eine solche zusammensetzbare Funktion ist Button. Sie können eine Schaltfläche als einzelnes Element betrachten, auch wenn sie mehrere untergeordnete Knoten enthalten kann:

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

In der Semantikstruktur werden die Attribute der untergeordneten Elemente der Schaltfläche zusammengeführt und die Schaltfläche wird als einzelner Blattknoten in der Struktur dargestellt:

Zusammengeführte Darstellung der Semantik eines einzelnen Blatts
Abbildung 10. Zusammengeführte Semantikdarstellung für ein einzelnes Blatt.

Composables und Modifikatoren können angeben, dass sie die Semantik-Properties ihrer untergeordneten Elemente zusammenführen möchten, indem sie Modifier.semantics (mergeDescendants = true) {} aufrufen. Wenn Sie diese Property auf true setzen, werden die Semantik-Properties zusammengeführt. Im Beispiel Button verwendet die komponierbare Funktion Button intern den Modifikator clickable, der diesen Modifikator semantics enthält. Daher werden die untergeordneten Knoten der Schaltfläche zusammengeführt. In der Dokumentation zur Barrierefreiheit im Internet erfahren Sie, wann Sie das Zusammenführungsverhalten in Ihrer zusammensetzbaren Funktion ändern sollten.

Diese Property ist für mehrere Modifikatoren und Composables in den Foundation- und Material Compose-Bibliotheken festgelegt. Beispielsweise werden bei den Modifikatoren clickable und toggleable die untergeordneten Elemente automatisch zusammengeführt. Außerdem werden die untergeordneten Elemente des ListItem-Kompositums zusammengeführt.

Baum prüfen

Der Semantikbaum besteht eigentlich aus zwei verschiedenen Bäumen. Es gibt einen zusammengeführten Semantikbaum, in dem untergeordnete Knoten zusammengeführt werden, wenn mergeDescendants auf true gesetzt ist. Es gibt auch einen nicht zusammengeführten Semantikbaum, bei dem die Zusammenführung nicht angewendet wird, sondern jeder Knoten intakt bleibt. Bedienungshilfen verwenden den nicht zusammengeführten Baum und wenden ihre eigenen Zusammenführungsalgorithmen an, wobei das Attribut mergeDescendants berücksichtigt wird. Das Test-Framework verwendet standardmäßig den zusammengeführten Baum.

Sie können beide Bäume mit der Methode printToLog() prüfen. Standardmäßig und wie in den vorherigen Beispielen wird der zusammengeführte Baum protokolliert. Wenn Sie stattdessen den nicht zusammengeführten Baum ausgeben möchten, setzen Sie den Parameter useUnmergedTree des Matchers onRoot() auf true:

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

Mit dem Layout Inspector können Sie sowohl die zusammengeführte als auch die nicht zusammengeführte Semantikstruktur anzeigen lassen, indem Sie im Ansichtsfilter die gewünschte Option auswählen:

Anzeigeoptionen für den Layout Inspector, mit denen sowohl der zusammengeführte als auch der nicht zusammengeführte Semantikbaum angezeigt werden kann
Abbildung 11. Anzeigeoptionen für den Layout Inspector, mit denen sowohl der zusammengeführte als auch der nicht zusammengeführte Semantikbaum angezeigt werden kann.

Für jeden Knoten im Baum zeigt der Layout Inspector im Steuerfeld „Eigenschaften“ sowohl die zusammengeführte Semantik als auch die für diesen Knoten festgelegte Semantik an:

Semantik-Properties zusammengeführt und festgelegt
Abbildung 12. Semantikattribute zusammengeführt und festgelegt.

Standardmäßig verwenden die Abgleiche im Testing Framework den zusammengeführten Semantikbaum. Daher können Sie 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 Abgleiche auf true setzen, wie beim Abgleich onRoot.

Baum anpassen

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