Concetti e implementazione di Jetpack Compose
Per aiutare gli utenti con esigenze di accessibilità, il framework Android ti consente di creare un servizio di accessibilità che possa presentare i contenuti delle app agli utenti e anche utilizzare le app per loro conto.
Android fornisce diversi servizi di accessibilità di sistema, tra cui:
- TalkBack: aiuta le persone ipovedenti o cieche. Annuncia i contenuti tramite una voce sintetizzata ed esegue azioni su un'app in risposta ai gesti dell'utente.
- Switch Access: aiuta le persone con disabilità motorie. Mette in evidenza gli elementi interattivi ed esegue azioni in risposta alla pressione di un pulsante da parte dell'utente. Consente di controllare il dispositivo utilizzando solo uno o due pulsanti.
Per aiutare le persone con esigenze di accessibilità a utilizzare la tua app, quest'ultima deve seguire le best practice descritte in questa pagina, che si basano sulle linee guida descritte in Rendere le app più accessibili.
Elementi delle etichette
È importante fornire agli utenti etichette utili e descrittive per ogni elemento UI interattivo dell'app. Ogni etichetta deve spiegare il significato e lo scopo di un determinato elemento. Gli screen reader come TalkBack possono annunciare queste etichette agli utenti.
Nella maggior parte dei casi, la descrizione di un elemento dell'interfaccia utente viene specificata nel file di risorse di layout che contiene l'elemento. In genere, le etichette vengono aggiunte utilizzando
l'attributo contentDescription, come spiegato nella guida per rendere le app
più accessibili. Nelle sezioni seguenti sono descritte diverse altre tecniche di etichettatura.
Elementi modificabili
Quando etichetti elementi modificabili, ad esempio
oggetti EditText, è utile mostrare
un testo che fornisca un esempio di input valido nell'elemento stesso, oltre a
rendere questo testo di esempio disponibile per gli screen reader. In queste situazioni, puoi
utilizzare l'attributo android:hint, come mostrato nel seguente snippet:
<!-- The hint text for en-US locale would be "Apartment, suite, or building". --> <EditText android:id="@+id/addressLine2" android:hint="@string/aptSuiteBuilding" ... />
In questa situazione, l'oggetto View deve avere l'attributo android:labelFor
impostato sull'ID dell'elemento EditText. Per ulteriori dettagli, consulta la sezione
seguente.
Coppie di elementi in cui uno descrive l'altro
È comune che un elemento EditText abbia un oggetto View corrispondente che descrive ciò che gli utenti devono inserire nell'elemento EditText. Puoi indicare questa relazione impostando
l'attributo android:labelFor dell'oggetto View.
Un esempio di etichettatura di queste coppie di elementi è riportato nello snippet seguente:
<!-- Label text for en-US locale would be "Username:" --> <TextView android:id="@+id/usernameLabel" ... android:text="@string/username" android:labelFor="@+id/usernameEntry" /> <EditText android:id="@+id/usernameEntry" ... /> <!-- Label text for en-US locale would be "Password:" --> <TextView android:id="@+id/passwordLabel" ... android:text="@string/password android:labelFor="@+id/passwordEntry" /> <EditText android:id="@+id/passwordEntry" android:inputType="textPassword" ... />
Elementi in una raccolta
Quando aggiungi etichette agli elementi di una raccolta, ogni etichetta deve essere univoca. In questo modo, i servizi di accessibilità del sistema possono fare riferimento a un solo elemento sullo schermo quando annunciano un'etichetta. Questa corrispondenza consente agli utenti di sapere quando scorrono l'interfaccia utente o quando spostano lo stato attivo su un elemento che hanno già scoperto.
In particolare, includi testo aggiuntivo o informazioni contestuali negli elementi all'interno dei layout riutilizzati, ad esempio gli oggetti RecyclerView, in modo che ogni elemento secondario sia identificato in modo univoco.
Per farlo, imposta la descrizione dei contenuti nell'ambito dell'implementazione dell'adattatore, come mostrato nello snippet di codice seguente:
Kotlin
data class MovieRating(val title: String, val starRating: Integer) class MyMovieRatingsAdapter(private val myData: Array<MovieRating>): RecyclerView.Adapter<MyMovieRatingsAdapter.MyRatingViewHolder>() { class MyRatingViewHolder(val ratingView: ImageView) : RecyclerView.ViewHolder(ratingView) override fun onBindViewHolder(holder: MyRatingViewHolder, position: Int) { val ratingData = myData[position] holder.ratingView.contentDescription = "Movie ${position}: " + "${ratingData.title}, ${ratingData.starRating} stars" } }
Java
public class MovieRating { private String title; private int starRating; // ... public String getTitle() { return title; } public int getStarRating() { return starRating; } } public class MyMovieRatingsAdapter extends RecyclerView.Adapter<MyAdapter.MyRatingViewHolder> { private MovieRating[] myData; public static class MyRatingViewHolder extends RecyclerView.ViewHolder { public ImageView ratingView; public MyRatingViewHolder(ImageView iv) { super(iv); ratingView = iv; } } @Override public void onBindViewHolder(MyRatingViewHolder holder, int position) { MovieRating ratingData = myData[position]; holder.ratingView.setContentDescription("Movie " + position + ": " + ratingData.getTitle() + ", " + ratingData.getStarRating() + " stars") } }
Gruppi di contenuti correlati
Se la tua app mostra diversi elementi dell'interfaccia utente che formano un gruppo naturale, ad esempio
i dettagli di un brano o gli attributi di un messaggio, disponi questi elementi all'interno di un
contenitore, che di solito è una sottoclasse di ViewGroup. Imposta l'attributo
android:screenReaderFocusable
dell'oggetto contenitore
su true e l'attributo
android:focusable
di ogni oggetto interno
su false. In questo modo, i servizi di accessibilità possono presentare le descrizioni dei contenuti degli elementi interni, uno dopo l'altro, in un unico annuncio.
Questo consolidamento degli elementi correlati aiuta gli utenti di tecnologie per la disabilità
a scoprire le informazioni sullo schermo in modo più efficiente.
Il seguente snippet contiene parti di contenuti correlate tra loro, quindi l'elemento contenitore, un'istanza di ConstraintLayout, ha l'attributo android:screenReaderFocusable impostato su true e gli elementi TextView interni hanno ciascuno l'attributo android:focusable impostato su false:
<!-- In response to a single user interaction, accessibility services announce both the title and the artist of the song. --> <ConstraintLayout android:id="@+id/song_data_container" ... android:screenReaderFocusable="true"> <TextView android:id="@+id/song_title" ... android:focusable="false" android:text="@string/my_song_title" /> <TextView android:id="@+id/song_artist" android:focusable="false" android:text="@string/my_songwriter" /> </ConstraintLayout>
Poiché i servizi di accessibilità annunciano le descrizioni degli elementi interni in un'unica espressione, è importante mantenere ogni descrizione il più breve possibile, trasmettendo comunque il significato dell'elemento.
Nota:in generale, devi evitare di creare una descrizione dei contenuti per un gruppo aggregando il testo dei relativi elementi secondari. In questo modo, la descrizione del gruppo diventa fragile e, quando il testo di un elemento secondario cambia, la descrizione del gruppo potrebbe non corrispondere più al testo visibile.
In un contesto di elenco o griglia, uno screen reader può consolidare il testo dei nodi di testo secondari di un elemento di elenco o griglia. È meglio evitare di modificare questo annuncio.
Gruppi nidificati
Se l'interfaccia della tua app presenta informazioni multidimensionali, ad esempio un elenco giorno per giorno degli eventi del festival, utilizza l'attributo android:screenReaderFocusable nei contenitori dei gruppi interni. Questo schema di etichettatura offre un buon
equilibrio tra il numero di annunci necessari per scoprire i contenuti
dello schermo e la durata di ciascun annuncio.
Il seguente snippet di codice mostra un metodo per etichettare i gruppi all'interno di gruppi più grandi:
<!-- In response to a single user interaction, accessibility services announce the events for a single stage only. --> <ConstraintLayout android:id="@+id/festival_event_table" ... > <ConstraintLayout android:id="@+id/stage_a_event_column" android:screenReaderFocusable="true"> <!-- UI elements that describe the events on Stage A. --> </ConstraintLayout> <ConstraintLayout android:id="@+id/stage_b_event_column" android:screenReaderFocusable="true"> <!-- UI elements that describe the events on Stage B. --> </ConstraintLayout> </ConstraintLayout>
Intestazioni all'interno del testo
Alcune app utilizzano i titoli per riassumere i gruppi di testo visualizzati sullo schermo. Se
un determinato elemento View rappresenta un'intestazione, puoi indicarne lo scopo
per i servizi di accessibilità impostando l'attributo
android:accessibilityHeading su
true.
Gli utenti dei servizi di accessibilità possono scegliere di navigare tra i titoli anziché tra i paragrafi o tra le parole. Questa flessibilità migliora l'esperienza di navigazione del testo.
Titoli dei riquadri di accessibilità
In Android 9 (livello API 28) e versioni successive, puoi fornire titoli accessibili per i riquadri di una schermata. Ai fini dell'accessibilità, un riquadro è una parte visivamente distinta di una finestra, ad esempio i contenuti di un frammento. Affinché i servizi di accessibilità comprendano il comportamento simile a una finestra di un riquadro, assegna titoli descrittivi ai riquadri della tua app. I servizi di accessibilità possono quindi fornire informazioni più dettagliate agli utenti quando l'aspetto o i contenuti di un riquadro cambiano.
Per specificare il titolo di un riquadro, utilizza l'attributo
android:accessibilityPaneTitle, come mostrato nel seguente snippet:
<!-- Accessibility services receive announcements about content changes that are scoped to either the "shopping cart view" section (top) or "browse items" section (bottom) --> <MyShoppingCartView android:id="@+id/shoppingCartContainer" android:accessibilityPaneTitle="@string/shoppingCart" ... /> <MyShoppingBrowseView android:id="@+id/browseItemsContainer" android:accessibilityPaneTitle="@string/browseProducts" ... />
Elementi decorativi
Se un elemento della tua UI esiste solo a scopo di spaziatura o aspetto visivo, imposta l'attributo
android:importantForAccessibility
su "no".
Aggiungere azioni di accessibilità
È importante consentire agli utenti dei servizi di accessibilità di eseguire facilmente tutti i flussi utente all'interno dell'app. Ad esempio, se un utente può scorrere un elemento in un elenco, questa azione può essere esposta anche ai servizi di accessibilità, in modo che gli utenti abbiano un modo alternativo per completare lo stesso flusso utente.
Rendere accessibili tutte le azioni
Un utente di TalkBack, Voice Access o Switch Access potrebbe aver bisogno di modi alternativi per completare determinati flussi utente all'interno dell'app. Per le azioni associate a gesti come il trascinamento o gli scorrimenti, la tua app può esporre le azioni in modo accessibile agli utenti dei servizi di accessibilità.
Utilizzando le azioni di accessibilità, l'app può fornire modi alternativi per completare un'azione.
Ad esempio, se la tua app consente agli utenti di scorrere un elemento, puoi anche esporre la funzionalità tramite un'azione di accessibilità personalizzata, come questa:
Kotlin
ViewCompat.addAccessibilityAction( // View to add accessibility action itemView, // Label surfaced to user by an accessibility service getText(R.id.archive) ) { _, _ -> // Same method executed when swiping on itemView archiveItem() true }
Java
ViewCompat.addAccessibilityAction( // View to add accessibility action itemView, // Label surfaced to user by an accessibility service getText(R.id.archive), (view, arguments) -> { // Same method executed when swiping on itemView archiveItem(); return true; } );
With the custom accessibility action implemented, users can access the action through the actions menu.
Make available actions understandable
When a view supports actions such as touch & hold, an accessibility service such as TalkBack announces it as "Double tap and hold to long press."
This generic announcement doesn't give the user any context about what a touch & hold action does.
To make this announcement more descriptive, you can replace the accessibility action’s announcement like so:
Kotlin
ViewCompat.replaceAccessibilityAction( // View that contains touch & hold action itemView, AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_LONG_CLICK, // Announcement read by TalkBack to surface this action getText(R.string.favorite), null )
Java
ViewCompat.replaceAccessibilityAction( // View that contains touch & hold action itemView, AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_LONG_CLICK, // Announcement read by TalkBack to surface this action getText(R.string.favorite), null );
This results in TalkBack announcing "Double tap and hold to favorite," helping users understand the purpose of the action.
Extend system widgets
Note: When you design your app's UI, use or extend
system-provided widgets that are as far down Android's class hierarchy as
possible. System-provided widgets that are far down the hierarchy already
have most of the accessibility capabilities your app needs. It's easier
to extend these system-provided widgets than to create your own from the more
generic View,
ViewCompat,
Canvas, and
CanvasCompat
classes.
If you must extend View or Canvas directly, which
might be necessary for a highly customized experience or a game level, see
Make custom views more
accessible.
This section uses the example of implementing a special type of
Switch called TriSwitch while following
best practices around extending system widgets. A TriSwitch
object works similarly to a Switch object, except that each instance of
TriSwitch allows the user to toggle among three possible states.
Extend from far down the class hierarchy
The Switch object inherits from several framework UI classes in its hierarchy:
View ↳ TextView ↳ Button ↳ CompoundButton ↳ Switch
È preferibile che la nuova classe TriSwitch estenda direttamente la classe Switch. In questo modo, il framework di accessibilità di Android fornisce la maggior parte delle funzionalità di accessibilità di cui ha bisogno la classe TriSwitch:
- Azioni di accessibilità:informazioni per il sistema su come i servizi di accessibilità possono emulare ogni possibile input dell'utente eseguito su un oggetto
TriSwitch. (Ereditato daView.) - Eventi di accessibilità:informazioni per i servizi di accessibilità su ogni
possibile modo in cui l'aspetto di un oggetto
TriSwitchpuò cambiare quando lo schermo si aggiorna. (Ereditato daView.) - Caratteristiche:dettagli su ogni oggetto
TriSwitch, ad esempio i contenuti di qualsiasi testo visualizzato. (Ereditato daTextView.) - Informazioni sullo stato: descrizione dello stato attuale di un oggetto
TriSwitch, ad esempio "selezionato" o "deselezionato". (Ereditato daCompoundButton.) - Descrizione testuale dello stato:spiegazione testuale di ciò che rappresenta ogni stato. (Ereditato da
Switch.)
Questo comportamento di Switch e delle relative superclassi è quasi identico a quello degli oggetti TriSwitch. Pertanto, l'implementazione può
concentrarsi sull'espansione del numero di stati possibili da due a tre.
Definisci eventi personalizzati
Quando estendi un widget di sistema, probabilmente modifichi un aspetto del modo in cui gli utenti interagiscono con quel widget. È importante definire queste modifiche all'interazione in modo che i servizi di accessibilità possano aggiornare il widget dell'app come se l'utente interagisse direttamente con il widget.
Una linea guida generale è che per ogni callback basato sulla visualizzazione che sostituisci,
devi anche ridefinire l'azione di accessibilità corrispondente sostituendo
ViewCompat.replaceAccessibilityAction().
Nei test dell'app, puoi convalidare il comportamento di queste azioni ridefinite chiamando
ViewCompat.performAccessibilityAction().
Come può funzionare questo principio per gli oggetti TriSwitch
A differenza di un normale oggetto Switch, toccando un oggetto TriSwitch si passa
a tre possibili stati. Pertanto, l'azione di accessibilità ACTION_CLICK corrispondente
deve essere aggiornata:
Kotlin
class TriSwitch(context: Context) : Switch(context) { // 0, 1, or 2 var currentState: Int = 0 private set init { updateAccessibilityActions() } private fun updateAccessibilityActions() { ViewCompat.replaceAccessibilityAction(this, ACTION_CLICK, action-label) { view, args -> moveToNextState() }) } private fun moveToNextState() { currentState = (currentState + 1) % 3 } }
Java
public class TriSwitch extends Switch { // 0, 1, or 2 private int currentState; public int getCurrentState() { return currentState; } public TriSwitch() { updateAccessibilityActions(); } private void updateAccessibilityActions() { ViewCompat.replaceAccessibilityAction(this, ACTION_CLICK, action-label, (view, args) -> moveToNextState()); } private void moveToNextState() { currentState = (currentState + 1) % 3; } }
Risorse aggiuntive
Per scoprire di più su come rendere la tua app più accessibile, consulta le seguenti risorse aggiuntive: