Gestur

Compose menyediakan berbagai API untuk membantu Anda mendeteksi gestur yang dihasilkan dari interaksi pengguna. API mencakup berbagai kasus penggunaan:

  • Beberapa di antaranya tingkat tinggi dan didesain untuk mencakup gestur yang paling umum digunakan. Misalnya, pengubah clickable memungkinkan deteksi klik yang mudah, dan pengubah juga menyediakan fitur aksesibilitas dan menampilkan indikator visual saat diketuk (seperti ripple).

  • Ada juga pendeteksi gestur yang jarang digunakan yang menawarkan fleksibilitas yang lebih fleksibel pada tingkat lebih rendah, seperti PointerInputScope.detectTapGestures atau PointerInputScope.detectDragGestures tetapi tidak menyertakan fitur tambahan.

Mengetuk dan menekan

Pengubah clickable memungkinkan aplikasi mendeteksi klik pada elemen yang diterapkan.

@Composable
fun ClickableSample() {
    val count = remember { mutableStateOf(0) }
    // content that you want to make clickable
    Text(
        text = count.value.toString(),
        modifier = Modifier.clickable { count.value += 1 }
    )
}

Contoh elemen UI yang merespons ketuk

Jika fleksibilitas lebih dibutuhkan, Anda dapat memberikan pendeteksi gestur ketuk melalui pengubah pointerInput:

Modifier.pointerInput(Unit) {
    detectTapGestures(
        onPress = { /* Called when the gesture starts */ },
        onDoubleTap = { /* Called on Double Tap */ },
        onLongPress = { /* Called on Long Press */ },
        onTap = { /* Called on Tap */ }
    )
}

Scroll

Pengubah scroll

Pengubah verticalScroll dan horizontalScroll memberikan cara termudah untuk memungkinkan pengguna men-scroll elemen jika batas kontennya lebih besar dari batasan ukuran maksimumnya. Dengan pengubah verticalScroll dan horizontalScroll, Anda tidak perlu menerjemahkan atau melakukan offset pada konten tersebut.

@Composable
fun ScrollBoxes() {
    Column(
        modifier = Modifier
            .background(Color.LightGray)
            .size(100.dp)
            .verticalScroll(rememberScrollState())
    ) {
        repeat(10) {
            Text("Item $it", modifier = Modifier.padding(2.dp))
        }
    }
}

Daftar vertikal sederhana yang merespons gestur scroll

Dengan ScrollState, Anda dapat mengubah posisi scroll atau mendapatkan status saat ini. Untuk membuatnya dengan parameter default, gunakan rememberScrollState().

@Composable
private fun ScrollBoxesSmooth() {

    // Smoothly scroll 100px on first composition
    val state = rememberScrollState()
    LaunchedEffect(Unit) { state.animateScrollTo(100) }

    Column(
        modifier = Modifier
            .background(Color.LightGray)
            .size(100.dp)
            .padding(horizontal = 8.dp)
            .verticalScroll(state)
    ) {
        repeat(10) {
            Text("Item $it", modifier = Modifier.padding(2.dp))
        }
    }
}

Pengubah yang dapat di-scroll

Pengubah scrollable berbeda dengan pengubah scroll dalam scrollable tersebut yang mendeteksi gestur scroll, tetapi tidak mengimbangi kontennya. ScrollableController diperlukan untuk pengubah ini agar berfungsi dengan benar. Saat membuat ScrollableController, Anda harus menyediakan fungsi consumeScrollDelta yang akan dipanggil pada setiap langkah scroll (dengan input gestur, scroll halus, atau lempar) dengan delta dalam piksel. Jumlah jarak scroll yang digunakan harus ditampilkan dari fungsi ini untuk memastikan penyebaran peristiwa yang tepat.

Cuplikan berikut mendeteksi gestur dan menampilkan nilai numerik untuk offset, tetapi tidak melakukan offset pada elemen apa pun:

@Composable
fun ScrollableSample() {
    // actual composable state
    var offset by remember { mutableStateOf(0f) }
    Box(
        Modifier
            .size(150.dp)
            .scrollable(
                orientation = Orientation.Vertical,
                // Scrollable state: describes how to consume
                // scrolling delta and update offset
                state = rememberScrollableState { delta ->
                    offset += delta
                    delta
                }
            )
            .background(Color.LightGray),
        contentAlignment = Alignment.Center
    ) {
        Text(offset.toString())
    }
}

Elemen UI mendeteksi penekanan jari dan menampilkan nilai numerik untuk lokasi jari

Scroll Bertingkat

Compose mendukung scroll bertingkat, yaitu beberapa elemen memberikan reaksi terhadap gestur scroll tunggal. Contoh umum scroll bertingkat adalah daftar di dalam daftar lain, dan kasus yang lebih kompleks adalah toolbar yang dapat diciutkan.

Scroll bertingkat otomatis

Scroll bertingkat sederhana tidak memerlukan tindakan dari Anda. Gestur yang memulai tindakan scroll diterapkan dari turunan ke induk secara otomatis, sehingga saat turunan tidak dapat men-scroll lagi, gestur tersebut akan ditangani oleh elemen induknya.

Contoh berikut menunjukkan elemen dengan pengubah verticalScroll yang diterapkan di dalamnya dalam penampung yang juga memiliki pengubah verticalScroll yang diterapkan ke elemen tersebut.

val gradient = Brush.verticalGradient(0f to Color.Gray, 1000f to Color.White)
Box(
    modifier = Modifier
        .background(Color.LightGray)
        .verticalScroll(rememberScrollState())
        .padding(32.dp)
) {
    Column {
        repeat(6) {
            Box(
                modifier = Modifier
                    .height(128.dp)
                    .verticalScroll(rememberScrollState())
            ) {
                Text(
                    "Scroll here",
                    modifier = Modifier
                        .border(12.dp, Color.DarkGray)
                        .background(brush = gradient)
                        .padding(24.dp)
                        .height(150.dp)
                )
            }
        }
    }
}

Dua elemen UI scroll vertikal bertingkat, merespons gestur di dalam dan di luar elemen dalam

Menggunakan pengubah nestedScroll

Jika Anda perlu membuat scroll terkoordinasi lanjutan di antara beberapa elemen, pengubah nestedScroll memberi Anda lebih banyak fleksibilitas dengan menentukan hierarki scroll bertingkat.

Menarik

Pengubah draggable adalah titik entri tingkat tinggi untuk menarik gestur dalam satu orientasi, dan melaporkan jarak tarik dalam piksel.

Penting untuk diperhatikan bahwa pengubah ini mirip dengan scrollable, karena pengubah hanya mendeteksi gestur. Anda harus menyimpan status dan merepresentasikannya di layar dengan, misalnya, memindahkan elemen melalui pengubah offset:

var offsetX by remember { mutableStateOf(0f) }
Text(
    modifier = Modifier
        .offset { IntOffset(offsetX.roundToInt(), 0) }
        .draggable(
            orientation = Orientation.Horizontal,
            state = rememberDraggableState { delta ->
                offsetX += delta
            }
        ),
    text = "Drag me!"
)

Jika Anda perlu mengontrol seluruh gestur tarik, pertimbangkan untuk menggunakan detektor gestur tarik, melalui pengubah pointerInput.

Box(modifier = Modifier.fillMaxSize()) {
    var offsetX by remember { mutableStateOf(0f) }
    var offsetY by remember { mutableStateOf(0f) }

    Box(
        Modifier
            .offset { IntOffset(offsetX.roundToInt(), offsetY.roundToInt()) }
            .background(Color.Blue)
            .size(50.dp)
            .pointerInput(Unit) {
                detectDragGestures { change, dragAmount ->
                    change.consumeAllChanges()
                    offsetX += dragAmount.x
                    offsetY += dragAmount.y
                }
            }
    )
}

