שלבים מרכזיים לשיפור הנגישות של ההצעות לכתיבה

כדי לעזור לאנשים עם צורכי נגישות להשתמש באפליקציה, צריך לעצב שתומכת בדרישות הנגישות העיקריות.

כדאי להביא בחשבון את הגדלים המינימליים של משטחי המגע

כל אלמנט שמופיע במסך שמשתמשים יכולים ללחוץ עליו, לגעת בו או לבצע איתו אינטראקציה, צריך להיות שהוא מספיק גדול לאינטראקציה אמינה. כאשר משנים את הגודל של הרכיבים האלה, חשוב לוודא להגדיר את הגודל המינימלי ל-48dp כדי להתאים בצורה נכונה ל-Material Design הנחיות בנושא נגישות.

רכיבי חומר - כמו Checkbox, RadioButton, Switch, Slider וגם Surface – מגדירים את הגודל המינימלי הזה באופן פנימי, אבל רק כשהרכיב יכול לקבל פעולות משתמש. לדוגמה, כשיש ב-Checkbox הפרמטר onCheckedChange מוגדר לערך שאינו null, תיבת הסימון כוללת רוחב וגובה של 48dp לפחות.

@Composable
private fun CheckableCheckbox() {
    Checkbox(checked = true, onCheckedChange = {})
}

כשהפרמטר onCheckedChange מוגדר כ-null, המרווח הפנימי לא כי לא ניתן לקיים אינטראקציה ישירה עם הרכיב.

@Composable
private fun NonClickableCheckbox() {
    Checkbox(checked = true, onCheckedChange = null)
}

איור 1. תיבת סימון ללא מרווח פנימי.

כשמטמיעים בקרות בחירה כמו 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)
        }
    }
}

כשהגודל של תוכן קומפוזבילי קליקבילי קטן מיעד המגע המינימלי גודל, 'כתיבה' עדיין מגדיל את משטח המגע. היא עושה זאת באמצעות הרחבת גודל משטח המגע נמצא מחוץ לגבולות של התוכן הקומפוזבילי.

בדוגמה הבאה יש 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)
        )
    }
}

הוספת תוויות של קליקים

ניתן להשתמש בתווית קליק כדי להוסיף משמעות סמנטית להתנהגות הקליקים של קומפוזבילי. תוויות קליקים מתארות מה קורה כשהמשתמש מקיים אינטראקציה עם קומפוזבילי. שירותי הנגישות משתמשים בתוויות קליקים כדי לתאר את האפליקציה משתמשים עם צרכים ספציפיים.

כדי להגדיר את תווית הקליק, מעבירים את הפרמטר באמצעות מקש הצירוף clickable:

@Composable
private fun ArticleListItem(openArticle: () -> Unit) {
    Row(
        Modifier.clickable(
            // R.string.action_read_article = "read article"
            onClickLabel = stringResource(R.string.action_read_article),
            onClick = openArticle
        )
    ) {
        // ..
    }
}

לחלופין, אם אין לך גישה ללחצן הצירוף שניתן ללחוץ עליו, הגדר תווית הקליק במגביל הסמנטיקה:

@Composable
private fun LowLevelClickLabel(openArticle: () -> Boolean) {
    // R.string.action_read_article = "read article"
    val readArticleLabel = stringResource(R.string.action_read_article)
    Canvas(
        Modifier.semantics {
            onClick(label = readArticleLabel, action = openArticle)
        }
    ) {
        // ..
    }
}

תיאור של אלמנטים חזותיים

כשמגדירים תוכן קומפוזבילי Image או Icon, אין דרך אוטומטית ל-Android להבין מהי האפליקציה מוצגת. צריך להעביר תיאור טקסטואלי של האלמנט החזותי.

דמיינו מסך שבו המשתמש יכול לשתף את הדף הנוכחי עם חברים. הזה מכיל סמל שיתוף שניתן ללחוץ עליו:

רצועה של סמלים קליקביליים, עם

בהתבסס על הסמל בלבד, מסגרת Android לא יכולה לתאר אותה באופן חזותי משתמש עם מוגבלות. ל-framework של Android נדרש תיאור טקסט נוסף נוסף של הסמל.

