Mengubah urutan traversal

Urutan traversal adalah urutan layanan aksesibilitas menavigasi elemen UI. Di aplikasi Compose, elemen diatur dalam urutan baca yang diharapkan, yang biasanya dari kiri ke kanan, lalu dari atas ke bawah. Namun, ada beberapa skenario saat Compose mungkin memerlukan petunjuk tambahan untuk menentukan urutan pembacaan yang benar.

isTraversalGroup dan traversalIndex adalah properti semantik yang memungkinkan Anda memengaruhi urutan traversal untuk layanan aksesibilitas dalam skenario saat algoritma pengurutan default Compose tidak memadai. isTraversalGroup mengidentifikasi grup yang penting secara semantik yang memerlukan penyesuaian, sedangkan traversalIndex menyesuaikan urutan setiap elemen dalam grup tersebut. Anda dapat menggunakan isTraversalGroup saja untuk menunjukkan bahwa semua elemen dalam grup harus dipilih bersama, atau dengan traversalIndex untuk penyesuaian lebih lanjut.

Gunakan isTraversalGroup dan traversalIndex di aplikasi Anda untuk mengontrol urutan traversal pembaca layar.

Mengelompokkan elemen untuk traversal

isTraversalGroup adalah properti boolean yang menentukan apakah node semantik adalah grup traversal. Jenis node ini berfungsi sebagai batas atau pembatas dalam mengatur turunan node.

Menetapkan isTraversalGroup = true pada node berarti semua turunan node tersebut dibuka sebelum berpindah ke elemen lain. Anda dapat menetapkan isTraversalGroup pada node yang dapat difokuskan oleh pembaca layar, seperti kolom, baris, atau kotak.

Contoh berikut menggunakan isTraversalGroup. Fungsi ini memunculkan empat elemen teks. Dua elemen kiri merupakan bagian dari satu elemen CardBox, sedangkan dua elemen kanan merupakan bagian dari elemen CardBox lain:

// 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
        )
    }
}

Kode tersebut menghasilkan output yang mirip dengan berikut ini:

Tata letak dengan dua kolom teks, dengan kolom kiri bertuliskan 'Kalimat
  ini ada di kolom kiri' dan kolom kanan bertuliskan 'Kalimat ini ada di sebelah kanan'.
Gambar 1. Tata letak dengan dua kalimat (satu di kolom kiri dan satu di kolom kanan).

Karena tidak ada semantik yang telah ditetapkan, perilaku default pembaca layar adalah melintasi elemen dari kiri ke kanan dan atas ke bawah. Karena setelan default ini, TalkBack membaca fragmen kalimat dalam urutan yang salah:

"Kalimat ini ada di" → "Kalimat ini ada" → "kolom kiri". → "di sebelah kanan".

Untuk mengurutkan fragmen dengan benar, ubah cuplikan asli untuk menetapkan isTraversalGroup ke 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 }
        )
    }
}

Karena isTraversalGroup ditetapkan secara khusus pada setiap CardBox, batas CardBox akan diterapkan saat mengurutkan elemennya. Dalam hal ini, CardBox kiri dibaca terlebih dahulu, diikuti dengan CardBox kanan.

Sekarang, TalkBack membacakan fragmen kalimat dalam urutan yang benar:

"Kalimat ini ada di" → "kolom kiri". → "Kalimat ini" → "di sebelah kanan."

Menyesuaikan urutan traversal

traversalIndex adalah properti float yang memungkinkan Anda menyesuaikan urutan traversal TalkBack. Jika pengelompokan elemen tidak cukup agar TalkBack berfungsi dengan benar, gunakan traversalIndex bersama dengan isTraversalGroup untuk menyesuaikan lebih lanjut pengurutan pembaca layar.

Properti traversalIndex memiliki karakteristik berikut:

  • Elemen dengan nilai traversalIndex yang lebih rendah diprioritaskan terlebih dahulu.
  • Dapat berupa positif atau negatif.
  • Nilai default-nya adalah 0f.
  • Agar indeks traversal dapat memengaruhi perilaku traversal, indeks tersebut harus ditetapkan pada komponen yang akan dapat dipilih dan difokuskan oleh layanan aksesibilitas, seperti elemen di layar seperti teks atau tombol.
    • Menyetel traversalIndex saja, misalnya, Column tidak akan berpengaruh, kecuali jika kolom tersebut juga memiliki isTraversalGroup.

Contoh berikut menunjukkan cara menggunakan traversalIndex dan isTraversalGroup secara bersamaan.

Tampilan jam adalah skenario umum saat pengurutan traversal standar tidak berfungsi. Contoh di bagian ini adalah pemilih waktu, tempat pengguna dapat menjelajahi angka pada tampilan jam dan memilih digit untuk slot jam dan menit.

Tampilan jam dengan pemilih waktu di atasnya.
Gambar 2. Gambar tampilan jam.

Dalam cuplikan sederhana berikut, terdapat CircularLayout yang menggambar 12 angka, dimulai dengan 12 dan bergerak searah jarum jam di sekitar lingkaran:

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

Karena tampilan jam tidak dibaca secara logis dengan urutan kiri ke kanan dan atas ke bawah default, TalkBack membaca angka secara tidak berurutan. Untuk memperbaikinya, gunakan nilai penghitung yang bertambah, seperti yang ditunjukkan dalam cuplikan berikut:

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

Untuk menetapkan pengurutan traversal dengan benar, buat CircularLayout menjadi grup traversal terlebih dahulu dan tetapkan isTraversalGroup = true. Kemudian, saat setiap teks jam digambar ke tata letak, tetapkan traversalIndex yang sesuai ke nilai penghitung.

Karena nilai penghitung terus meningkat, setiap traversalIndex nilai clock akan lebih besar saat angka ditambahkan ke layar—nilai clock 0 memiliki traversalIndex 0, dan nilai clock 1 memiliki traversalIndex 1. Dengan cara ini, urutan pembacaan TalkBack akan ditetapkan. Sekarang, angka di dalam CircularLayout dibaca dalam urutan yang diharapkan.

Karena traversalIndexes yang telah ditetapkan hanya relatif terhadap indeks lain dalam pengelompokan yang sama, urutan layar lainnya telah dipertahankan. Dengan kata lain, perubahan semantik yang ditampilkan dalam cuplikan kode sebelumnya hanya mengubah pengurutan dalam tampilan jam yang telah menyetel isTraversalGroup = true.

Perhatikan bahwa, tanpa menetapkan semantik CircularLayout's ke isTraversalGroup = true, perubahan traversalIndex tetap berlaku. Namun, tanpa CircularLayout untuk mengikatnya, dua belas digit tampilan jam dibaca terakhir, setelah semua elemen lain di layar telah dikunjungi. Hal ini terjadi karena semua elemen lain memiliki traversalIndex default 0f, dan elemen teks jam dibaca setelah semua elemen 0f lainnya.

Pertimbangan API

Pertimbangkan hal berikut saat menggunakan API traversal:

  • isTraversalGroup = true harus ditetapkan pada induk yang berisi elemen yang dikelompokkan.
  • traversalIndex harus ditetapkan pada komponen turunan yang berisi semantik dan akan dipilih oleh layanan aksesibilitas.
  • Pastikan semua elemen yang Anda selidiki berada di tingkat zIndex yang sama, karena hal itu juga memengaruhi semantik dan urutan traversal.
  • Pastikan tidak ada semantik yang digabungkan secara tidak perlu, karena hal ini dapat memengaruhi komponen yang akan diterapkan indeks traversal.