Ketuk dan tekan

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

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

Namun, klik bukanlah satu-satunya cara pengguna dapat berinteraksi dengan composable. Halaman ini berfokus pada {i>gesture <i}yang melibatkan satu {i>pointer<i}, di mana posisi pointer itu tidak signifikan untuk menangani kejadian itu. Hal berikut tabel ini mencantumkan jenis gestur berikut:

Gestur

Deskripsi

Ketuk (atau klik)

Pointer turun, lalu ke atas

Ketuk dua kali

Pointer turun, atas, bawah, atas

Tekan lama

Pointer turun, dan ditahan lebih lama

Pers

Pointer turun

Merespons ketukan atau klik

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

Bayangkan petak gambar, dengan gambar ditampilkan dalam layar penuh ketika pengguna mengekliknya:

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

@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 Menangani pengguna interaksi.
  • Memungkinkan layanan aksesibilitas berinteraksi dengan elemen dengan menyetel atribut informasi semantik.
  • Mendukung interaksi keyboard atau joystick dengan memungkinkan fokus dan menekan Enter atau bagian tengah d-pad untuk berinteraksi.
  • Buat elemen dapat diarahkan, agar merespons mouse atau stilus yang melayang masalah tersebut.

Tekan lama untuk menampilkan menu konteks kontekstual

combinedClickable memungkinkan Anda menambahkan perilaku ketuk dua kali atau tekan lama di selain perilaku klik normal. Anda dapat menggunakan combinedClickable untuk menampilkan saat pengguna menyentuh dan menahan 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 performHapticFeedback pemanggilan.

Menutup composable dengan mengetuk scrim

Pada contoh di atas, clickable dan combinedClickable menambahkan informasi yang berguna fungsi-fungsi lainnya pada composable Anda. Visualisasi menunjukkan indikasi visual pada interaksi, merespons pengarahan kursor, dan menyertakan fokus, keyboard, dan dukungan aksesibilitas. Tapi perilaku ekstra 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 itu seharusnya tidak memiliki indikasi visual di interaksi, seharusnya tidak merespons pengarahan kursor, seharusnya tidak dapat difokuskan, dan terhadap keyboard dan peristiwa aksesibilitas berbeda dari biasanya composable. Daripada mencoba menyesuaikan perilaku clickable, Anda dapat menghapus ke tingkat abstraksi yang lebih rendah dan langsung menggunakan pengubah pointerInput dalam kombinasi 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. Ini secara otomatis mengeksekusi ulang lambda, memastikan callback yang tepat dipanggil saat pengguna mengetuk scrim.

Ketuk dua kali untuk zoom

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

Mari kita lihat lagi layar detail gambar. Praktik terbaik adalah membuatnya memperbesar gambar dengan mengetuk dua kali:

Seperti yang Anda lihat dalam video, zoom terjadi di sekitar posisi ketukan peristiwa. Hasilnya akan berbeda jika kita memperbesar bagian kiri gambar versus bagian yang tepat. Kita dapat menggunakan pengubah pointerInput bersama-sama dengan detectTapGestures untuk memasukkan posisi ketuk ke kalkulasi:

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