Semantik di Compose

Komposisi menjelaskan UI aplikasi Anda dan diproduksi dengan menjalankan composable. Komposisi adalah struktur pohon yang terdiri dari composable yang mendeskripsikan UI Anda.

Di samping Komposisi, terdapat pohon paralel yang disebut Pohon semantik. Pohon ini menjelaskan UI dengan cara lain yang dapat dipahami untuk layanan Aksesibilitas dan untuk framework Pengujian. Layanan aksesibilitas menggunakan pohon untuk mendeskripsikan aplikasi kepada pengguna yang memiliki kebutuhan tertentu. Framework Pengujian menggunakannya untuk berinteraksi dengan aplikasi Anda dan membuat pernyataan tentang aplikasi tersebut. Hierarki Semantik tidak berisi informasi cara menggambar composable Anda, tetapi berisi informasi makna semantik dari composable.

Gambar 1. Hierarki UI umum dan pohon semantiknya.

Jika aplikasi Anda terdiri atas composable dan pengubah dari library foundation dan material Compose, pohon Semantik akan otomatis diisi dan dibuat untuk Anda. Namun, saat menambahkan composable kustom tingkat rendah, Anda harus menyediakan semantiknya secara manual. Mungkin juga ada situasi saat pohon Anda salah atau tidak sepenuhnya mewakili makna dari elemen di layar, dan Anda dapat menyesuaikan pohonnya.

Misalnya, perhatikan composable kalender kustom ini:

Gambar 2. Composable kalender kustom dengan elemen hari yang dapat dipilih.

Dalam contoh ini, seluruh kalender diimplementasikan sebagai satu composable tingkat rendah menggunakan composable Layout dan menggambar langsung ke Canvas. Jika Anda tidak melakukan tindakan lain, layanan aksesibilitas tidak akan menerima informasi yang cukup tentang konten composable dan pilihan pengguna dalam kalender. Misalnya, jika pengguna mengklik pada hari yang berisi 17, framework aksesibilitas hanya akan menerima informasi deskripsi untuk seluruh kontrol kalender. Dalam hal ini, layanan aksesibilitas TalkBack hanya akan mengumumkan "Kalender" atau, yang sedikit lebih baik, "Kalender bulan April", dan pengguna akan bertanya-tanya hari apa yang dipilih. Agar composable ini lebih mudah diakses, Anda harus menambahkan informasi semantik secara manual.

Properti semantik

Semua node di pohon UI dengan beberapa makna semantik memiliki node paralel di pohon Semantik. Node di pohon Semantik berisi properti yang menyajikan makna composable yang sesuai. Misalnya, composable Text berisi properti semantik text karena itu adalah makna dari composable tersebut. Icon berisi properti contentDescription (jika ditetapkan oleh developer) yang menyampaikan makna Icon dalam teks. Composable dan pengubah yang di-build berdasarkan atribut library foundation Compose sudah menetapkan properti yang relevan untuk Anda. Atau, Anda dapat menetapkan atau mengganti properti dengan pengubah semantics dan clearAndSetSemantics. Misalnya, Anda dapat menambahkan tindakan aksesibilitas kustom ke node, memberikan deskripsi status alternatif untuk elemen yang dapat diganti, atau menunjukkan bahwa composable teks tertentu harus dianggap sebagai judul.

Untuk memvisualisasikan pohon Semantik, kita dapat menggunakan Alat Layout Inspector atau menggunakan metode printToLog() di dalam pengujian. Cara ini akan mencetak pohon Semantik saat ini di dalam Logcat.

class MyComposeTest {

    @get:Rule
    val composeTestRule = createComposeRule()

    @Test
    fun MyTest() {
        // Start the app
        composeTestRule.setContent {
            MyTheme {
                Text("Hello world!")
            }
        }
        // Log the full semantics tree
        composeTestRule.onRoot().printToLog("MY TAG")
    }
}

Output pengujian ini adalah:

    Printing with useUnmergedTree = 'false'
    Node #1 at (l=0.0, t=63.0, r=221.0, b=120.0)px
     |-Node #2 at (l=0.0, t=63.0, r=221.0, b=120.0)px
       Text = '[Hello world!]'
       Actions = [GetTextLayoutResult]

Mari kita perhatikan contoh untuk melihat cara properti semantik digunakan guna menyampaikan makna composable. Mari kita pikirkan Switch. Ini yang akan dilihat pengguna:

Gambar 3. Tombol akses dalam status "Aktif" dan "Nonaktif".

