ממשקי API של Material, Compose UI ו-Foundation מטמיעים ומציעים כברירת מחדל הרבה שיטות עבודה שקשורות לנגישות. הם מכילים סמנטיקה מובנית שמתאימה לתפקיד ולפונקציה הספציפיים שלהם. זה אומר שרוב התמיכה בנגישות ניתנת עם מעט עבודה נוספת או בלי עבודה נוספת בכלל.
שימוש בממשקי ה-API המתאימים למטרה המתאימה פירושו שרכיבים בדרך כלל מגיעים עם התנהגויות נגישות מוגדרות מראש שמכסות תרחישי שימוש סטנדרטיים. עם זאת, תמיד כדאי לבדוק אם ברירות המחדל האלה מתאימות לצורכי הנגישות שלכם. אם לא, Compose מספק דרכים לענות על דרישות ספציפיות יותר.
הבנת הסמנטיקה והתבניות של ברירת המחדל לנגישות בממשקי ה-API של Compose עוזרת לכם להשתמש בהם תוך התחשבות בנגישות. הוא גם עוזר לתמוך בנגישות ברכיבים נוספים בהתאמה אישית.
הגדלים המינימליים של יעדי המגע
כל אלמנט שמופיע במסך שמשתמשים יכולים ללחוץ עליו, לגעת בו או לבצע איתו אינטראקציה, צריך להיות גדול מספיק כדי לאפשר אינטראקציה אמינה. כשקובעים את הגודל של הרכיבים האלה, חשוב להגדיר את הגודל המינימלי ל-48dp כדי לפעול בצורה נכונה לפי הנחיות הנגישות של Material Design.
רכיבי Material – כמו Checkbox, RadioButton, Switch, Slider ו-Surface – מגדירים את הגודל המינימלי הזה באופן פנימי, אבל רק כשהרכיב יכול לקבל פעולות של משתמשים. לדוגמה, כשמגדירים ערך שאינו null לפרמטר onCheckedChange של Checkbox, תיבת הסימון כוללת מרווח פנימי כדי שהרוחב והגובה יהיו לפחות 48dp.
@Composable private fun CheckableCheckbox() { Checkbox(checked = true, onCheckedChange = {}) }
כשהפרמטר onCheckedChange מוגדר כ-null, המרווח הפנימי לא נכלל כי אי אפשר לבצע אינטראקציה ישירה עם הרכיב.
@Composable private fun NonClickableCheckbox() { Checkbox(checked = true, onCheckedChange = null) }
כשמטמיעים פקדי בחירה כמו Switch, RadioButton או Checkbox, בדרך כלל מעבירים את ההתנהגות הקליקבילית למאגר הורה על ידי הגדרת הקריאה החוזרת (callback) של הקליק בתוכן הקומפוזבילי ל-null, והוספת משתנה מסוג toggleable או selectable לתוכן הקומפוזבילי ההורה.
@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) } } }
כשהגודל של תוכן קומפוזבילי קליקבילי קטן מהגודל המינימלי של יעד המגע, Compose עדיין מגדיל את הגודל של יעד המגע. הוא עושה זאת על ידי הרחבת גודל יעד המגע מחוץ לגבולות של ה-composable.
בדוגמה הבאה יש רכיב Box קטן מאוד שניתן ללחוץ עליו. אזור יעד המגע מתרחב באופן אוטומטי מעבר לגבולות של Box, כך שהקשה ליד Box עדיין מפעילה את האירוע מסוג קליק.
@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) ) } }
כדי למנוע חפיפה אפשרית בין אזורי המגע של פונקציות קומפוזביליות שונות, תמיד צריך להשתמש בגודל מינימלי גדול מספיק לפונקציה הקומפוזבילית. בדוגמה הזו, המשמעות היא שימוש במשתנה sizeIn כדי להגדיר את הגודל המינימלי של התיבה הפנימית:
@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) ) } }
אם הרכיב חייב לשמור על גודל גלוי קטן יותר, השתמשו במקום זאת ב-modifier minimumInteractiveComponentSize מ-Material 3. הוא שומר לפחות 48dp של שטח פריסה בכל ממד וממרכז את הרכיב בשטח הזה, כך שהרכיב שומר על הגודל המצויר שלו והרכיבים שסביבו זזים כדי לפנות מקום. המשתנה לא מרחיב את יעד המגע עצמו: הוא משאיר מקום בפריסה להרחבה שתוארה קודם.
רכיבי Material כמו Checkbox משתמשים ב-modifier הזה באופן פנימי. צריך להוסיף אותו לפני כל משנה גודל, כי משנה גודל שמופיע לפניו מגביל את המקום שהוא יכול להקצות.
@Composable private fun MinimumInteractiveBox() { var clicked by remember { mutableStateOf(false) } Row(verticalAlignment = Alignment.CenterVertically) { Box( Modifier .clickable { clicked = !clicked } .minimumInteractiveComponentSize() .size(24.dp) .background(if (clicked) Color.DarkGray else Color.Black) ) Text("Option") } }
רכיבים גרפיים
כשמגדירים רכיב Composable מסוג Image או Icon, למסגרת של Android אין דרך אוטומטית להבין מה מוצג באפליקציה. צריך להעביר תיאור טקסטואלי של הרכיב הגרפי.
דמיינו מסך שבו המשתמש יכול לשתף את הדף הנוכחי עם חברים. במסך הזה יש סמל שיתוף שאפשר ללחוץ עליו:
על סמך הסמל בלבד, מסגרת Android לא יכולה לתאר אותו למשתמש עם לקות ראייה. ב-Android Framework צריך תיאור טקסטואלי נוסף של הסמל.
הפרמטר contentDescription מתאר רכיב גרפי. צריך להשתמש במחרוזת מותאמת לשוק המקומי, כי היא מוצגת למשתמש.
@Composable private fun ShareButton(onClick: () -> Unit) { IconButton(onClick = onClick) { Icon( imageVector = Icons.Filled.Share, contentDescription = stringResource(R.string.label_share) ) } }
חלק מהרכיבים הגרפיים הם דקורטיביים בלבד, ויכול להיות שלא תרצו להעביר אותם למשתמש. כשמגדירים את הפרמטר contentDescription לערך null, מציינים למסגרת של Android שלרכיב הזה אין פעולות או מצב משויכים.
@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) ) }
התג contentDescription מיועד בעיקר לרכיבים גרפיים, כמו תמונות. רכיבי Material, כמו Button או Text, והתנהגויות שניתן לבצע, כמו clickable או toggleable, כוללים סמנטיקה מוגדרת מראש שמתארת את ההתנהגות המהותית שלהם, וניתן לשנות אותם באמצעות ממשקי API אחרים של Compose.
אלמנטים אינטראקטיביים
ממשקי ה-API של Material ו-Foundation Compose יוצרים רכיבים בממשק המשתמש שהמשתמשים יכולים לקיים איתם אינטראקציה באמצעות ממשקי ה-API של מגדירי השינוי clickable ו-toggleable. רכיבים שאפשר לבצע איתם אינטראקציה יכולים להיות מורכבים מכמה אלמנטים, ולכן clickable ו-toggleable ממזגים את הסמנטיקה של הצאצאים שלהם כברירת מחדל, כדי שהרכיב יטופל כישות לוגית אחת.
לדוגמה, Button של Material יכול להכיל סמל צאצא וטקסט. במקום להתייחס לצאצאים כאל רכיבים נפרדים, רכיב Material
Button ממזג את הסמנטיקה של הצאצאים שלו כברירת מחדל, כדי ששירותי הנגישות יוכלו לקבץ אותם בהתאם:
באופן דומה, שימוש ב-modifier clickable גורם גם לרכיב קומפוזבילי למזג את הסמנטיקה של הצאצאים שלו לישות אחת, שנשלחת לשירותי הנגישות עם ייצוג פעולה תואם:
Row( // Uses `mergeDescendants = true` under the hood modifier = Modifier.clickable { openArticle() } ) { Icon( painter = painterResource(R.drawable.ic_logo), contentDescription = "Open", ) Text("Accessibility in Compose") }
אפשר גם להגדיר onClickLabel ספציפי ברכיב ההורה שניתן ללחוץ עליו כדי לספק מידע נוסף לשירותי הנגישות ולהציג את הפעולה בצורה ברורה יותר:
Row( modifier = Modifier .clickable(onClickLabel = "Open this article") { openArticle() } ) { Icon( painter = painterResource(R.drawable.ic_logo), contentDescription = "Open" ) Text("Accessibility in Compose") }
אם ניקח את TalkBack כדוגמה, מקש הצירוף clickable ותווית הקליק שלו יאפשרו ל-TalkBack לספק רמז לפעולה כמו "יש ללחוץ לחיצה כפולה כדי לפתוח את המאמר הזה", במקום המשוב הגנרי יותר שמוגדר כברירת מחדל, "יש ללחוץ לחיצה כפולה כדי להפעיל".
המשוב הזה משתנה בהתאם לסוג הפעולה. לחיצה ארוכה תספק רמז של TalkBack: "יש ללחוץ לחיצה כפולה ולהחזיק כדי", ואחריו תווית:
Row( modifier = Modifier .combinedClickable( onLongClickLabel = "Bookmark this article", onLongClick = { addToBookmarks() }, onClickLabel = "Open this article", onClick = { openArticle() }, ) ) {}
במקרים מסוימים, יכול להיות שלא תהיה לכם גישה ישירה ל-modifier של clickable (לדוגמה, כשהוא מוגדר בשכבה נמוכה יותר),אבל עדיין תרצו לשנות את תווית ההודעה מברירת המחדל. כדי לעשות את זה, צריך לפצל את ההגדרה של clickable משינוי ההודעה באמצעות המשתנה semantics והגדרת תווית הקליק שם, כדי לשנות את ייצוג הפעולה:
@Composable private fun ArticleList(openArticle: () -> Unit) { NestedArticleListItem( // Clickable is set separately, in a nested layer: onClickAction = openArticle, // Semantics are set here: modifier = Modifier.semantics { onClick( label = "Open this article", action = { // Not needed here: openArticle() true } ) } ) }
אין צורך להעביר את פעולת הקליק פעמיים. ממשקי API קיימים של Compose, כמו clickable או Button, מטפלים בזה בשבילכם. לוגיקת המיזוג מוודאת שהתווית והפעולה של מגדיר השינוי החיצוני ביותר נלקחות עבור המידע שקיים. בדוגמה הקודמת, ה-NestedArticleListItem מעביר באופן אוטומטי את פעולת הקליק של openArticle() לסמנטיקה של clickable. אפשר להשאיר את פעולת הקליק כ-null בפעולה השנייה של משתנה הסמנטיקה. עם זאת, תווית הקליק נלקחת מהמשנה הסמנטי השני onClick(label = "Open this document") כי היא לא הייתה קיימת בראשון.
יכול להיות שתיתקלו במקרים שבהם אתם מצפים שהסמנטיקה של הצאצאים תמוזג עם זו של ההורה, אבל זה לא קורה. מידע נוסף זמין במאמר מיזוג וניקוי.
רכיבים בהתאמה אישית
כשיוצרים רכיב בהתאמה אישית, כדאי לבדוק את ההטמעה של רכיב דומה בספריית Material או בספריות אחרות של Compose. לאחר מכן, מחקים או משנים את התנהגות הנגישות שלו לפי הצורך. לדוגמה, אם אתם מחליפים את Checkbox של Material בהטמעה משלכם, עיון בהטמעה הקיימת של Checkbox יזכיר לכם להוסיף את המשתנה triStateToggleable, שמטפל במאפייני הנגישות של הרכיב. בנוסף, כדאי להשתמש הרבה בשינויי Foundation, כי הם כוללים שיקולי נגישות מובנים ושיטות קיימות של "פיתוח נייטיב" שמוסברות בקטע הזה.
אפשר גם למצוא דוגמה לרכיב מתג בהתאמה אישית בקטע "ניקוי והגדרה של סמנטיקה", ומידע מפורט יותר על תמיכה בנגישות ברכיבים בהתאמה אישית בהנחיות ל-API.
מומלץ בשבילך
- הערה: טקסט הקישור מוצג כש-JavaScript מושבת
- נגישות ב-Compose
- בדיקת הפריסה של Compose