Passaggi principali per migliorare l'accessibilità di Compose

Per aiutare le persone con esigenze di accessibilità a usare correttamente la tua app, progetta la tua per supportare i principali requisiti di accessibilità.

Valutare le dimensioni minime dei touch target

Ogni elemento visualizzato sullo schermo che è possibile selezionare, toccare o con cui è possibile interagire deve abbastanza grande da consentire un'interazione affidabile. Quando scegli le dimensioni di questi elementi, assicurati di imposta le dimensioni minime su 48 dp per rispettare correttamente il Material Design linee guida sull'accessibilità.

Componenti dei materiali, come Checkbox, RadioButton, Switch, Slider e Surface: imposta internamente questa dimensione minima, ma solo quando il componente può ricevere azioni dell'utente. Ad esempio, quando Checkbox ha il parametro onCheckedChange è impostato su un valore diverso da null, la casella di controllo include per avere una larghezza e un'altezza di almeno 48 dp.

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

Se il parametro onCheckedChange è impostato su null, la spaziatura interna non è perché non è possibile interagire direttamente con il componente.

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

Figura 1. Una casella di controllo senza spaziatura interna.

Quando implementi controlli di selezione come Switch, RadioButton o Checkbox, in genere rimuovi il comportamento cliccabile a un contenitore principale, imposta il callback clic sul componibile in null e aggiungi toggleable selectable al componibile principale.

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

Quando le dimensioni di un componibile cliccabile sono inferiori al touch target minimo , Compose aumenta comunque le dimensioni del touch target. per farlo espandendo le dimensioni del touch target al di fuori dei confini del componibile.

Il seguente esempio contiene un elemento Box cliccabile molto piccolo. Il touch target si espande automaticamente oltre i confini di Box, quindi toccare accanto a Box attiva comunque l'evento di clic.

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

Per evitare possibili sovrapposizioni tra aree di tocco di diversi componibili, utilizzare una dimensione minima sufficientemente grande per il componibile. Nell'esempio, significa utilizzare il tasto di modifica sizeIn per impostare la dimensione minima per la scatola interna:

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

Aggiungi etichette clic

Puoi utilizzare un'etichetta clic per aggiungere un significato semantico al comportamento del clic di un componibili. Le etichette clic descrivono cosa succede quando l'utente interagisce con il componibili. I servizi di accessibilità usano le etichette clic per descrivere l'app a: utenti con esigenze specifiche.

Imposta l'etichetta clic trasmettendo un parametro nel modificatore clickable:

@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
        )
    ) {
        // ..
    }
}

In alternativa, se non hai accesso al tasto di modifica cliccabile, imposta l'etichetta clic nel modificatore semantics:

@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)
        }
    ) {
        // ..
    }
}

Descrivere gli elementi visivi

Quando definisci un componibile Image o Icon, non esiste modo automatico al framework Android di capire cos'è visualizzazione. Devi trasmettere una descrizione testuale dell'elemento visivo.

Immagina una schermata in cui l'utente può condividere la pagina corrente con gli amici. Questo schermo contiene un'icona di condivisione cliccabile:

Una striscia di icone cliccabili, con

Basandosi solo sull'icona, il framework Android non è in grado di descriverla a un con disabilità uditiva. Il framework Android richiede un'ulteriore descrizione testuale l'icona.

Il parametro contentDescription descrive un elemento visivo. Utilizza un perché è visibile all'utente.

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

Alcuni elementi visivi sono puramente decorativi e potresti non voler comunicare per l'utente. Se imposti il parametro contentDescription su null, indicare al framework Android che questo elemento non è associato azioni o lo stato desiderato.

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

Sei tu a decidere se un determinato elemento visivo necessita di un contentDescription. Chiediti se l'elemento trasmette informazioni che l'utente dovrà eseguire la propria attività. In caso contrario, è preferibile lasciare la descrizione.

Unisci elementi

I servizi di accessibilità come TalkBack e Switch Access consentono agli utenti di spostare lo stato attivo tra gli elementi sullo schermo. È importante che gli elementi siano incentrati livello di dettaglio corretto. Quando ogni singolo elemento componibile di basso livello sul tuo schermo viene e in modo indipendente, gli utenti devono interagire molto per spostarsi sullo schermo. Se gli elementi si uniscono in modo troppo aggressivo, gli utenti potrebbero non capire quale che gli elementi appartengano

