שליטה בסדר המעבר

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

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

שימוש ב-isTraversalGroup וב-traversalIndex ב- אפליקציה כדי לשלוט בסדר המעבר של קורא המסך.

קיבוץ רכיבים עם isTraversalGroup

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

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

הדוגמה הבאה משתמשת ב-isTraversalGroup. הוא פולט ארבעה רכיבי טקסט. שני רכיבים משמאל שייכים לרכיב CardBox אחד, ושני רכיבים ימניים שייכים לרכיב CardBox אחר:

// CardBox() function takes in top and bottom sample text.
@Composable
fun CardBox(
    topSampleText: String,
    bottomSampleText: String,
    modifier: Modifier = Modifier
) {
    Box(modifier) {
        Column {
            Text(topSampleText)
            Text(bottomSampleText)
        }
    }
}

@Composable
fun TraversalGroupDemo() {
    val topSampleText1 = "This sentence is in "
    val bottomSampleText1 = "the left column."
    val topSampleText2 = "This sentence is "
    val bottomSampleText2 = "on the right."
    Row {
        CardBox(
            topSampleText1,
            bottomSampleText1
        )
        CardBox(
            topSampleText2,
            bottomSampleText2
        )
    }
}

הקוד יוצר פלט שדומה לזה:

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

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

"המשפט הזה נמצא" ← "המשפט הזה הוא" ← "העמודה השמאלית". → "ב נכון".

כדי לסדר את המקטעים בצורה נכונה, צריך לשנות את קטע הקוד המקורי להגדרה isTraversalGroup עד true:

@Composable
fun TraversalGroupDemo2() {
    val topSampleText1 = "This sentence is in "
    val bottomSampleText1 = "the left column."
    val topSampleText2 = "This sentence is"
    val bottomSampleText2 = "on the right."
    Row {
        CardBox(
//      1,
            topSampleText1,
            bottomSampleText1,
            Modifier.semantics { isTraversalGroup = true }
        )
        CardBox(
//      2,
            topSampleText2,
            bottomSampleText2,
            Modifier.semantics { isTraversalGroup = true }
        )
    }
}

בגלל שהפרמטר isTraversalGroup מוגדר באופן ספציפי בכל CardBox, CardBox הגבולות חלים בזמן מיון הרכיבים שלהם. במקרה הזה, החלון השמאלי קוראים קודם CardBox, ואחריהם CardBox מימין.

עכשיו, TalkBack מקריא את קטעי המשפטים בסדר הנכון:

"המשפט הזה נמצא" ← "העמודה השמאלית". ← "המשפט הזה הוא" → "ב נכון".

התאמה אישית נוספת של סדר המעבר

traversalIndex הוא נכס צף שמאפשר להתאים אישית את TalkBack סדר המעבר. אם קיבוץ רכיבים ביחד לא מספיק כדי ש-TalkBack פועלות כראוי, צריך להשתמש ב-traversalIndex בשילוב עם isTraversalGroup כדי להתאים אישית את הסדר של קוראי המסך.

המאפיין traversalIndex כולל את המאפיינים הבאים:

  • רכיבים עם ערכי traversalIndex נמוכים יותר מקבלים עדיפות ראשונה.
  • הוא יכול להיות חיובי או שלילי.
  • ערך ברירת המחדל הוא 0f.
  • משפיעה רק על צמתים עם קורא מסך שניתן להתמקד בהם, כמו רכיבים במסך כמו טקסט או לחצנים. לדוגמה, אם מגדירים רק את הערך traversalIndex בעמודה, אין להם השפעה, אלא אם מוגדר גם isTraversalGroup על העמודה.

הדוגמה הבאה מראה איך אפשר להשתמש ב-traversalIndex וב- isTraversalGroup ביחד.

דוגמה: תצוגת שעון לחצי

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

תצוגת שעון עם חלונית לבחירת שעה מעליה.
איור 2. תמונה של תצוגת שעון.

בקטע הקוד הפשוט הבא, יש CircularLayout שבו 12 המספרים נשלפים, החל מ-12 ונעים בכיוון השעון סביב המעגל:

@Composable
fun ClockFaceDemo() {
    CircularLayout {
        repeat(12) { hour ->
            ClockText(hour)
        }
    }
}

@Composable
private fun ClockText(value: Int) {
    Box(modifier = Modifier) {
        Text((if (value == 0) 12 else value).toString())
    }
}

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

