כששירותי נגישות מנווטים בין רכיבים במסך, חשוב שהרכיבים האלה יהיו מקובצים, מופרדים או אפילו מוסתרים ברמת הפירוט הנכונה. כשכל רכיב Composable ברמה נמוכה במסך מודגש בנפרד, המשתמשים צריכים לבצע הרבה אינטראקציות כדי לעבור בין חלקי המסך. אם הרכיבים מתמזגים בצורה אגרסיבית מדי, יכול להיות שהמשתמשים לא יבינו אילו רכיבים שייכים זה לזה מבחינה לוגית. אם יש רכיבים במסך שהם דקורטיביים בלבד, אפשר להסתיר אותם משירותי הנגישות. במקרים כאלה, אפשר להשתמש בממשקי API של Compose כדי למזג, לנקות ולהסתיר סמנטיקה.
מיזוג סמנטיקה
כשמחילים עיבוד מסוג clickable על פונקציה קומפוזבילית הורה, Compose ממזג באופן אוטומטי את כל רכיבי הצאצא שמתחתיו. כדי להבין איך רכיבים אינטראקטיביים של Compose Material ו-Foundation משתמשים באסטרטגיות מיזוג כברירת מחדל, עיינו בקטע רכיבים אינטראקטיביים.
הרבה פעמים רכיב מורכב מכמה רכיבים קומפוזביליים. יכול להיות שהרכיבים האלה יוצרים קבוצה לוגית, וכל אחד מהם מכיל מידע חשוב, אבל אתם עדיין רוצים ששירותי הנגישות יציגו אותם כרכיב אחד.
לדוגמה, נניח שיש פונקציה קומפוזבילית שמציגה את דמות הפרופיל של המשתמש, את השם שלו ומידע נוסף:
כדי לאפשר ל-Compose למזג את הרכיבים האלה, משתמשים בפרמטר mergeDescendants ב-modifier של הסמנטיקה. כך שירותי הנגישות מתייחסים לרכיב כישות אחת, וכל המאפיינים הסמנטיים של הצאצאים מתמזגים:
@Composable private fun PostMetadata(metadata: Metadata) { // Merge elements below for accessibility purposes Row(modifier = Modifier.semantics(mergeDescendants = true) {}) { Image( imageVector = Icons.Filled.AccountCircle, contentDescription = null // decorative ) Column { Text(metadata.author.name) Text("${metadata.date} • ${metadata.readTimeMinutes} min read") } } }
שירותי הנגישות מתמקדים עכשיו בכל המאגר בבת אחת, וממזגים את התוכן שלו:
לכל מאפיין סמנטי יש אסטרטגיית מיזוג מוגדרת. לדוגמה, המאפיין ContentDescription מוסיף לרשימה את כל הערכים של הצאצאים של ContentDescription. כדי לבדוק את אסטרטגיית המיזוג של מאפיין סמנטי, בודקים את ההטמעה של mergePolicy ב-SemanticsProperties.kt.
המאפיינים יכולים לקבל את הערך של ההורה או הצאצא, למזג את הערכים לרשימה או למחרוזת, לא לאפשר מיזוג בכלל ולזרוק חריגה במקום זאת, או כל אסטרטגיית מיזוג מותאמת אישית אחרת.
יש עוד מקרים שבהם אתם מצפים שהסמנטיקה של הצאצאים תמוזג עם זו של ההורה, אבל זה לא קורה. בדוגמה הבאה, יש לנו רכיב הורה של פריט ברשימה clickable עם רכיבי צאצא, ואנחנו מצפים שההורה ימזג את כולם:
@Composable private fun ArticleListItem( openArticle: () -> Unit, addToBookmarks: () -> Unit, ) { Row(modifier = Modifier.clickable { openArticle() }) { // Merges with parent clickable: Icon( painter = painterResource(R.drawable.ic_logo), contentDescription = "Article thumbnail" ) ArticleDetails() // Defies the merge due to its own clickable: BookmarkButton(onClick = addToBookmarks) } }
כשהמשתמש לוחץ על הפריט clickable Row, המאמר נפתח. בתוך האלמנט הזה יש אלמנט BookmarkButton שמאפשר להוסיף את המאמר לסימניות. כפתור Nest זה מוצג כלא ממוזג, בעוד ששאר התוכן של הצאצאים בשורה ממוזג:
Row. העץ שלא מוזג מכיל צמתים נפרדים לכל Text פונקציה קומפוזבילית.חלק מהרכיבים הקומפוזביליים לא מתמזגים באופן אוטומטי תחת הורה, כחלק מהעיצוב. רכיב הורה לא יכול למזג את רכיבי הצאצא שלו אם גם הם מתמזגים, בין אם בגלל הגדרה מפורשת של mergeDescendants = true ובין אם בגלל שהם רכיבים שמתמזגים בעצמם, כמו כפתורים או רכיבים שניתן ללחוץ עליהם. אם תבינו איך ממשקי API מסוימים מתמזגים או לא מתמזגים, תוכלו לנפות באגים בהתנהגויות בלתי צפויות.
כדאי להשתמש במיזוג כשאלמנטים צאצאים מהווים קבוצה הגיונית וסבירה תחת ההורה שלהם. אבל אם צריך לשנות באופן ידני את הצאצאים המקוננים או להסיר את הסמנטיקה שלהם, יכול להיות שממשקי API אחרים יתאימו יותר לצרכים שלכם (לדוגמה, clearAndSetSemantics).
ניקוי והגדרה של סמנטיקה
אם צריך למחוק או להחליף לחלוטין את המידע הסמנטי, אפשר להשתמש ב-API העוצמתי clearAndSetSemantics.
כשרכיב צריך לנקות את הסמנטיקה שלו ושל הצאצאים שלו, משתמשים ב-API הזה עם פונקציית למבדה ריקה. כדי לשנות את הסמנטיקה, צריך לכלול את התוכן החדש בתוך הלמבדה.
חשוב לדעת: כשמנקים באמצעות למבדה ריקה, הסמנטיקה שנמחקה לא נשלחת לאף צרכן שמשתמש במידע הזה, כמו נגישות, מילוי אוטומטי או בדיקה. כשמחליפים תוכן באמצעות clearAndSetSemantics{/*semantic information*/}, הסמנטיקה החדשה מחליפה את כל הסמנטיקה הקודמת של הרכיב ושל הצאצאים שלו.
הדוגמה הבאה ממחישה רכיב מתג בהתאמה אישית, שמיוצג על ידי שורה אינטראקטיבית עם סמל וטקסט:
// Developer might intend this to be a toggleable. // Using `clearAndSetSemantics`, on the Row, a clickable modifier is applied, // a custom description is set, and a Role is applied. @Composable fun FavoriteToggle() { val checked = remember { mutableStateOf(true) } Row( modifier = Modifier .toggleable( value = checked.value, onValueChange = { checked.value = it } ) .clearAndSetSemantics { stateDescription = if (checked.value) "Favorited" else "Not favorited" toggleableState = ToggleableState(checked.value) role = Role.Switch }, ) { Icon( imageVector = Icons.Default.Favorite, contentDescription = null // not needed here ) Text("Favorite?") } }
למרות שהסמל והטקסט מכילים מידע סמנטי, הם לא מציינים יחד שאפשר להחליף את המצב של הרכיב הזה. מיזוג לא מספיק כי צריך לספק מידע נוסף על הרכיב.
מכיוון שקטע הקוד שלמעלה יוצר רכיב החלפה בהתאמה אישית, צריך להוסיף את יכולת ההחלפה, וגם את הסמנטיקה של stateDescription, toggleableState ו-role. כך, הסטטוס של הרכיב והפעולה שמשויכת אליו זמינים. לדוגמה, TalkBack מכריז "יש ללחוץ לחיצה כפולה כדי להחליף מצב" במקום "יש ללחוץ לחיצה כפולה כדי להפעיל".
כשמנקים את הסמנטיקה המקורית ומגדירים סמנטיקה חדשה וברורה יותר, שירותי הנגישות יכולים לראות שזה רכיב שניתן להחלפה, שיכול לשנות מצב.
כשמשתמשים ב-clearAndSetSemantics, חשוב לשים לב לדברים הבאים:
- השירותים לא מקבלים מידע כשה-API הזה מוגדר, ולכן עדיף להשתמש בו כמה שפחות.
- סוכני AI ושירותים דומים יכולים להשתמש במידע סמנטי כדי להבין את המסך, ולכן צריך למחוק אותו רק כשצריך.
- אפשר להגדיר סמנטיקה מותאמת אישית בתוך פונקציית הלמבדה של ה-API.
- סדר השינויים חשוב – ה-API הזה מוחק את כל הסמנטיקה שמופיעה אחרי המקום שבו הוא מופעל, בלי קשר לאסטרטגיות מיזוג אחרות.
הסתרת הסמנטיקה
במקרים מסוימים, אין צורך לשלוח רכיבים לשירותי הנגישות. למשל, אם המידע הנוסף שלהם מיותר מבחינת הנגישות, או אם הם מיועדים רק לקישוט חזותי ולא אינטראקטיביים. במקרים כאלה, אתם יכולים להסתיר רכיבים באמצעות ה-API של hideFromAccessibility.
בדוגמאות הבאות מוצגים רכיבים שאולי צריך להסתיר: סימן מים מיותר שמשתרע על פני רכיב, ותו שמשמש להפרדה דקורטיבית של מידע.
@Composable fun WatermarkExample( watermarkText: String, content: @Composable () -> Unit, ) { Box { WatermarkedContent() // Mark the watermark as hidden to accessibility services. WatermarkText( text = watermarkText, color = Color.Gray.copy(alpha = 0.5f), modifier = Modifier .align(Alignment.BottomEnd) .semantics { hideFromAccessibility() } ) } } @Composable fun DecorativeExample() { Text( modifier = Modifier.semantics { hideFromAccessibility() }, text = "A dot character that is used to decoratively separate information, like •" ) }
השימוש ב-hideFromAccessibility כאן מבטיח שסימן המים והקישוט מוסתרים משירותי הנגישות, אבל עדיין שומרים על הסמנטיקה שלהם לתרחישי שימוש אחרים, כמו בדיקה.
פירוט תרחישי השימוש
הטבלה הבאה מסכמת את תרחישי השימוש כדי להבין איך להבדיל בבירור בין ממשקי ה-API הקודמים:
- כשהתוכן לא מיועד לשימוש על ידי שירותי נגישות:
- השתמשו ב-
hideFromAccessibilityכשהתוכן עשוי להיות דקורטיבי או מיותר, אבל עדיין צריך לבדוק אותו. - משתמשים ב-
clearAndSetSemantics{}עם למבדה ריקה כשצריך לנקות את הסמנטיקה של ההורה והילדים בכל השירותים. - משתמשים ב-
clearAndSetSemantics{/*content*/}עם תוכן בתוך הלמבדה כשצריך להגדיר באופן ידני את הסמנטיקה של רכיב.
- השתמשו ב-
- כשהתוכן צריך להיות מטופל כישות אחת, וצריך את כל המידע של הצאצאים שלו כדי להיות שלם:
- שימוש במיזוג צאצאים סמנטיים.
מומלץ בשבילכם
- הערה: טקסט הקישור מוצג כש-JavaScript מושבת
- נגישות ב-Compose
- [Material Design 2 ב-Compose][19]
- בדיקת הפריסה של Compose