Langkah utama untuk meningkatkan aksesibilitas Compose

Untuk membantu pengguna yang memiliki kebutuhan aksesibilitas agar berhasil menggunakan aplikasi Anda, desain aplikasi aplikasi Anda untuk mendukung persyaratan aksesibilitas utama.

Mempertimbangkan 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 mengukur elemen-elemen ini, pastikan untuk setel ukuran minimum ke 48 dp untuk mengikuti Desain Material dengan benar panduan aksesibilitas.

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

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

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

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

Gambar 1. Kotak centang tanpa padding.

Saat menerapkan kontrol pemilihan seperti Switch, RadioButton, atau Checkbox, Anda biasanya menghilangkan perilaku yang dapat diklik ke penampung induk, menetapkan callback klik pada composable ke null, dan tambahkan toggleable atau Pengubah 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)
        }
    }
}

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 dan berukuran sangat kecil. Target sentuh area secara otomatis diperluas di luar batas Box, sehingga mengetuk di samping Box masih 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)
        )
    }
}

Untuk mencegah kemungkinan tumpang-tindih di antara area sentuh composable yang berbeda, selalu gunakan ukuran minimum yang cukup besar untuk composable. Pada contoh, proses itu akan berarti menggunakan pengubah sizeIn untuk menetapkan ukuran minimum kotak bagian 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)
        )
    }
}

Menambahkan label klik

Anda dapat menggunakan label klik untuk menambahkan makna semantik ke perilaku klik composable. Label klik menjelaskan apa yang terjadi bila pengguna berinteraksi dengan composable. Layanan aksesibilitas menggunakan label klik untuk membantu mendeskripsikan aplikasi pengguna dengan kebutuhan tertentu.

Tetapkan label klik dengan meneruskan parameter di pengubah clickable:

@Composable
private fun ArticleListItem(openArticle: () -> Unit) {
    Row(
        Modifier.clickable(
            // R.string.action_read_article = "read article"
            onClickLabel = stringResource(R.string.action_read_article),
            onClick = openArticle
        )
    ) {
        // ..
    }
}

Atau, jika Anda tidak memiliki akses ke pengubah yang dapat diklik, setel label klik di pengubah semantik:

@Composable
private fun LowLevelClickLabel(openArticle: () -> Boolean) {
    // R.string.action_read_article = "read article"
    val readArticleLabel = stringResource(R.string.action_read_article)
    Canvas(
        Modifier.semantics {
            onClick(label = readArticleLabel, action = openArticle)
        }
    ) {
        // ..
    }
}

Menjelaskan elemen visual

Saat menentukan composable Image atau Icon, tidak ada cara otomatis agar framework Android memahami aplikasi tersebut ditampilkan. Anda harus meneruskan deskripsi tekstual dari elemen visual.

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

Strip ikon yang dapat diklik, dengan

Berdasarkan ikon saja, framework Android tidak dapat mendeskripsikannya dengan pengguna yang memiliki gangguan. Framework Android membutuhkan deskripsi tekstual tambahan tentang ikon.

Parameter contentDescription mendeskripsikan elemen visual. Gunakan terjemahan {i>string<i}, seperti yang 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 visual hanya bersifat dekoratif dan Anda mungkin tidak ingin berkomunikasi mereka kepada pengguna. Jika menetapkan parameter contentDescription ke null, Anda menunjukkan ke kerangka kerja Android bahwa elemen ini tidak terkait tindakan atau status.

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

Andalah yang memutuskan apakah elemen visual tertentu memerlukan contentDescription. Tanyakan pada diri Anda sendiri apakah elemen tersebut menyampaikan informasi yang apa yang dibutuhkan pengguna untuk melakukan tugas mereka. Jika tidak, sebaiknya biarkan deskripsi.

Menggabungkan elemen

Layanan aksesibilitas seperti Talkback dan Tombol Akses memungkinkan pengguna memindahkan fokus ke seluruh elemen di layar. Elemen-elemen harus difokuskan pada tingkat perincian yang tepat. Saat setiap composable tingkat rendah di layar Anda berfokus secara mandiri, pengguna harus banyak berinteraksi untuk bergerak di seluruh layar. Jika elemen menggabungkan terlalu banyak elemen, pengguna mungkin tidak memahami elemen mana elemen saling berkaitan

Saat Anda menerapkan pengubah clickable ke composable, Compose secara otomatis menggabungkan semua elemen yang ada di composable. Hal ini juga berlaku untuk ListItem; elemen di dalam item daftar digabungkan, dan aksesibilitas melihatnya sebagai satu elemen.

Anda dapat memiliki sekumpulan composable yang membentuk grup logis, tetapi grup tersebut tidak dapat diklik atau merupakan bagian dari item daftar. Anda masih menginginkan aksesibilitas layanan untuk 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.

