Ketuk dan tekan

Banyak composable memiliki dukungan bawaan untuk ketukan atau klik dan menyertakan lambda onClick. Misalnya, Anda dapat membuat Surface yang dapat diklik yang menyertakan semua perilaku Desain Material yang sesuai untuk interaksi dengan platform:

Surface(onClick = { /* handle click */ }) {
    Text("Click me!", Modifier.padding(24.dp))
}

Namun, klik bukan satu-satunya cara pengguna dapat berinteraksi dengan composable. Halaman ini berfokus pada gestur yang melibatkan satu pointer, dengan posisi pointer tersebut tidak signifikan untuk penanganan peristiwa tersebut. Tabel berikut mencantumkan jenis gestur ini:

Gestur

Deskripsi

Ketuk (atau klik)

Pointer turun, lalu naik

Ketuk dua kali

Pointer bergerak ke bawah, atas, bawah, atas

Tekan lama

Kursor turun, dan ditahan selama waktu yang lebih lama

Tekan

Pointer turun

Merespons ketukan atau klik

clickable adalah pengubah yang biasa digunakan yang membuat composable bereaksi terhadap ketukan atau klik. Pengubah ini juga menambahkan fitur tambahan, seperti dukungan untuk fokus, pengarahan kursor mouse dan stilus, serta indikasi visual yang dapat disesuaikan saat ditekan. Pengubah merespons "klik" dalam arti kata yang paling luas-- tidak hanya dengan mouse atau jari, tetapi juga peristiwa klik melalui input keyboard atau saat menggunakan layanan aksesibilitas.

Bayangkan petak gambar, dengan gambar yang ditampilkan dalam layar penuh saat pengguna mengkliknya:

Anda dapat menambahkan pengubah clickable ke setiap item dalam petak untuk menerapkan perilaku ini:

@Composable
private fun ImageGrid(photos: List<Photo>) {
    var activePhotoId by rememberSaveable { mutableStateOf<Int?>(null) }
    LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 128.dp)) {
        items(photos, { it.id }) { photo ->
            ImageItem(
                photo,
                Modifier.clickable { activePhotoId = photo.id }
            )
        }
    }
    if (activePhotoId != null) {
        FullScreenImage(
            photo = photos.first { it.id == activePhotoId },
            onDismiss = { activePhotoId = null }
        )
    }
}

Pengubah clickable juga menambahkan perilaku tambahan:

  • interactionSource dan indication, yang menggambar ripple secara default saat pengguna mengetuk composable. Pelajari cara menyesuaikannya di halaman Menangani interaksi pengguna.
  • Memungkinkan layanan aksesibilitas berinteraksi dengan elemen dengan menetapkan informasi semantik.
  • Mendukung interaksi keyboard atau joystick dengan mengizinkan fokus dan menekan Enter atau bagian tengah d-pad untuk berinteraksi.
  • Buat elemen dapat diarahkan kursor, sehingga merespons mouse atau stilus yang diarahkan ke atasnya.

Tekan lama untuk menampilkan menu konteks kontekstual

combinedClickable memungkinkan Anda menambahkan perilaku ketuk dua kali atau tekan lama selain perilaku klik normal. Anda dapat menggunakan combinedClickable untuk menampilkan menu konteks saat pengguna menyentuh lama gambar petak:

var contextMenuPhotoId by rememberSaveable { mutableStateOf<Int?>(null) }
val haptics = LocalHapticFeedback.current
LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 128.dp)) {
    items(photos, { it.id }) { photo ->
        ImageItem(
            photo,
            Modifier
                .combinedClickable(
                    onClick = { activePhotoId = photo.id },
                    onLongClick = {
                        haptics.performHapticFeedback(HapticFeedbackType.LongPress)
                        contextMenuPhotoId = photo.id
                    },
                    onLongClickLabel = stringResource(R.string.open_context_menu)
                )
        )
    }
}
if (contextMenuPhotoId != null) {
    PhotoActionsSheet(
        photo = photos.first { it.id == contextMenuPhotoId },
        onDismissSheet = { contextMenuPhotoId = null }
    )
}