Untuk mendeskripsikan makna elemen ini, Anda dapat mengucapkan: “Ini adalah Tombol Akses, yang merupakan elemen yang dapat diganti, saat ini dalam status 'Aktif'. Anda dapat mengkliknya untuk berinteraksi dengan tombol”.

Inilah kegunaan properti semantik yang sebenarnya. Node semantik dari elemen Tombol Akses ini berisi properti berikut, seperti yang divisualisasikan dengan Layout Inspector:

Gambar 4. Layout Inspector menampilkan properti Semantik composable Tombol Akses.

Role menunjukkan jenis elemen yang sedang dilihat. StateDescription menjelaskan bagaimana status "Aktif" seharusnya direferensikan. Secara default, ini hanyalah versi lokal dari kata "Aktif", tetapi dapat dibuat lebih spesifik (misalnya, "Diaktifkan") dengan menyesuaikan konteks. ToggleableState adalah status Tombol Akses saat ini. Properti OnClick mereferensikan metode yang digunakan untuk berinteraksi dengan elemen ini. Untuk daftar lengkap properti semantik, lihat objek SemanticsProperties. Untuk daftar lengkap Tindakan Aksesibilitas yang mungkin, lihat objek SemanticsActions.

Melacak properti semantik dari setiap composable di aplikasi Anda akan membuka berbagai kemungkinan yang canggih. Beberapa contohnya:

  • Talkback menggunakan properti untuk membaca informasi yang ditampilkan di layar dengan jelas dan memungkinkan pengguna berinteraksi dengannya secara lancar. Untuk Tombol Akses, mungkin tertulis: “Aktif; Tombol Akses; ketuk dua kali untuk beralih”. Pengguna dapat mengetuk layar dua kali untuk menonaktifkan Tombol Akses.
  • Framework pengujian menggunakan properti untuk menemukan node, berinteraksi dengannya, dan membuat pernyataan. Contoh pengujian untuk Tombol Akses kita dapat berupa:
    val mySwitch = SemanticsMatcher.expectValue(
        SemanticsProperties.Role, Role.Switch
    )
    composeTestRule.onNode(mySwitch)
        .performClick()
        .assertIsOff()

Pohon Semantik yang digabungkan dan yang tidak digabungkan

Seperti yang disebutkan sebelumnya, setiap composable di pohon UI mungkin memiliki nol atau beberapa properti semantik yang ditetapkan. Jika composable tidak memiliki properti semantik yang ditetapkan, composable tidak akan disertakan sebagai bagian dari pohon Semantik. Dengan demikian, pohon Semantik hanya berisi node yang benar-benar berisi makna semantik. Akan tetapi, terkadang menyampaikan makna yang benar dari informasi yang ditampilkan di layar juga berguna untuk menggabungkan sub-pohon tertentu dari node dan memperlakukannya sebagai node. Dengan begitu, kita bisa mempertimbangkan sekumpulan node secara keseluruhan, bukan menangani setiap node turunan satu per satu. Prinsipnya adalah, setiap node dalam pohon ini mewakili elemen yang dapat difokuskan selama menggunakan layanan Aksesibilitas.

Contoh dari composable tersebut adalah Tombol. Kita ingin menjelaskan Tombol sebagai elemen tunggal, meskipun mungkin berisi beberapa node turunan:

Button(onClick = { /*TODO*/ }) {
    Icon(
        imageVector = Icons.Filled.Favorite,
        contentDescription = null
    )
    Spacer(Modifier.size(ButtonDefaults.IconSpacing))
    Text("Like")
}

Dalam pohon Semantik kita, properti turunan Tombol digabungkan, dan Tombol ditampilkan sebagai satu node daun dalam pohon:

Composable dan pengubah dapat menunjukkan bahwa keduanya ingin menggabungkan properti semantik turunannya dengan memanggil Modifier.semantics (mergeDescendants = true) {}. Menyetel properti ini ke true menunjukkan bahwa properti semantik harus digabungkan. Dalam contoh Button, composable Button menggunakan pengubah clickable secara internal yang menyertakan pengubah semantics ini. Oleh karena itu, node turunan Tombol akan digabungkan. Baca dokumentasi aksesibilitas untuk mempelajari lebih lanjut kapan Anda harus mengubah perilaku penggabungan di composable.

Beberapa pengubah dan composable di library Foundation dan Material Compose memiliki kumpulan properti ini. Misalnya, pengubah clickable dan toggleable akan otomatis menggabungkan turunannya. Selain itu, composable ListItem akan menggabungkan turunannya.

