Lliçó 4 de 10 · 12 min de lectura · Revisada el 26/10/2026
Android: TalkBack, contentDescription, semàntica de Compose i Switch Access
Estàs llegint una lliçó oberta. Matricula't per fer els tests i els exercicis, desar el teu progrés i aconseguir el certificat.
Matricula-m'hi · nivell 6 (119 €)A Android, l'accessibilitat passa pel servei d'accessibilitat: TalkBack, Switch Access, Accés per veu i altres consulten l'arbre de nodes AccessibilityNodeInfo que el framework construeix a partir de les vistes (sistema View) o de la semàntica (Jetpack Compose). Aquesta lliçó explica què ha d'exposar el desenvolupador en cada sistema, amb codi Kotlin, i com provar-ho amb TalkBack i Switch Access.
TalkBack: l'imprescindible
Activa TalkBack amb els dos botons de volum (drecera d'accessibilitat) o a Configuració > Accessibilitat. Gestos: lliscar a la dreta/esquerra per a l'element següent/anterior, doble toc per activar, lliscar amunt/avall per canviar la granularitat o el mode de lectura, el menú de TalkBack (toc amb tres dits o lliscar cap avall i després a la dreta) per a les accions. TalkBack anuncia per a cada node: descripció o text, estat (marcat, seleccionat, desplegat), rol («botó», «casella»), accions («toca dues vegades per activar») i, si n'hi ha, accions personalitzades al menú. Activa a TalkBack > Configuració per a desenvolupadors l'opció «Mostra la sortida de veu» per veure a la pantalla el que s'anuncia: imprescindible per auditar i per enregistrar evidències.
Sistema View (XML i Kotlin)
android:contentDescription: el nom accessible de les imatges i dels botons d'icona. Per a les imatges decoratives,@null; per a lesImageViewinformatives, un text descriptiu. ATextViewiButtonamb text no cal: el text és el nom.android:importantForAccessibility="no"per amagar un element al servei;"noHideDescendants"per amagar també els seus fills (per exemple, el contingut darrere d'un diàleg).android:labelForalTextViewque etiqueta unEditText; alternativamentTextInputLayoutambhint, que gestiona l'etiqueta correctament.android:minHeight/minWidthde 48dp per a l'àrea tàctil, oTouchDelegateper ampliar l'àrea sense canviar la mida visual.android:accessibilityHeading="true"(API 28) per als encapçalaments; TalkBack permet navegar-hi.android:accessibilityLiveRegion="polite"per a les zones el text de les quals canvia i s'ha d'anunciar (missatges d'estat, 4.1.3).announceForAccessibility()està desaconsellat i figura com a obsolet des de l'API 36 (Android 16): fes servir live regions o, per als canvis de finestra,setAccessibilityPaneTitle.android:accessibilityTraversalBefore/Afterper corregir l'ordre de recorregut quan la disposició visual i la lògica no coincideixen.- Unitats
spper a tot el text (s'escalen amb l'ajust del sistema); maidpni mides fixes al text;android:maxLinesamb compte iwrap_contenta les alçades. android:screenOrientation: no forcisportraitllevat que sigui essencial (1.3.4).
Per a una vista personalitzada (un gràfic, un control de puntuació), implementa un AccessibilityDelegate que empleni el node amb el rol, l'estat i les accions:
class PuntuacionView(context: Context) : View(context) {
var valor = 3
init {
isFocusable = true
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
override fun onInitializeAccessibilityNodeInfo(
host: View, info: AccessibilityNodeInfoCompat
) {
super.onInitializeAccessibilityNodeInfo(host, info)
info.className = SeekBar::class.java.name // rol: control ajustable
info.contentDescription = "Valoració"
info.stateDescription = "$valor de 5 estrelles"
info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD)
info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD)
}
override fun performAccessibilityAction(
host: View, action: Int, args: Bundle?
): Boolean = when (action) {
AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD -> { cambiar(+1); true }
AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD -> { cambiar(-1); true }
else -> super.performAccessibilityAction(host, action, args)
}
})
}
private fun cambiar(d: Int) {
valor = (valor + d).coerceIn(1, 5); invalidate()
ViewCompat.setStateDescription(this, "$valor de 5 estrelles")
}
}Punt clau
Abans d'escriure un delegat d'accessibilitat, pregunta't si pots fer servir un component estàndard (MaterialButton, Slider, CheckBox, Chip). Els components de Material ja exposen el rol, l'estat i les accions, i es mantenen al dia amb cada versió d'Android. La vista personalitzada és l'últim recurs.
Jetpack Compose: semàntica
A Compose no hi ha vistes: hi ha un arbre de semàntica construït pels composables. Els components de Material 3 (Button, Checkbox, Switch, TextField) aporten el rol i l'estat. El desenvolupador completa el que falta amb el modificador semantics:
@Composable
fun FilaMovimiento(m: Movimiento, onAbrir: () -> Unit, onCategorizar: () -> Unit) {
Row(
modifier = Modifier
.fillMaxWidth()
.heightIn(min = 48.dp)
.clickable(role = Role.Button, onClick = onAbrir) // rol i acció
.semantics(mergeDescendants = true) { // un sol node
contentDescription = "${m.concepto}, ${m.importeAccesible}, ${m.fechaLarga}"
customActions = listOf(
CustomAccessibilityAction("Categoritza") { onCategorizar(); true }
)
}
) {
Icon(painterResource(m.icono), contentDescription = null) // decoratiu
Column(Modifier.weight(1f)) {
Text(m.concepto, style = MaterialTheme.typography.bodyLarge)
Text(m.fechaFormateada, style = MaterialTheme.typography.bodySmall)
}
Text(m.importeFormateado)
}
}
// Encapçalament de secció
Text(
"Moviments de març",
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.semantics { heading() }
)
// Botó d'icona: sempre amb descripció
IconButton(onClick = onBuscar) {
Icon(Icons.Default.Search, contentDescription = "Cerca")
}
// Missatge d'estat que s'ha d'anunciar en canviar
Text(
estado,
modifier = Modifier.semantics { liveRegion = LiveRegionMode.Polite }
)Altres elements de l'API de semàntica: role = Role.Button (o Checkbox, Switch, Tab, Image) quan construeixes un control a mà; stateDescription per als estats («Desplegat»); clearAndSetSemantics { } per substituir completament la semàntica dels fills; isTraversalGroup = true i traversalIndex dins de semantics per controlar l'ordre de recorregut (Compose 1.5+); Modifier.minimumInteractiveComponentSize() (l'apliquen per defecte els components de Material) per garantir 48 dp; Modifier.focusRequester per moure el focus al primer error; Modifier.toggleable/selectable en lloc de clickable per als elements amb estat, perquè hi afegeixen el rol i l'estat automàticament.
Per a l'escalat del text, fes servir sp (els estils de tipografia de Material ja ho fan) i comprova-ho amb «Mida de la lletra» al màxim i «Mida de visualització» gran. Les alçades fixes i maxLines = 1 amb overflow = Ellipsis en textos essencials són l'error més comú.
Error freqüent
Posar contentDescription a tot, inclosos els textos i els contenidors, «perquè ho llegeixi TalkBack». El resultat és soroll: el text s'anuncia dues vegades, els contenidors es converteixen en elements enfocables buits i les imatges decoratives interrompen la lectura. La regla: descripció només als elements no textuals informatius; null als decoratius; agrupació amb mergeDescendants quan diversos textos formen una unitat.
Switch Access, Accés per veu i teclat
Switch Access permet fer servir Android amb un o dos commutadors: recorre els elements enfocables i accionables (o escaneja per files/punts) i activa el seleccionat. Requereix que tots els controls siguin focusable i clickable a nivell d'accessibilitat, que l'ordre sigui lògic i que no hi hagi accions només per gest. Accés per veu («Toca Cerca», «Mostra els números») fa servir el text visible o el contentDescription: els botons d'icona sense descripció són inabastables. Els teclats físics (molt utilitzats per persones amb discapacitat motriu) exigeixen android:focusable i nextFocusDown/Up coherents; a Compose, Modifier.focusable() i focusProperties. L'indicador de focus (2.4.7) ha de ser visible: els components de Material el pinten; als controls propis cal dibuixar-lo amb Modifier.onFocusChanged o un state_focused al drawable.
Ajustos del sistema que l'aplicació ha de respectar
- Suprimeix les animacions (Configuració > Accessibilitat): consulta
Settings.Global.ANIMATOR_DURATION_SCALEo deixa que les animacions del framework ho respectin. - Text d'alt contrast, Inversió de colors i Correcció de color: fes servir els colors del tema i comprova-ho en mode fosc.
- Temps per fer accions (
AccessibilityManager.getRecommendedTimeoutMillis): s'aplica als toasts, als snackbars i als missatges temporals (2.2.1). - Àudio mono i subtítols: per a les aplicacions amb vídeo, respecta les preferències de
CaptioningManager.
Exemple
Aplicació de cita prèvia d'un centre de salut: la llista de forats fa servir Chip seleccionables (rol i estat automàtics), el calendari personalitzat exposa cada dia amb role = Role.Button, un contentDescription amb la data completa i un stateDescription «Sense forats» o «3 forats»; en confirmar, un Text amb liveRegion anuncia «Cita confirmada per al 12 de maig a les 9:30», i el focus passa al botó «Afegeix al calendari». Tot funciona amb TalkBack, Switch Access i teclat.
Resum
- TalkBack anuncia la descripció, l'estat, el rol i les accions del node d'accessibilitat; «Mostra la sortida de veu» permet veure què s'anuncia.
- Views:
contentDescriptionnomés als no textuals informatius (@nullals decoratius),labelFor,accessibilityHeading,accessibilityLiveRegion, 48 dp,sp, sense forçar l'orientació. - Compose:
semantics { contentDescription, role, stateDescription, heading(), liveRegion, customActions },mergeDescendantsper agrupar,clearAndSetSemantics,traversalIndexper a l'ordre. - Prefereix els components de Material; els delegats i les semàntiques manuals són l'últim recurs.
- Switch Access, Accés per veu i el teclat exigeixen controls enfocables, amb nom i amb focus visible, i res d'accions només per gest.
T'ha servit aquesta lliçó?
Crea el teu compte i segueix el curs amb tot:
- El test d'aquesta lliçó (3 preguntes) per comprovar el que has après
- Exercicis pràctics amb pàgines reals
- El teu progrés desat: continues on ho vas deixar, des de qualsevol dispositiu
- Constància del curs amb les hores i certificat del nivell 6 quan aprovis l'examen
Entres amb el teu compte de Google o només amb el teu correu electrònic. No cal contrasenya.
Matricula-m'hi · nivell 6 (119 €)