Quando applichi un modificatore clickable a un componibile, Compose unisce automaticamente tutti gli elementi contenuti nel componibile. Questo vale anche per ListItem l'unione degli elementi di un elemento dell'elenco e l'accessibilità e servizi li vedono come un unico elemento.

È possibile avere un insieme di componenti componibili che formano un gruppo logico, ma gruppo non cliccabile o parte di un elemento dell'elenco. Vuoi continuare a usare l'accessibilità per visualizzarli come un unico elemento. Ad esempio, immagina un componibile mostra l'avatar di un utente, il suo nome e alcune informazioni aggiuntive:

Un gruppo di elementi dell'interfaccia utente che include il nome di un utente. Il nome è selezionato.

Puoi consentire a Compose di unire questi elementi utilizzando l'mergeDescendants nel modificatore semantics. In questo modo, i servizi di accessibilità selezionare solo l'elemento unito e tutte le proprietà semantiche dei discendenti vengono uniti.

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

I servizi di accessibilità ora si concentrano sull'intero container, unendoli i relativi contenuti:

Un gruppo di elementi dell'interfaccia utente che include il nome di un utente. Tutti gli elementi vengono selezionati insieme.

Aggiungi azioni personalizzate

Dai un'occhiata al seguente elemento dell'elenco:

Un tipico elemento dell'elenco, contenente il titolo di un articolo, l'autore e l'icona dei preferiti.

Quando utilizzi uno screen reader come TalkBack per ascoltare ciò che viene visualizzato nella schermata iniziale, viene selezionato prima l'intero elemento e poi l'icona dei preferiti.

L'elemento dell'elenco, con tutti gli elementi selezionati insieme.

La voce dell'elenco, con solo l'icona dei preferiti selezionata

In un elenco lungo, questo può diventare molto ripetitivo. Un approccio migliore consiste nel definisci un'azione personalizzata che consenta a un utente di aggiungere l'elemento ai preferiti. Aspetti da tenere presenti che devi anche rimuovere esplicitamente il comportamento dell'icona dei preferiti per assicurarsi che non sia selezionato dal servizio di accessibilità. Questo si ottiene utilizzando il tasto di modifica 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 { }
        )
    }
}

Descrivere lo stato di un elemento

Un componibile può definire una stateDescription per la semantica, Il framework Android utilizza per leggere lo stato in cui si trova il componibile. Per Ad esempio, un componibile attivabile può essere sia in formato "selezionato" o "deselezionata" stato. In alcuni casi, potresti voler sostituire la descrizione dello stato predefinita utilizzate da Compose. Puoi farlo specificando esplicitamente lo stato prima di definire un componibile come attivabile:

@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() }
            )
    ) {
        /* ... */
    }
}

Definisci intestazioni

A volte le app mostrano molti contenuti su una sola schermata in un contenitore scorrevole. Ad esempio, una schermata potrebbe mostrare l'intero contenuto di un articolo che l'utente sta leggendo:

Screenshot di un post del blog, con il testo dell'articolo in un contenitore scorrevole.

Gli utenti con esigenze di accessibilità hanno difficoltà a navigare in una schermata di questo tipo. Per aiutare di navigazione, indica quali elementi sono intestazioni. Nell'esempio precedente, ogni può essere definito come intestazione per l'accessibilità. Alcune come TalkBack, consentono agli utenti di navigare direttamente in heading.

In Compose, indichi che un componibile è un'intestazione definendone il Proprietà semantics:

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

Gestire elementi componibili personalizzati

Ogni volta che sostituisci determinati componenti Material nella tua app con devi tenere presenti le considerazioni sull'accessibilità.

Supponiamo che tu stia sostituendo il materiale Checkbox con la tua implementazione. Potresti dimenticare di aggiungere il modificatore triStateToggleable, che gestisce le proprietà di accessibilità per questo componente.

Come regola generale, considera l'implementazione del componente in la libreria Material e imitare qualsiasi comportamento di accessibilità che riesci a trovare. Inoltre, fai un uso intensivo dei modificatori di base, anziché a livello di UI che includono considerazioni sull'accessibilità pronte all'uso.

Testa l'implementazione del componente personalizzato con più dei servizi di accessibilità per verificarne il comportamento.

Risorse aggiuntive