Penggabungan dan penghapusan

Saat layanan aksesibilitas menjelajahi elemen di layar, elemen-elemen ini harus dikelompokkan, dipisahkan, atau bahkan disembunyikan pada tingkat perincian yang tepat. Jika setiap satu composable level rendah di layar Anda ditandai secara independen, pengguna harus banyak berinteraksi untuk berpindah-pindah di layar. Jika elemen digabungkan dengan terlalu agresif, pengguna mungkin tidak memahami elemen mana yang secara logis menjadi satu. Jika ada elemen di layar yang murni dekoratif, elemen ini dapat disembunyikan dari layanan aksesibilitas. Dalam kasus ini, Anda dapat menggunakan Compose API untuk menggabungkan, menghapus, dan menyembunyikan semantik.

Menggabungkan semantik

Saat Anda menerapkan pengubah clickable ke composable induk, Compose akan otomatis menggabungkan semua elemen turunan di bawahnya. Untuk memahami cara komponen Foundation dan Material Compose interaktif menggunakan strategi penggabungan secara default, lihat bagian Elemen interaktif.

Komponen biasanya terdiri dari beberapa composable. Composable ini dapat membentuk grup logis dan masing-masing dapat berisi informasi penting, tetapi Anda mungkin tetap ingin layanan aksesibilitas melihatnya sebagai satu elemen.

Misalnya, bayangkan composable yang menampilkan avatar pengguna, nama mereka, dan beberapa informasi tambahan:

Sekumpulan elemen UI termasuk nama pengguna. Nama dipilih.
Gambar 1. Sekumpulan elemen UI termasuk nama pengguna. Nama dipilih.

Anda dapat mengaktifkan Compose untuk menggabungkan elemen ini dengan menggunakan parameter mergeDescendants dalam pengubah semantik. Dengan cara ini, layanan aksesibilitas memperlakukan komponen sebagai satu entity, dan semua properti semantik turunan digabungkan:

@Composable
private fun PostMetadata(metadata: Metadata) {
    // Merge elements below for accessibility purposes
    Row(modifier = Modifier.semantics(mergeDescendants = true) {}) {
        Image(
            imageVector = Icons.Filled.AccountCircle,
            contentDescription = null // decorative
        )
        Column {
            Text(metadata.author.name)
            Text("${metadata.date} • ${metadata.readTimeMinutes} min read")
        }
    }
}

Layanan aksesibilitas kini akan berfokus pada seluruh container sekaligus, dan menggabungkan kontennya:

Sekumpulan elemen UI termasuk nama pengguna. Semua elemen dipilih bersama.
Gambar 2. Sekumpulan elemen UI termasuk nama pengguna. Semua elemen dipilih bersama.

Setiap properti semantik memiliki strategi penggabungan yang ditetapkan. Misalnya, properti ContentDescription menambahkan semua nilai ContentDescription turunan ke daftar. Anda dapat memeriksa strategi penggabungan properti semantik dengan memeriksa implementasi mergePolicy-nya di SemanticsProperties.kt. Properti dapat 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.

Ada skenario lain saat Anda mengharapkan semantik turunan digabungkan ke semantik induk, tetapi hal itu tidak terjadi. Dalam contoh berikut, kita memiliki induk item daftar clickable dengan elemen turunan, dan kita mungkin mengharapkan induk menggabungkan semuanya:

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

@Composable
private fun ArticleListItem(
    openArticle: () -> Unit,
    addToBookmarks: () -> Unit,
) {

    Row(modifier = Modifier.clickable { openArticle() }) {
        // Merges with parent clickable:
        Icon(
            painter = painterResource(R.drawable.ic_logo),
            contentDescription = "Article thumbnail"
        )
        ArticleDetails()

        // Defies the merge due to its own clickable:
        BookmarkButton(onClick = addToBookmarks)
    }
}

Saat pengguna menekan item clickable Row, artikel akan terbuka. Di dalamnya, ada BookmarkButton untuk mem-bookmark artikel. Tombol bertingkat ini muncul sebagai tidak digabungkan, sedangkan konten turunan lainnya di dalam baris digabungkan:

Pohon gabungan berisi beberapa teks dalam daftar di dalam node Baris. Pohon terpisah berisi node yang terpisah untuk setiap komposisi Teks.
Gambar 4. Hierarki gabungan berisi beberapa teks dalam daftar di dalam node Row. Hierarki yang tidak digabungkan berisi node terpisah untuk setiap composable Text.

Beberapa composable tidak otomatis digabungkan di bawah induk, sesuai desain. Induk tidak dapat menggabungkan turunannya saat turunan juga digabungkan, baik dari setelan mergeDescendants = true secara eksplisit maupun dengan menjadi komponen yang menggabungkan dirinya sendiri, seperti tombol atau elemen yang dapat diklik. Mengetahui cara API tertentu digabungkan atau tidak digabungkan dapat membantu Anda men-debug beberapa perilaku yang berpotensi tidak terduga.

Gunakan penggabungan jika elemen turunan merupakan grup yang logis dan masuk akal di bawah induknya. Namun, jika turunan bertingkat memerlukan penyesuaian manual atau penghapusan semantiknya sendiri, API lain mungkin lebih sesuai dengan kebutuhan Anda (misalnya, clearAndSetSemantics).

Menghapus dan menetapkan semantik

Jika informasi semantik perlu dihapus atau ditimpa sepenuhnya, API yang efektif untuk digunakan adalah clearAndSetSemantics.