Anda dapat mengaktifkan Compose untuk menggabungkan elemen ini menggunakan mergeDescendants dalam pengubah semantics. Dengan cara ini, layanan aksesibilitas memilih hanya elemen gabungan, dan semua properti semantik turunan akan 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 container sekaligus, menggabungkan isi kontennya:

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

Menambahkan tindakan kustom

Lihat item daftar berikut:

Item daftar umum, yang berisi judul artikel, penulis, dan ikon bookmark.

Saat Anda menggunakan pembaca layar seperti Talkback untuk mendengar apa yang ditampilkan di layar, pertama-tama memilih seluruh item, dan kemudian ikon bookmark.

Item daftar, dengan semua elemen dipilih bersama-sama.

Item daftar, hanya dengan ikon bookmark yang dipilih

Dalam daftar panjang, hal ini dapat menjadi sangat berulang. Pendekatan yang lebih baik adalah menentukan tindakan kustom yang memungkinkan pengguna menandai item. Harap diingat bahwa Anda juga harus secara eksplisit menghapus perilaku ikon bookmark itu sendiri untuk memastikannya tidak dipilih oleh layanan aksesibilitas. Ini dilakukan dengan pengubah clearAndSetSemantics:

@Composable
private fun PostCardSimple(
    /* ... */
    isFavorite: Boolean,
    onToggleFavorite: () -> Boolean
) {
    val actionLabel = stringResource(
        if (isFavorite) R.string.unfavorite else R.string.favorite
    )
    Row(
        modifier = Modifier
            .clickable(onClick = { /* ... */ })
            .semantics {
                // Set any explicit semantic properties
                customActions = listOf(
                    CustomAccessibilityAction(actionLabel, onToggleFavorite)
                )
            }
    ) {
        /* ... */
        BookmarkButton(
            isBookmarked = isFavorite,
            onClick = onToggleFavorite,
            // Clear any semantics properties set on this node
            modifier = Modifier.clearAndSetSemantics { }
        )
    }
}

Menjelaskan status elemen

Composable dapat menentukan stateDescription untuk semantik yang Framework Android digunakan untuk membaca status tempat composable berada. Sebagai contoh, composable yang dapat diganti statusnya bisa dalam status "dicentang" atau "tidak dicentang" status. Dalam beberapa kasus, Anda mungkin ingin mengganti deskripsi status default label yang digunakan Compose. Anda dapat melakukannya dengan menentukan status secara eksplisit label deskripsi sebelum menentukan composable sebagai dapat diganti:

@Composable
private fun TopicItem(itemTitle: String, selected: Boolean, onToggle: () -> Unit) {
    val stateSubscribed = stringResource(R.string.subscribed)
    val stateNotSubscribed = stringResource(R.string.not_subscribed)
    Row(
        modifier = Modifier
            .semantics {
                // Set any explicit semantic properties
                stateDescription = if (selected) stateSubscribed else stateNotSubscribed
            }
            .toggleable(
                value = selected,
                onValueChange = { onToggle() }
            )
    ) {
        /* ... */
    }
}

Menentukan judul

Aplikasi terkadang menampilkan banyak konten di satu layar dalam container yang dapat di-scroll. Misalnya, layar dapat menampilkan konten lengkap artikel yang sedang dibaca pengguna:

Screenshot postingan blog, dengan teks artikel dalam container yang dapat di-scroll.

Pengguna dengan kebutuhan aksesibilitas mengalami kesulitan menavigasi layar tersebut. Untuk membantu navigasi, menunjukkan elemen mana yang merupakan {i>heading<i}. Dalam contoh sebelumnya, masing-masing judul subbagian dapat didefinisikan sebagai judul untuk aksesibilitas. Agak besar layanan aksesibilitas, seperti TalkBack, memungkinkan pengguna untuk menavigasi langsung dari ke judul.

Di Compose, Anda menunjukkan bahwa composable adalah judul dengan menentukan Properti semantics:

@Composable
private fun Subsection(text: String) {
    Text(
        text = text,
        style = MaterialTheme.typography.headlineSmall,
        modifier = Modifier.semantics { heading() }
    )
}

Menangani composable kustom

Setiap kali Anda mengganti komponen Material tertentu di aplikasi dengan kustom versi, Anda harus selalu mempertimbangkan aksesibilitas.

Misalnya Anda mengganti Checkbox Material dengan implementasi Anda sendiri. Anda mungkin lupa untuk menambahkan pengubah triStateToggleable, yang menangani properti aksesibilitas untuk komponen ini.

Prinsipnya adalah melihat implementasi komponen pada library Material dan meniru perilaku aksesibilitas apa pun yang dapat Anda temukan. Selain itu, gunakan banyak pengubah Foundation, berbeda dengan pengubah tingkat UI, karena hal ini mencakup pertimbangan aksesibilitas.

Menguji implementasi komponen kustom dengan beberapa layanan aksesibilitas untuk memverifikasi perilakunya.

Referensi lainnya