הפרמטר contentDescription מתאר רכיב חזותי. שימוש בשפה מחרוזת, כפי שהיא גלויה למשתמש.

@Composable
private fun ShareButton(onClick: () -> Unit) {
    IconButton(onClick = onClick) {
        Icon(
            imageVector = Icons.Filled.Share,
            contentDescription = stringResource(R.string.label_share)
        )
    }
}

חלק מהאלמנטים הוויזואליים הם רק קישוטיים, וייתכן שלא תרצו לתקשר אותם למשתמש. כשמגדירים את הפרמטר contentDescription ל-null, לציין ל-framework של 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 תשאלו את עצמכם אם האלמנט מעביר מידע המשתמש יצטרך לבצע את המשימה שלו. אם לא, עדיף להשאיר להשמיע את התיאור.

מיזוג רכיבים

שירותי נגישות כמו Talkback ו'גישה באמצעות מתג' מאפשרים למשתמשים להעביר את המיקוד בין רכיבים שונים במסך. חשוב להתמקד באלמנטים רזולוציית הניווט. כשכל תוכן קומפוזבילי ברמה נמוכה שמופיע במסך מתמקדים באופן עצמאי, המשתמשים צריכים לבצע הרבה פעולות כדי לנוע על פני המסך. אם רכיבים מתמזגים באופן אגרסיבי מדי, ייתכן שהמשתמשים לא יבינו אלמנטים משויכים זה לזה

כשמחילים מגביל clickable על תוכן קומפוזבילי, צריך ללחוץ על 'כתיבה' התוכן הקומפוזבילי ממזג אוטומטית את כל הרכיבים שכלולים בו. התנאי הזה חל גם על ListItem אלמנטים של פריט ברשימה מתמזגים יחד, והנגישות רואה אותם כרכיב אחד.

יכול להיות שיש קבוצה של תכנים קומפוזביליים שיוצרים קבוצה לוגית, לא ניתן ללחוץ על הקבוצה או שהיא חלק מפריט ברשימה. עדיין יהיה צורך בנגישות כדי להציג אותם כרכיב אחד. לדוגמה, דמיינו תוכן קומפוזבילי מציג את הדמות של המשתמש, את השם שלו ומידע נוסף:

קבוצה של רכיבים בממשק המשתמש שכוללים את שם המשתמש. השם נבחר.

אפשר להפעיל את התכונה 'כתיבה' כדי למזג את הרכיבים האלה באמצעות mergeDescendants במגביל הצירוף semantics. כך, שירותי הנגישות בוחרים רק את הרכיב שמוזג ואת כל מאפייני הסמנטיקה של הצאצאים ממוזגות.

@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")
        }
    }
}

שירותי הנגישות מתמקדים עכשיו בכל הקונטיינרים, ומיזוגים התוכן שלהם:

קבוצה של רכיבים בממשק המשתמש שכוללים את שם המשתמש. כל הרכיבים נבחרים יחד.

הוספת פעולות מותאמות אישית

נבחן את הפריט הבא ברשימה:

פריט אופייני ברשימה, המכיל כותרת מאמר, מחבר וסמל של סימניה.

כאשר משתמשים בקורא מסך כמו Talkback כדי לשמוע מה מוצג במסך, היא בוחרת קודם את הפריט כולו ואז את סמל הסימנייה.

הפריט ברשימה, יחד עם כל הרכיבים.

הפריט ברשימה, רק סמל הסימנייה נבחר

ברשימה ארוכה, הדבר עשוי להדאיג ביותר. גישה טובה יותר היא מגדירים פעולה מותאמת אישית שמאפשרת למשתמש להוסיף את הפריט לסימניות. חשוב לזכור צריך להסיר באופן מפורש את ההתנהגות של סמל הסימנייה עצמו כדי לוודא שהוא לא נבחר על ידי שירות הנגישות. הזה מבוצעת באמצעות הצירוף clearAndSetSemantics:

