שינוי סדר המעבר

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

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

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

קיבוץ רכיבים לצורך סריקה

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 יקריא את קטעי המשפט בסדר הנכון:

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

התאמה אישית של סדר הטרaversal

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

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

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

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

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

תצוגת שעון עם בורר זמן מעליו.
איור 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())
    }
}

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

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

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

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

שיקולים לשימוש ב-API

כשמשתמשים ב-API לטרaversal, כדאי לשים לב לדברים הבאים:

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