Penggabungan dan penghapusan

Saat layanan aksesibilitas menavigasi 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 disoroti 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 tersebut dapat disembunyikan dari layanan aksesibilitas. Dalam kasus ini, Anda dapat menggunakan Compose API untuk menggabungkan, menghapus, dan menyembunyikan semantik.

Semantik penggabungan

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

Umumnya, komponen terdiri dari beberapa composable. Composable ini dapat membentuk grup yang 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 berfokus pada seluruh penampung 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 menggunakan 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 dalam 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 dalam lapisan bertingkat, terdapat 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, karena desainnya. 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 menggabungkan atau menolak penggabungan dapat membantu Anda men-debug beberapa perilaku yang berpotensi tidak terduga.

Gunakan penggabungan saat elemen turunan membentuk grup yang logis dan masuk akal di bawah induknya. Namun, jika turunan bertingkat memerlukan penyesuaian atau penghapusan semantiknya sendiri secara manual, 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 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 yang menggunakan informasi ini, seperti aksesibilitas, isi otomatis, atau pengujian. Saat menimpa konten dengan clearAndSetSemantics{/*semantic information*/}, semantik baru akan menggantikan semua semantik sebelumnya dari elemen dan turunannya.

Berikut adalah contoh komponen tombol khusus, 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 diaktifkan/dinonaktifkan. Penggabungan tidak memadai karena Anda harus memberikan informasi tambahan tentang komponen.

Karena cuplikan di atas membuat komponen tombol khusus, Anda perlu menambahkan kemampuan tombol, serta semantik stateDescription, toggleableState, dan role. Dengan cara ini, status komponen dan tindakan terkait 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 seperlunya.
    • Informasi semantik berpotensi dapat digunakan oleh agen AI dan layanan yang serupa untuk memahami layar, sehingga hanya boleh dihapus jika diperlukan.
  • Semantik kustom dapat ditetapkan dalam lambda API.
  • Urutan pengubah penting―API ini menghapus semua semantik yang ada setelah tempat penerapannya, terlepas dari strategi penggabungan lainnya.

Menyembunyikan semantik

Dalam beberapa skenario, elemen tidak perlu dikirim ke layanan aksesibilitas. Mungkin informasi tambahannya berlebihan untuk aksesibilitas, atau hanya dekoratif secara visual dan non-interaktif. Dalam hal ini, Anda dapat menyembunyikan elemen dengan hideFromAccessibility API.

Dalam contoh berikut adalah komponen yang mungkin perlu disembunyikan: watermark berlebihan yang membentang di 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 lainnya, seperti pengujian.

Perincian kasus penggunaan

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

  • 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 turunan semantik penggabungan.
Tabel dengan kasus penggunaan API yang berbeda.
Gambar 5. Tabel dengan kasus penggunaan API yang berbeda.