Wichtige Schritte zur Verbesserung der Barrierefreiheit der Funktion „Compose“

Damit Menschen mit Anforderungen an die Barrierefreiheit Ihre App erfolgreich verwenden können, sollten Sie sie so gestalten, dass sie wichtige Anforderungen an die Barrierefreiheit erfüllt.

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 beispielsweise der Parameter onCheckedChange einer Checkbox auf einen Wert ungleich Null gesetzt ist, schließt das Kästchen einen Innenrand ein, sodass eine Breite und Höhe von mindestens 48 dp erreicht wird.

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

Wenn der Parameter onCheckedChange auf null gesetzt ist, wird der Abstand nicht verwendet, da die Komponente nicht direkt interagiert werden kann.

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

Abbildung 1. Ein Kästchen ohne Abstand.

Wenn Sie Auswahlsteuerelemente wie Switch, RadioButton oder Checkbox implementieren, heben Sie normalerweise das anklickbare Verhalten auf einen übergeordneten Container an, setzen den Klick-Callback der zusammensetzbaren Funktion auf null und fügen der übergeordneten zusammensetzbaren Funktion den Modifikator toggleable oder selectable hinzu.

@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.

Das folgende Beispiel enthält ein sehr kleines anklickbares Box-Objekt. Der Berührungszielbereich wird automatisch über die Grenzen von Box hinaus erweitert. Das Tippen neben Box löst also weiterhin das Klickereignis aus.

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

Verwenden Sie für die zusammensetzbare Funktion immer eine ausreichend große Mindestgröße, um eine mögliche Überschneidung zwischen den Berührungsbereichen verschiedener zusammensetzbarer Funktionen zu vermeiden. Im Beispiel würde dies bedeuten, dass der sizeIn-Modifikator verwendet wird, um die Mindestgröße für das innere Feld festzulegen:

@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 der Nutzer mit der zusammensetzbaren Funktion interagiert. Bedienungshilfen verwenden Klicklabels, um die App für Nutzer mit bestimmten Anforderungen zu beschreiben.

Übergeben Sie im clickable-Modifikator 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, legen Sie alternativ das Klicklabel im Modifikator semantics fest:

@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 aus Image oder Icon definieren, kann das Android-Framework nicht automatisch verstehen, was die App gerade anzeigt. 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 es einem sehbehinderten Nutzer nicht beschreiben. Das Android-Framework benötigt eine zusätzliche Textbeschreibung des Symbols.

Der Parameter contentDescription beschreibt ein visuelles Element. Verwende einen lokalisierten String, so wie er für den Nutzer sichtbar ist.

@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 im 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 Nutzende 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 fokussiert ist, müssen Nutzer viel interagieren, um sich über den Bildschirm zu bewegen. Wenn Elemente zu aggressiv aufeinander abgestimmt werden, verstehen Nutzer möglicherweise nicht, welche Elemente zusammengehören.

Wenn Sie einen clickable-Modifikator auf eine zusammensetzbare Funktion anwenden, werden in der Funktion „Compose“ automatisch alle Elemente zusammengeführt, die in der zusammensetzbaren Funktion enthalten sind. 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 sehen können. Stellen Sie sich z. B. eine zusammensetzbare Funktion vor, die den Avatar eines Nutzers, seinen Namen und einige zusätzliche Informationen anzeigt:

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

Sie können festlegen, dass diese Elemente in „Compose“ zusammengeführt werden. Verwenden Sie dazu den Parameter mergeDescendants im Modifikator semantics. 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 besteht darin, 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 Modifikator 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 { }
        )
    }
}

Status eines Elements beschreiben

Eine zusammensetzbare Funktion kann eine stateDescription für die Semantik definieren, mit der das Android-Framework den Status ausliest, in dem sich die zusammensetzbare Funktion befindet. Eine schaltbare zusammensetzbare Funktion kann beispielsweise entweder den Status „aktiviert“ oder „nicht aktiviert“ haben. In einigen Fällen müssen Sie die in der Funktion „Compose“ verwendeten Labels für die Standardstatusbeschreibung überschreiben. Geben Sie dazu explizit die Labels für die Zustandsbeschreibung an, bevor Sie eine zusammensetzbare Funktion 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 haben Schwierigkeiten, auf einem solchen Bildschirm zu navigieren. Um die Navigation zu erleichtern, geben Sie an, welche Elemente Überschriften sind. Im vorherigen Beispiel könnte jeder Unterabschnittstitel als Überschrift für die Barrierefreiheit definiert werden. Bei einigen Bedienungshilfen wie TalkBack können Nutzer direkt von Überschrift zu Überschrift wechseln.

In der Funktion „Compose“ geben Sie an, dass eine zusammensetzbare Funktion eine Überschrift ist. Dazu definieren Sie die Eigenschaft semantics:

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

Benutzerdefinierte zusammensetzbare Funktionen verarbeiten

Wenn Sie bestimmte Material-Komponenten in Ihrer App durch benutzerdefinierte Versionen ersetzen, müssen Sie die Überlegungen zur Barrierefreiheit berücksichtigen.

Angenommen, Sie ersetzen das Material Checkbox durch Ihre eigene Implementierung. Sie könnten vergessen, den triStateToggleable-Modifikator hinzuzufügen, der die Bedienungshilfen dieser Komponente verwaltet.

Als Faustregel gilt: Sehen Sie sich die Implementierung der Komponente in der Materialbibliothek an und ahmen Sie alle verfügbaren Bedienungshilfen nach. 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 Komponenten mit verschiedenen Bedienungshilfen, um ihr Verhalten zu überprüfen.

Weitere Informationen