Přístupný přepínač (Switch) v Jetpack Compose
Přepínač a jeho popisek vedle sebe vypadají jako jeden ovládací prvek, ale
pokud jsou to dva samostatné composables, TalkBack je vidí jako dva
samostatné uzly. Uživatel se dotykem/gestem dostane na Switch a uslyší jen
"přepínač, zapnuto" nebo "přepínač, vypnuto" — bez popisku, bez kontextu, co
vlastně přepíná. Popisek se pak ohlásí zvlášť jako neinteraktivní text o krok
dál.
1. Špatně — Switch a Text vedle sebe (dvě samostatné položky)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Text("Zvuk při dosažení poslední položky")
Switch(checked = enabled, onCheckedChange = { enabled = it })
}
Text a Switch jsou tu dvě nezávislé komponenty, každá se svým vlastním
sémantickým uzlem. TalkBack je ohlásí odděleně — a protože Switch sám o
sobě žádný text nenese, dostane se k němu uživatel jen jako k "přepínači"
bez jakéhokoli popisu. Navíc reaguje na dotyk jen přesně nad grafikou
přepínače, ne nad celým řádkem.
2. Správně — sloučené do jednoho fokusovatelného kontejneru
@Composable
fun SwitchRow(
label: String,
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
) {
Row(
modifier = modifier
.fillMaxWidth()
.toggleable(
value = checked,
onValueChange = onCheckedChange,
role = Role.Switch,
),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Text(label)
Switch(checked = checked, onCheckedChange = null)
}
}
Modifier.toggleable(...) s role = Role.Switch udělá z celého Row
jeden sémantický uzel s rolí přepínače a jeho aktuálním stavem. Switch
uvnitř dostane onCheckedChange = null — díky tomu sám o sobě nevytváří
vlastní klikatelný/fokusovatelný uzel, takže nevznikne druhá, vnořená
položka se stejnou funkcí.
3. Kde se to v mých projektech používá
V mých aplikacích je tohle standardní vzor pro každý přepínač v nastavení —
sdílená komponenta (SwitchRow), přes kterou jde každý labelovaný přepínač,
žádný Row { Text(...); Switch(...) } napřímo. Stejný princip (celý řádek =
jeden interaktivní uzel, vnitřní komponenta s onClick = null) používám i
pro řádky s RadioButton přes Modifier.selectable(...).
Zvuková odezva na klepnutí (playSoundEffect) patří ke stejné skupině zvyků —
viz Zvuk při tapnutí na interaktivní prvky (Jetpack Compose).
Proč to takhle je
role = Role.Switchna celém řádku — TalkBack ohlásí "popisek, přepínač, zapnuto/vypnuto" jedním krokem, ne jako dvě oddělené zastávky.Switch(onCheckedChange = null)— vnitřní komponenta bez vlastního handleru nevytváří druhý klikatelný uzel. Kdyby handler zůstal i tady, TalkBack by na stejnou funkci narazil dvakrát: jednou na celém řádku, podruhé na vnořeném přepínači.- Celý řádek reaguje na dotyk — netýká se to jen odečítačů obrazovky; pomáhá to i lidem s menší přesností dotyku nebo třesem rukou, kteří by se jinak museli trefit přesně do malé grafiky přepínače.
- Popisek musí být obsahem toho samého kontejneru (
Textjako dítěRow), ne vedle něj mimotoggleablemodifier — jinak sémantika role a stavu nemá k čemu přiřadit název.
Pravidla
- WCAG 4.1.2 (Název, role, hodnota) — přepínač musí programově vystavovat svou roli, aktuální stav (zapnuto/vypnuto) i svůj název jako jeden celek, ne jako tři nesouvisející kusy informace.
Shrnutí: přepínač a jeho popisek patří do jednoho Modifier.toggleable(...)
kontejneru s role = Role.Switch, vnitřní Switch dostane
onCheckedChange = null. Bez toho TalkBack vidí dva prvky místo jednoho a
uživatel se dozví "přepínač" bez kontextu, co přepíná.
Komentáře
Zatím zde nejsou žádné komentáře. Buďte první, kdo napíše svůj názor.
Přidat komentář k „Přístupný přepínač (Switch) v Jetpack Compose“