Vlastní contentDescription místo viditelného textu tlačítka (Jetpack Compose)
Někdy viditelný text prvku (např. název s počtem v závorce) nezní čtečce
přečtený nahlas přirozeně, nebo chcete odečítači předat bohatší informaci,
než kolik je vidět na obrazovce. Výchozí sémantika Compose prostě přečte to,
co je uvnitř — pro úplné nahrazení je potřeba Modifier.clearAndSetSemantics,
který ale zahodí sémantiku celého podstromu, včetně akce kliknutí, kterou by
jinak sám vystavil clickable.
1. Špatně — spoléhat na výchozí přečtení viditelného textu
Row(
modifier = Modifier
.fillMaxWidth()
.clickable { onClick() }
.padding(horizontal = 16.dp, vertical = 14.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(icon, contentDescription = null)
Spacer(modifier = Modifier.width(16.dp))
Text("Všechny položky (42)")
}
TalkBack přečte přesně to, co je vidět — "Všechny položky, otevírající závorka, 42, zavírající závorka" (přesná podoba závisí na syntetizátoru), místo srozumitelné věty o počtu nepřečtených.
2. Správně — clearAndSetSemantics s vlastním popisem
@Composable
fun DashboardRow(icon: ImageVector, title: String, unreadCount: Int, focusRequester: FocusRequester, onClick: () -> Unit) {
val view = LocalView.current
val countText = if (unreadCount > 999) "999+" else unreadCount.toString()
val displayTitle = if (unreadCount > 0) "$title ($countText)" else title
val description = if (unreadCount > 0) "$title, $unreadCount nepřečtených" else "$title, vše přečteno"
Row(
modifier = Modifier.fillMaxWidth()
.focusRequester(focusRequester)
.clickable { view.clickSound(); onClick() }
.padding(horizontal = 16.dp, vertical = 14.dp)
.clearAndSetSemantics {
contentDescription = description
role = Role.Button
this.onClick { view.clickSound(); onClick(); true }
},
verticalAlignment = Alignment.CenterVertically
) {
Icon(icon, contentDescription = null, tint = MaterialTheme.colorScheme.primary)
Spacer(modifier = Modifier.width(16.dp))
Text(displayTitle, style = MaterialTheme.typography.bodyLarge, modifier = Modifier.weight(1f))
}
}
clearAndSetSemantics zahodí sémantiku celého podstromu (včetně toho, co
by do ní jinak přispěl vnořený Text) a nahradí ji přesně tím, co je v
bloku. Proto description používá jiný, plynulejší tvar ("42 nepřečtených"
/ "vše přečteno") než viditelný text ("(42)") — čtečka ho nikdy nespojuje s
viditelným textem, jde o dvě úplně nezávislé věci.
3. Kde se to používá
V mé aplikaci pro čtení RSS kanálů tímhle způsobem funguje úplně každá položka na domovské obrazovce — "Všechny položky", "S hvězdičkou", každá složka i každý jednotlivý kanál. Viditelný text má počet v závorce, přístupný popis má plnou větu ("42 nepřečtených" / "vše přečteno").
Takový řádek je zároveň klikatelný, takže potřebuje i zvukovou odezvu na dotyk — viz Zvuk při tapnutí na interaktivní prvky (Jetpack Compose).
Proč to takhle je
clearAndSetSemanticszahazuje sémantiku VŠECH potomků, ne jen doplňuje — proto seTextuvnitř nikdy nepřečte souběžně sdescription. Kdyby se místo toho použil obyčejnýModifier.semantics { contentDescription = ... }(bez "clearAndSet"), TalkBack by přečetl obojí za sebou — vlastní popis i viditelný text znovu.role = Role.Buttonuvnitř bloku —clearAndSetSemanticszahodí i roli, kterou by řádek jinak mohl mít, takže je potřeba ji nastavit znovu, jinak TalkBack řádek neohlásí jako tlačítko.this.onClick { ... }uvnitř bloku — ze stejného důvodu:Modifier.clickablesám o sobě vystavuje do accessibility stromu i akci "kliknutí", aleclearAndSetSemanticsji zahodí spolu se vším ostatním. Bez ručního doplnění by dvojité poklepání v TalkBacku na řádku přestalo fungovat, i když by běžný dotyk (přesclickable) fungoval dál — proto seonClick()volá na obou místech, vclickablei uvnitřclearAndSetSemantics.- Přístupný popis a viditelný text se mohou lišit obsahem i formulací — nemusí to být to samé, jen jinak formátované; přístupná verze může nést informaci navíc (např. "vše přečteno" pro nulový počet, což viditelný text vůbec neřeší).
Pravidla
- WCAG 4.1.2 (Název, role, hodnota) — přístupný název, role tlačítka i (v tomto případě prostřednictvím textu) stav musí být programově zjistitelné jako jeden konzistentní celek.
Shrnutí: Modifier.clearAndSetSemantics { contentDescription = ...; role = Role.Button; onClick {...} } úplně nahradí, co čtečka o prvku řekne —
včetně role a akce kliknutí, které by jinak zajistil clickable. Používá
se, když má přístupný popis nést jiné/bohatší informace než holý viditelný
text, a je potřeba zabránit tomu, aby se ten viditelný text ještě přečetl
navíc.
Komentáře
Zatím zde nejsou žádné komentáře. Buďte první, kdo napíše svůj názor.
Přidat komentář k „Vlastní contentDescription místo viditelného textu tlačítka (Jetpack Compose)“