@Composable
fun ClockFaceDemo() {
    CircularLayout(Modifier.semantics { isTraversalGroup = true }) {
        repeat(12) { hour ->
            ClockText(hour)
        }
    }
}

@Composable
private fun ClockText(value: Int) {
    Box(modifier = Modifier.semantics { this.traversalIndex = value.toFloat() }) {
        Text((if (value == 0) 12 else value).toString())
    }
}

כדי להגדיר כראוי את סדר המעבר, תחילה צריך להפוך את CircularLayout קבוצת מעבר ולהגדיר את isTraversalGroup = true. לאחר מכן, מכיוון שכל טקסט של שעון שצייר על הפריסה, מגדירים את traversalIndex התואם למונה עם ערך מסוים.

מכיוון שערך המונה גדל כל הזמן, המדד traversalIndex גדול יותר ככל שמוסיפים למסך מספרים. ערך השעון הוא 0 הערך של traversalIndex הוא 0, והערך traversalIndex של השעון הוא 1. כך נקבע הסדר שבו TalkBack מקריא אותן. עכשיו, המספרים בתוך CircularLayout נקראות בסדר הצפוי.

בגלל שהערכים של traversalIndexes שהוגדרו מתייחסים רק לערכים של או אינדקסים בתוך אותה קיבוץ, שאר סדר המסכים נשמר. כלומר, השינויים הסמנטיים שמוצגים בקוד הקודם קטע הקוד ישנה רק את הסדר בתצוגת השעון שכוללת isTraversalGroup = true הוגדרה.

חשוב לשים לב שהשינויים של traversalIndex עדיין חלים בלי להגדיר את הסמנטיקה של CircularLayout's ל-isTraversalGroup = true. אבל בלי CircularLayout כדי לקשר ביניהם, 12 הספרות של תצוגת השעון נקראות לבסוף, אחרי שכל שאר הרכיבים במסך כבר ביקרו. מצב כזה קורה מכיוון שלכל שאר הרכיבים יש ערך traversalIndex של 0f כברירת מחדל, רכיבי טקסט השעון נקראים אחרי כל שאר רכיבי 0f.

דוגמה: התאמה אישית של סדר המעבר ללחצן פעולה צף

בדוגמה הזו, traversalIndex ו-isTraversalGroup שולטים סדר המעבר של לחצן פעולה צף מסוג Material Design (FAB). הבסיס של הדוגמה הזו היא הפריסה הבאה:

פריסה עם סרגל אפליקציה עליון, טקסט לדוגמה, לחצן פעולה צף וגם
  סרגל אפליקציה תחתון.
איור 3. פריסה עם סרגל אפליקציה עליון, טקסט לדוגמה, לחצן פעולה צף, וסרגל תחתון של האפליקציה.

כברירת מחדל, הפריסה בדוגמה הזו מבוססת על הסדר הבא של TalkBack:

סרגל מוביל של האפליקציה ← טקסטים לדוגמה 0 עד 6 ← לחצן פעולה צף (FAB) ← למטה סרגל האפליקציות

מומלץ שקורא המסך יתמקד קודם ב-FAB. כדי להגדיר traversalIndex ברכיב Material כמו FAB, מבצעים את הפעולות הבאות:

@Composable
fun FloatingBox() {
    Box(modifier = Modifier.semantics { isTraversalGroup = true; traversalIndex = -1f }) {
        FloatingActionButton(onClick = {}) {
            Icon(imageVector = Icons.Default.Add, contentDescription = "fab icon")
        }
    }
}

בקטע הקוד הזה, יצירת תיבה עם הטווח isTraversalGroup הוגדר לערך true והוגדר traversalIndex באותה תיבה (הערך -1f נמוך מערך ברירת המחדל של 0f) פירושו שהתיבה הצפה מופיע לפני כל שאר הרכיבים במסך.

בשלב הבא, אפשר להכניס את התיבה הצפה ורכיבים אחרים לפיגום, משתמשת בפריסה של Material Design:

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ColumnWithFABFirstDemo() {
    Scaffold(
        topBar = { TopAppBar(title = { Text("Top App Bar") }) },
        floatingActionButtonPosition = FabPosition.End,
        floatingActionButton = { FloatingBox() },
        content = { padding -> ContentColumn(padding = padding) },
        bottomBar = { BottomAppBar { Text("Bottom App Bar") } }
    )
}

TalkBack מקיים אינטראקציה עם הרכיבים בסדר הבא:

FAB ← הסרגל העליון של האפליקציה ← דגימות טקסטים 0 עד 6 ← הסרגל התחתון של האפליקציה

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