Memeriksa pohon

Saat membahas pohon Semantik, kita sebenarnya membahas dua pohon yang berbeda. Ada pohon Semantik gabungan yang menggabungkan node turunan saat mergeDescendants ditetapkan ke true. Ada juga hierarki Semantik terpisah, yang tidak menerapkan penggabungan, tetapi menjaga setiap node tetap utuh. Layanan aksesibilitas menggunakan pohon yang terpisah dan menerapkan algoritma penggabungan sendiri dengan mempertimbangkan properti mergeDescendants. Framework pengujian menggunakan struktur gabungan secara default.

Anda dapat memeriksa kedua pohon tersebut menggunakan metode printToLog(). Secara default, dan seperti dalam contoh sebelumnya, pohon gabungan akan dicatat dalam log. Untuk mencetak pohon yang terpisah, tetapkan parameter useUnmergedTree dari pencocok onRoot() ke true:

composeTestRule.onRoot(useUnmergedTree = true).printToLog("MY TAG")

Dengan Layout Inspector, Anda dapat menampilkan pohon Semantik gabungan dan terpisah dengan memilih pohon yang dipilih dalam filter tampilan:

Gambar 5. Opsi tampilan Layout Inspector, yang memungkinkan tampilan pohon Semantik gabungan dan terpisah.

Untuk setiap node di pohon Anda, Layout Inspector akan menampilkan Semantik Gabungan dan Semantik yang ditetapkan di node tersebut dalam panel properti:

Secara default, pencocok dalam Framework Pengujian menggunakan pohon Semantik gabungan. Oleh karena itu, Anda dapat berinteraksi dengan Tombol dengan mencocokkan teks yang ditampilkan di dalamnya:

composeTestRule.onNodeWithText("Like").performClick()

Anda dapat mengganti perilaku ini dengan menetapkan parameter useUnmergedTree dari pencocok ke true, seperti yang kita lakukan sebelumnya dengan pencocok onRoot.

Perilaku penggabungan

Saat composable menunjukkan bahwa turunannya harus digabungkan, bagaimana penggabungan ini sebenarnya dilakukan?

Setiap properti semantik memiliki strategi penggabungan yang ditetapkan. Misalnya, properti ContentDescription menambahkan semua nilai ContentDescription turunan ke daftar. Anda dapat memastikan strategi penggabungan dari properti semantik dengan memeriksa implementasi mergePolicy-nya di SemanticsProperties.kt. Properti dapat memilih untuk selalu mengambil nilai induk atau turunan, menggabungkan nilai ke dalam daftar atau string, tidak mengizinkan penggabungan sama sekali dan menampilkan pengecualian, atau strategi penggabungan kustom lainnya.

Perhatikan bahwa turunan yang telah menetapkan mergeDescendants = true tidak akan disertakan dalam penggabungan. Mari kita lihat contoh berikut:

Gambar 6. Item daftar dengan gambar, beberapa teks, dan ikon bookmark.

Di sini, kita memiliki item daftar yang dapat diklik. Saat pengguna menekan baris, aplikasi akan membuka halaman detail artikel, tempat pengguna dapat membaca artikel. Di dalam item daftar, terdapat tombol untuk memberi bookmark artikel ini. Dalam hal ini, kita memiliki elemen bertingkat yang dapat diklik sehingga tombol akan muncul secara terpisah di pohon gabungan. Konten lainnya yang ada di baris digabungkan:

Gambar 7. Pohon gabungan berisi beberapa teks dalam daftar di dalam node Baris. Pohon terpisah berisi node yang terpisah untuk setiap komposisi Teks.

Menyesuaikan pohon Semantik

Seperti yang disebutkan sebelumnya, Anda dapat mengganti atau menghapus properti semantik tertentu, atau mengubah perilaku penggabungan pohon. Penyesuaian ini sangat berguna saat Anda membuat komponen kustom sendiri. Tanpa menetapkan properti dan perilaku penggabungan yang tepat, aplikasi mungkin tidak dapat diakses, dan perilaku pengujian mungkin berbeda dari yang Anda harapkan. Untuk membaca lebih lanjut beberapa kasus penggunaan umum ketika Anda harus menyesuaikan pohon Semantik, baca dokumentasi aksesibilitas. Jika Anda ingin mempelajari pengujian lebih lanjut, lihat Panduan Pengujian.