Jika komponen memerlukan semantiknya sendiri dan semantik turunannya dihapus, gunakan API ini dengan lambda kosong. Jika semantiknya harus ditimpa, sertakan konten baru Anda di dalam lambda.

Perhatikan bahwa saat menghapus dengan lambda kosong, semantik yang dihapus tidak dikirim ke konsumen mana pun yang menggunakan informasi ini, seperti aksesibilitas, isi otomatis, atau pengujian. Saat menimpa konten dengan clearAndSetSemantics{/*semantic information*/}, semantik baru akan menggantikan semua semantik elemen dan turunannya sebelumnya.

Berikut adalah contoh komponen tombol kustom, yang direpresentasikan oleh baris yang dapat berinteraksi dengan ikon dan teks:

// Developer might intend this to be a toggleable.
// Using `clearAndSetSemantics`, on the Row, a clickable modifier is applied,
// a custom description is set, and a Role is applied.

@Composable
fun FavoriteToggle() {
    val checked = remember { mutableStateOf(true) }
    Row(
        modifier = Modifier
            .toggleable(
                value = checked.value,
                onValueChange = { checked.value = it }
            )
            .clearAndSetSemantics {
                stateDescription = if (checked.value) "Favorited" else "Not favorited"
                toggleableState = ToggleableState(checked.value)
                role = Role.Switch
            },
    ) {
        Icon(
            imageVector = Icons.Default.Favorite,
            contentDescription = null // not needed here

        )
        Text("Favorite?")
    }
}

Meskipun ikon dan teks memiliki beberapa informasi semantik, keduanya tidak menunjukkan bahwa komponen ini dapat diubah statusnya. Penggabungan tidak cukup karena Anda harus memberikan informasi tambahan tentang komponen.

Karena cuplikan di atas membuat komponen tombol kustom, Anda perlu menambahkan kemampuan tombol, serta semantik stateDescription, toggleableState, dan role. Dengan cara ini, status komponen dan tindakan terkait akan tersedia—misalnya, TalkBack mengumumkan "Ketuk dua kali untuk beralih", bukan "Ketuk dua kali untuk mengaktifkan".

Dengan menghapus semantik asli dan menetapkan semantik baru yang lebih deskriptif, layanan aksesibilitas kini dapat melihat bahwa ini adalah komponen yang dapat diganti yang dapat mengubah status.

Saat menggunakan clearAndSetSemantics, pertimbangkan hal berikut:

  • Karena layanan tidak menerima informasi saat API ini ditetapkan, sebaiknya gunakan API ini seperlunya.
    • Informasi semantik berpotensi digunakan oleh agen AI dan layanan serupa untuk memahami layar, sehingga hanya boleh dihapus jika diperlukan.
  • Semantik kustom dapat ditetapkan dalam lambda API.
  • Urutan pengubah sangat penting―API ini menghapus semua semantik yang ada setelah diterapkan, terlepas dari strategi penggabungan lainnya.

Menyembunyikan semantik

Dalam beberapa skenario, elemen tidak perlu dikirim ke layanan aksesibilitas—mungkin informasi tambahannya redundan untuk aksesibilitas, atau murni dekoratif secara visual dan non-interaktif. Dalam kasus ini, Anda dapat menyembunyikan elemen dengan API hideFromAccessibility.

Dalam contoh berikut, ada komponen yang mungkin perlu disembunyikan: watermark redundan yang mencakup komponen, dan karakter yang digunakan untuk memisahkan informasi secara dekoratif.

@Composable
fun WatermarkExample(
    watermarkText: String,
    content: @Composable () -> Unit,
) {
    Box {
        WatermarkedContent()
        // Mark the watermark as hidden to accessibility services.
        WatermarkText(
            text = watermarkText,
            color = Color.Gray.copy(alpha = 0.5f),
            modifier = Modifier
                .align(Alignment.BottomEnd)
                .semantics { hideFromAccessibility() }
        )
    }
}

@Composable
fun DecorativeExample() {
    Text(
        modifier =
        Modifier.semantics {
            hideFromAccessibility()
        },
        text = "A dot character that is used to decoratively separate information, like •"
    )
}

Penggunaan hideFromAccessibility di sini memastikan watermark dan dekorasi disembunyikan dari layanan aksesibilitas, tetapi tetap mempertahankan semantiknya untuk kasus penggunaan lain, seperti pengujian.

Perincian kasus penggunaan

Berikut adalah ringkasan kasus penggunaan untuk memahami cara membedakan API sebelumnya dengan jelas:

  • Jika konten tidak dimaksudkan untuk digunakan oleh layanan aksesibilitas:
    • Gunakan hideFromAccessibility jika konten mungkin bersifat dekoratif atau redundan, tetapi masih harus diuji.
    • Gunakan clearAndSetSemantics{} dengan lambda kosong saat semantik induk dan turunan perlu dihapus untuk semua layanan.
    • Gunakan clearAndSetSemantics{/*content*/} dengan konten di dalam lambda saat semantik komponen perlu ditetapkan secara manual.
  • Jika konten harus diperlakukan sebagai satu entity, dan memerlukan semua informasi turunannya agar lengkap:
    • Gunakan gabungkan turunan semantik.
Tabel dengan kasus penggunaan API yang berbeda.
Gambar 5. Tabel dengan kasus penggunaan API yang berbeda.