Elemen UI sedang ditarik dengan menekan jari

Fitur Geser

Pengubah swipeable memungkinkan Anda menarik elemen yang, jika dirilis, akan cenderung bergerak ke dua atau beberapa titik link yang ditetapkan dalam orientasi. Penggunaan yang umum untuk hal ini adalah dengan menerapkan pola 'geser untuk menutup'.

Perlu diperhatikan bahwa pengubah ini tidak memindahkan elemen, tetapi hanya mendeteksi gestur. Anda perlu mempertahankan status dan menampilkannya di layar dengan, misalnya, memindahkan elemen melalui pengubah offset.

Status geser diperlukan dalam pengubah swipeable, dan dapat dibuat serta diingat dengan rememberSwipeableState(). Status ini juga menyediakan serangkaian metode yang berguna untuk secara terprogram menganimasikan ke anchor (lihat snapTo, animateTo, performFling, dan performDrag) serta properti untuk mengamati progres penarikan.

Gestur geser dapat dikonfigurasi untuk memiliki jenis nilai minimum yang berbeda, seperti FixedThreshold(Dp) dan FractionalThreshold(Float), serta keduanya dapat berbeda untuk setiap kombinasi dari-ke titik link.

Untuk fleksibilitas yang lebih besar, Anda dapat mengonfigurasi resistance saat menggeser melewati batas dan juga velocityThreshold yang akan menggerakkan geser ke status berikutnya, meskipun thresholds posisi belum tercapai.

@Composable
fun SwipeableSample() {
    val width = 96.dp
    val squareSize = 48.dp

    val swipeableState = rememberSwipeableState(0)
    val sizePx = with(LocalDensity.current) { squareSize.toPx() }
    val anchors = mapOf(0f to 0, sizePx to 1) // Maps anchor points (in px) to states

    Box(
        modifier = Modifier
            .width(width)
            .swipeable(
                state = swipeableState,
                anchors = anchors,
                thresholds = { _, _ -> FractionalThreshold(0.3f) },
                orientation = Orientation.Horizontal
            )
            .background(Color.LightGray)
    ) {
        Box(
            Modifier
                .offset { IntOffset(swipeableState.offset.value.roundToInt(), 0) }
                .size(squareSize)
                .background(Color.DarkGray)
        )
    }
}

Elemen UI merespons gestur geser

Multisentuh: Menggeser, memperbesar/memperkecil, memutar

Untuk mendeteksi gestur multisentuh yang digunakan untuk menggeser, memperbesar/memperkecil, dan memutar, Anda dapat menggunakan pengubah transformable. Pengubah ini tidak mengubah elemen dengan sendirinya, hanya mendeteksi gestur.

@Composable
fun TransformableSample() {
    // set up all transformation states
    var scale by remember { mutableStateOf(1f) }
    var rotation by remember { mutableStateOf(0f) }
    var offset by remember { mutableStateOf(Offset.Zero) }
    val state = rememberTransformableState { zoomChange, offsetChange, rotationChange ->
        scale *= zoomChange
        rotation += rotationChange
        offset += offsetChange
    }
    Box(
        Modifier
            // apply other transformations like rotation and zoom
            // on the pizza slice emoji
            .graphicsLayer(
                scaleX = scale,
                scaleY = scale,
                rotationZ = rotation,
                translationX = offset.x,
                translationY = offset.y
            )
            // add transformable to listen to multitouch transformation events
            // after offset
            .transformable(state = state)
            .background(Color.Blue)
            .fillMaxSize()
    )
}

Elemen UI yang merespons gestur multisentuh—menggeser, memperbesar/memperkecil, dan memutar

Jika perlu menggabungkan perbesar/perkecil, penggeseran, dan rotasi dengan gestur lain, Anda dapat menggunakan deteksi PointerInputScope.detectTransformGestures.