Default API

Material, UI Compose, dan Foundation API menerapkan serta menawarkan banyak praktik yang mudah diakses secara default. Elemen ini berisi semantik bawaan yang mengikuti peran dan fungsi tertentu, yang berarti sebagian besar dukungan aksesibilitas disediakan dengan sedikit atau tanpa pekerjaan tambahan.

Menggunakan API yang sesuai untuk tujuan yang sesuai biasanya berarti komponen dilengkapi dengan perilaku aksesibilitas standar yang mencakup kasus penggunaan standar, tetapi jangan lupa untuk memeriksa kembali apakah setelan default ini sesuai dengan kebutuhan aksesibilitas Anda. Jika tidak, Compose juga menyediakan cara untuk memenuhi persyaratan yang lebih spesifik.

Mengetahui semantik dan pola aksesibilitas default di Compose API membantu memahami cara menggunakannya dengan mempertimbangkan aksesibilitas, serta mendukung aksesibilitas di lebih banyak komponen kustom.

Ukuran target sentuh minimum

Elemen apa pun di layar yang dapat diklik, disentuh, atau berinteraksi dengan seseorang harus cukup besar untuk interaksi yang dapat diandalkan. Saat mengubah ukuran elemen ini, pastikan untuk menetapkan ukuran minimum ke 48 dp agar dapat mengikuti panduan aksesibilitas Desain Material dengan benar.

Komponen material—seperti Checkbox, RadioButton, Switch, Slider, dan Surface—menyetel ukuran minimum ini secara internal, tetapi hanya saat komponen dapat menerima tindakan pengguna. Misalnya, jika Checkbox memiliki parameter onCheckedChange yang disetel ke nilai non-null, kotak centang akan menyertakan padding agar memiliki lebar dan tinggi minimal 48 dp.

@Composable
private fun CheckableCheckbox() {
    Checkbox(checked = true, onCheckedChange = {})
}

Kotak centang dengan padding default dengan lebar dan tinggi 48 dp.
Gambar 1. Kotak centang dengan padding default.

Jika parameter onCheckedChange disetel ke null, padding tidak disertakan, karena komponen tidak dapat berinteraksi secara langsung.

@Composable
private fun NonClickableCheckbox() {
    Checkbox(checked = true, onCheckedChange = null)
}

Kotak centang yang tidak memiliki padding.
Gambar 2. Kotak centang tanpa padding.

Saat menerapkan kontrol pemilihan seperti Switch, RadioButton, atau Checkbox, Anda biasanya menaikkan perilaku yang dapat diklik ke penampung induk dengan menyetel callback klik pada composable menjadi null, dan menambahkan pengubah toggleable atau selectable ke composable induk.

@Composable
private fun CheckableRow() {
    MaterialTheme {
        var checked by remember { mutableStateOf(false) }
        Row(
            Modifier
                .toggleable(
                    value = checked,
                    role = Role.Checkbox,
                    onValueChange = { checked = !checked }
                )
                .padding(16.dp)
                .fillMaxWidth()
        ) {
            Text("Option", Modifier.weight(1f))
            Checkbox(checked = checked, onCheckedChange = null)
        }
    }
}

Kotak centang di samping teks 'Opsi' yang dipilih dan dibatalkan pilihannya.
Gambar 3. Kotak centang dengan perilaku yang dapat diklik.

Jika ukuran composable yang dapat diklik lebih kecil dari ukuran target sentuh minimum, Compose masih akan meningkatkan ukuran target sentuh. Hal ini dilakukan dengan memperluas ukuran target sentuh di luar batas composable.

Contoh berikut berisi Box yang dapat diklik dengan ukuran sangat kecil. Area target sentuh otomatis diperluas di luar batas Box, sehingga mengetuk di samping Box tetap akan memicu peristiwa klik.

@Composable
private fun SmallBox() {
    var clicked by remember { mutableStateOf(false) }
    Box(
        Modifier
            .size(100.dp)
            .background(if (clicked) Color.DarkGray else Color.LightGray)
    ) {
        Box(
            Modifier
                .align(Alignment.Center)
                .clickable { clicked = !clicked }
                .background(Color.Black)
                .size(1.dp)
        )
    }
}

