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

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

"המשפט הזה נמצא" ← "המשפט הזה" ← "בעמודה השמאלית". → "on the right" (בצד ימין).

כדי לסדר את החלקים בצורה נכונה, משנים את קטע הקוד המקורי ומגדירים את 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 ב-Column, לא תהיה לכך השפעה, אלא אם מגדירים גם את 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 בכל שעון גדל ככל שנוספים מספרים למסך – הערך של traversalIndex בשעון 0 הוא 0, והערך של traversalIndex בשעון 1 הוא 1. כך נקבע הסדר שבו TalkBack מקריא אותם. עכשיו, המספרים בתוך CircularLayout נקראים בסדר הצפוי.

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

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

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

כשמשתמשים בממשקי ה-API למעבר, חשוב להביא בחשבון את הנקודות הבאות:

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