Sebagai praktik terbaik, Anda harus menyertakan respons haptik saat pengguna menekan lama elemen, itulah sebabnya cuplikan menyertakan pemanggilan performHapticFeedback.

Menutup composable dengan mengetuk scrim

Pada contoh di atas, clickable dan combinedClickable menambahkan fungsi yang berguna ke composable Anda. Komponen ini menampilkan indikasi visual pada interaksi, merespons pengarahan kursor, dan menyertakan dukungan fokus, keyboard, dan aksesibilitas. Namun, perilaku tambahan ini tidak selalu diinginkan.

Mari kita lihat layar detail gambar. Latar belakang harus semi-transparan dan pengguna harus dapat mengetuk latar belakang tersebut untuk menutup layar detail:

Dalam hal ini, latar belakang tersebut tidak boleh memiliki indikasi visual apa pun pada interaksi, tidak boleh merespons pengarahan kursor, tidak boleh difokuskan, dan responsnya terhadap peristiwa keyboard dan aksesibilitas berbeda dengan composable biasa. Daripada mencoba menyesuaikan perilaku clickable, Anda dapat melakukan drop-down ke tingkat abstraksi yang lebih rendah dan langsung menggunakan pengubah pointerInput bersama dengan metode detectTapGestures:

@Composable
private fun Scrim(onClose: () -> Unit, modifier: Modifier = Modifier) {
    val strClose = stringResource(R.string.close)
    Box(
        modifier
            // handle pointer input
            .pointerInput(onClose) { detectTapGestures { onClose() } }
            // handle accessibility services
            .semantics(mergeDescendants = true) {
                contentDescription = strClose
                onClick {
                    onClose()
                    true
                }
            }
            // handle physical keyboard input
            .onKeyEvent {
                if (it.key == Key.Escape) {
                    onClose()
                    true
                } else {
                    false
                }
            }
            // draw scrim
            .background(Color.DarkGray.copy(alpha = 0.75f))
    )
}

Sebagai kunci pengubah pointerInput, Anda meneruskan lambda onClose. Tindakan ini akan otomatis mengeksekusi ulang lambda, sehingga memastikan callback yang tepat dipanggil saat pengguna mengetuk scrim.

Ketuk dua kali untuk zoom

Terkadang clickable dan combinedClickable tidak menyertakan informasi yang memadai untuk merespons interaksi dengan cara yang benar. Misalnya, composable mungkin memerlukan akses ke posisi dalam batas composable tempat interaksi terjadi.

Mari kita lihat kembali layar detail gambar. Praktik terbaiknya adalah memungkinkan pembesaran gambar dengan mengetuk dua kali:

Seperti yang dapat Anda lihat dalam video, zoom in terjadi di sekitar posisi peristiwa ketuk. Hasilnya berbeda saat kita memperbesar bagian kiri gambar dibandingkan bagian kanan. Kita dapat menggunakan pengubah pointerInput dalam kombinasi dengan detectTapGestures untuk menggabungkan posisi ketuk ke dalam penghitungan kita:

var zoomed by remember { mutableStateOf(false) }
var zoomOffset by remember { mutableStateOf(Offset.Zero) }
Image(
    painter = rememberAsyncImagePainter(model = photo.highResUrl),
    contentDescription = null,
    modifier = modifier
        .pointerInput(Unit) {
            detectTapGestures(
                onDoubleTap = { tapOffset ->
                    zoomOffset = if (zoomed) Offset.Zero else
                        calculateOffset(tapOffset, size)
                    zoomed = !zoomed
                }
            )
        }
        .graphicsLayer {
            scaleX = if (zoomed) 2f else 1f
            scaleY = if (zoomed) 2f else 1f
            translationX = zoomOffset.x
            translationY = zoomOffset.y
        }
)