Kotak yang sangat kecil dan dapat diklik yang diperluas ke target sentuh yang lebih besar dengan mengetuk di samping kotak.
Gambar 4. Kotak kecil yang dapat diklik dan diperluas ke target sentuh yang lebih besar.

Untuk mencegah kemungkinan tumpang tindih di antara area sentuh composable yang berbeda, selalu gunakan ukuran minimum yang cukup besar untuk composable. Dalam contoh, ini berarti menggunakan pengubah sizeIn untuk menetapkan ukuran minimum kotak dalam:

@Composable
private fun LargeBox() {
    var clicked by remember { mutableStateOf(false) }
    Box(
        Modifier
            .size(100.dp)
            .background(if (clicked) Color.DarkGray else Color.LightGray)
    ) {
        Box(
            Modifier
                .align(Alignment.Center)
                .clickable { clicked = !clicked }
                .background(Color.Black)
                .sizeIn(minWidth = 48.dp, minHeight = 48.dp)
        )
    }
}

Ukuran kotak yang sangat kecil dari contoh sebelumnya ditingkatkan untuk membuat target sentuh yang lebih besar.
Gambar 5. Target sentuh kotak yang lebih besar.

Elemen grafis

Saat Anda menentukan composable Image atau Icon, tidak ada cara otomatis bagi framework Android untuk memahami apa yang ditampilkan aplikasi. Anda harus meneruskan deskripsi teks dari elemen grafis.

Bayangkan layar tempat pengguna dapat berbagi halaman saat ini dengan teman. Layar ini berisi ikon berbagi yang dapat diklik:

Strip empat ikon yang dapat diklik, dengan ikon 'bagikan' ditandai.
Gambar 6. Baris ikon yang dapat diklik dengan ikon 'Bagikan' dipilih.

Berdasarkan ikon tersebut saja, framework Android tidak dapat mendeskripsikannya kepada pengguna dengan gangguan penglihatan. Framework Android memerlukan deskripsi teks tambahan untuk ikon.

Parameter contentDescription menjelaskan elemen grafis. Gunakan string yang dilokalkan, karena string ini terlihat oleh pengguna.

@Composable
private fun ShareButton(onClick: () -> Unit) {
    IconButton(onClick = onClick) {
        Icon(
            imageVector = Icons.Filled.Share,
            contentDescription = stringResource(R.string.label_share)
        )
    }
}

Beberapa elemen grafis hanyalah sebuah hiasan dan Anda mungkin tidak ingin menyampaikannya kepada pengguna. Saat menetapkan parameter contentDescription ke null, Anda menunjukkan pada framework Android bahwa elemen ini tidak memiliki tindakan atau status terkait.

@Composable
private fun PostImage(post: Post, modifier: Modifier = Modifier) {
    val image = post.imageThumb ?: painterResource(R.drawable.placeholder_1_1)

    Image(
        painter = image,
        // Specify that this image has no semantic meaning
        contentDescription = null,
        modifier = modifier
            .size(40.dp, 40.dp)
            .clip(MaterialTheme.shapes.small)
    )
}

contentDescription terutama dimaksudkan untuk digunakan pada elemen grafis, seperti gambar. Komponen Material, seperti Button atau Text, dan perilaku yang dapat ditindaklanjuti, seperti clickable atau toggleable, dilengkapi dengan semantik yang telah ditentukan sebelumnya yang menjelaskan perilaku intrinsiknya, dan dapat diubah melalui Compose API lainnya.

Elemen interaktif

Material dan Foundation Compose API membuat elemen UI yang dapat berinteraksi dengan pengguna melalui API pengubah clickable dan toggleable. Karena komponen yang dapat berinteraksi mungkin terdiri dari beberapa elemen, clickable dan toggleable menggabungkan semantik turunannya secara default, sehingga komponen diperlakukan sebagai satu entity logis.

Misalnya, Button Material mungkin terdiri dari ikon turunan dan beberapa teks. Alih-alih memperlakukan turunan sebagai individu, Tombol Material menggabungkan semantik turunannya secara default, sehingga layanan aksesibilitas dapat mengelompokkan turunan tersebut:

Tombol dengan semantik turunan yang digabungkan dan tidak digabungkan.
Gambar 7. Tombol dengan semantik turunan yang digabungkan dan tidak digabungkan.