@Composable
private fun PostCardSimple(
    /* ... */
    isFavorite: Boolean,
    onToggleFavorite: () -> Boolean
) {
    val actionLabel = stringResource(
        if (isFavorite) R.string.unfavorite else R.string.favorite
    )
    Row(
        modifier = Modifier
            .clickable(onClick = { /* ... */ })
            .semantics {
                // Set any explicit semantic properties
                customActions = listOf(
                    CustomAccessibilityAction(actionLabel, onToggleFavorite)
                )
            }
    ) {
        /* ... */
        BookmarkButton(
            isBookmarked = isFavorite,
            onClick = onToggleFavorite,
            // Clear any semantics properties set on this node
            modifier = Modifier.clearAndSetSemantics { }
        )
    }
}

תיאור מצב של יסוד

תוכן קומפוזבילי יכול להגדיר stateDescription לסמנטיקה, ה-framework של Android משתמש כדי להקריא את המצב שבו נמצא התוכן הקומפוזבילי. עבור לדוגמה, תוכן קומפוזבילי שניתן להחליף יכול להיות בשדה 'מסומן' או טקסט 'לא מסומן' . במקרים מסוימים, יכול להיות שתרצו לשנות את תיאור ברירת המחדל של המצב התוויות שמשמשות את 'פיתוח נייטיב'. אפשר לעשות זאת באמצעות ציון מפורש של המדינה תוויות תיאור לפני שמגדירים תוכן קומפוזבילי כחומר שניתן להחליף מצב:

@Composable
private fun TopicItem(itemTitle: String, selected: Boolean, onToggle: () -> Unit) {
    val stateSubscribed = stringResource(R.string.subscribed)
    val stateNotSubscribed = stringResource(R.string.not_subscribed)
    Row(
        modifier = Modifier
            .semantics {
                // Set any explicit semantic properties
                stateDescription = if (selected) stateSubscribed else stateNotSubscribed
            }
            .toggleable(
                value = selected,
                onValueChange = { onToggle() }
            )
    ) {
        /* ... */
    }
}

הגדרת הכותרות

לפעמים אפליקציות מציגות תוכן רב על מסך אחד במאגר שניתן לגלול. לדוגמה, מסך יכול להציג את התוכן המלא של כתבה שהמשתמש משתמש בה קורא/ת:

צילום מסך של פוסט בבלוג, עם טקסט המאמר במאגר שניתן לגלול.

משתמשים עם צורכי נגישות מתקשים לנווט במסך כזה. כדי לעזור ניווט, לציין אילו רכיבים הם כותרות. בדוגמה שלמעלה, כל אחד ניתן להגדיר כותרת של סעיף משנה ככותרת לנגישות. במידה מסוימת שירותי נגישות כמו Talkback, מאפשרים למשתמשים לנווט ישירות מהכותרת העליונה.

בקטע 'פיתוח', מציינים שתוכן קומפוזבילי הוא כותרת על ידי הגדרת נכס semantics:

@Composable
private fun Subsection(text: String) {
    Text(
        text = text,
        style = MaterialTheme.typography.headlineSmall,
        modifier = Modifier.semantics { heading() }
    )
}

איך מטפלים בתכנים קומפוזביליים בהתאמה אישית

בכל פעם שמחליפים רכיבי Material מסוימים באפליקציה בפריטים מותאמים אישית וצריך להביא בחשבון את שיקולי הנגישות.

נניח שאתם מחליפים את 'חומר' Checkbox ביישום משלכם. אפשר לשכוח להוסיף את מקש הצירוף triStateToggleable, שמטפל את מאפייני הנגישות של רכיב זה.

ככלל אצבע, מומלץ לבדוק את ההטמעה של הרכיב ספריית Material שמחקה כל התנהגות נגישות שניתן למצוא. בנוסף, כדאי לעשות שימוש רב בהתאמות של הבסיס, ולא ברמת ממשק המשתמש מגבילי התאמה, כי הם כוללים שיקולי נגישות שניתנים להתאמה אישית.

בדיקת ההטמעה של הרכיבים המותאמים אישית באמצעות כמה שירותי נגישות כדי לאמת את התנהגותו.

מקורות מידע נוספים