Demikian pula, menggunakan pengubah clickable juga menyebabkan composable menggabungkan semantik turunannya menjadi satu entity, yang dikirim ke layanan aksesibilitas dengan representasi tindakan yang sesuai:

Row(
    // Uses `mergeDescendants = true` under the hood
    modifier = Modifier.clickable { openArticle() }
) {
    Icon(
        painter = painterResource(R.drawable.ic_logo),
        contentDescription = "Open",
    )
    Text("Accessibility in Compose")
}

Anda juga dapat menetapkan onClickLabel tertentu pada induk yang dapat diklik untuk memberikan informasi tambahan ke layanan aksesibilitas dan menawarkan representasi tindakan yang lebih rapi:

Row(
    modifier = Modifier
        .clickable(onClickLabel = "Open this article") {
            openArticle()
        }
) {
    Icon(
        painter = painterResource(R.drawable.ic_logo),
        contentDescription = "Open"
    )
    Text("Accessibility in Compose")
}

Dengan menggunakan TalkBack sebagai contoh, pengubah clickable ini dan label kliknya akan memungkinkan TalkBack memberikan petunjuk tindakan "Ketuk dua kali untuk membuka artikel ini", bukan respons default yang lebih umum "Ketuk dua kali untuk mengaktifkan".

Masukan ini berubah bergantung pada jenis tindakan. Klik lama akan memberikan petunjuk TalkBack "Ketuk dua kali dan tahan untuk", diikuti dengan label:

Row(
    modifier = Modifier
        .combinedClickable(
            onLongClickLabel = "Bookmark this article",
            onLongClick = { addToBookmarks() },
            onClickLabel = "Open this article",
            onClick = { openArticle() },
        )
) {}

Dalam beberapa kasus, Anda mungkin tidak memiliki akses langsung ke pengubah clickable (misalnya, saat ditetapkan di suatu tempat di lapisan bertingkat yang lebih rendah),tetapi masih ingin mengubah label pengumuman dari default. Untuk melakukannya, pisahkan penetapan clickable dari pengubahan pengumuman menggunakan pengubah semantics dan tetapkan label klik di sana, untuk mengubah representasi tindakan:

@Composable
private fun ArticleList(openArticle: () -> Unit) {
    NestedArticleListItem(
        // Clickable is set separately, in a nested layer:
        onClickAction = openArticle,
        // Semantics are set here:
        modifier = Modifier.semantics {
            onClick(
                label = "Open this article",
                action = {
                    // Not needed here: openArticle()
                    true
                }
            )
        }
    )
}

Dalam hal ini, Anda tidak perlu meneruskan tindakan klik dua kali, karena Compose API yang ada, seperti clickable atau Button, menanganinya untuk Anda. Hal ini karena logika penggabungan memastikan label dan tindakan pengubah paling luar diambil untuk informasi yang ada.

Pada contoh sebelumnya, tindakan klik openArticle() diteruskan ke bawah oleh NestedArticleListItem secara otomatis ke semantik clickable-nya, dan dapat dibiarkan null dalam tindakan pengubah semantik kedua. Namun, label klik diambil dari pengubah semantik kedua onClick(label = "Open this article"), karena tidak ada di yang pertama.

Anda mungkin mengalami skenario saat mengharapkan semantik turunan digabungkan ke dalam semantik induk, tetapi hal itu tidak terjadi. Lihat Menggabungkan dan menghapus untuk mengetahui informasi yang lebih mendalam.

Komponen kustom

Untuk komponen kustom, sebagai aturan umum, lihat penerapan komponen serupa di library Material, atau library Compose lainnya, dan tiru atau ubah perilaku aksesibilitasnya jika memungkinkan.

Misalnya, jika Anda mengganti Checkbox Material dengan penerapan Anda sendiri, melihat penerapan Kotak Centang yang ada akan mengingatkan Anda untuk menambahkan pengubah triStateToggleable, yang menangani properti aksesibilitas untuk komponen ini.

Selain itu, gunakan banyak pengubah Foundation, karena hal ini mencakup pertimbangan aksesibilitas, serta praktik Compose yang ada yang dibahas di bagian ini.

Anda juga dapat menemukan contoh komponen tombol kustom di bagian Hapus dan tetapkan semantik, serta informasi yang lebih mendetail tentang cara mendukung aksesibilitas di komponen kustom di panduan API.