Menangani interaksi pengguna

Komponen antarmuka pengguna memberikan masukan kepada pengguna perangkat melalui cara komponen merespons interaksi pengguna. Setiap komponen memiliki cara sendiri untuk merespons interaksi, yang membantu pengguna mengetahui apa yang akan terjadi dengan interaksi yang mereka lakukan. Misalnya, jika pengguna menyentuh tombol pada layar sentuh perangkat, tombol tersebut kemungkinan akan berubah sedemikian rupa, mungkin dengan menambahkan warna sorotan. Perubahan ini memberi tahu pengguna bahwa mereka telah menyentuh tombol. Jika pengguna tidak ingin melakukannya, mereka akan menarik jari mereka dari tombol sebelum melepaskan–jika tidak, tombol akan berfungsi.

Dokumentasi Gestur Compose mencakup cara komponen Compose menangani peristiwa pointer level rendah, seperti gerakan pointer dan klik. Secara langsung, Compose memisahkan peristiwa level rendah tersebut menjadi interaksi level yang lebih tinggi–misalnya, serangkaian peristiwa pointer dapat ditambahkan ke penekanan dan pelepasan tombol. Memahami abstraksi level tinggi tersebut dapat membantu Anda menyesuaikan respons UI terhadap pengguna. Misalnya, Anda mungkin ingin menyesuaikan perubahan tampilan komponen saat pengguna berinteraksi dengan komponen, atau mungkin Anda hanya ingin mempertahankan log tindakan pengguna tersebut. Dokumen ini memberikan informasi yang Anda perlukan untuk mengubah elemen UI standar, atau mendesain elemen UI Anda sendiri.

Interaksi

Dalam banyak kasus, Anda tidak perlu mengetahui cara komponen Compose menafsirkan interaksi pengguna. Misalnya, Button bergantung pada Modifier.clickable untuk mencari tahu apakah pengguna mengklik tombol atau tidak. Jika menambahkan tombol khusus ke aplikasi, Anda dapat menentukan kode onClick tombol, dan Modifier.clickable akan menjalankan kode tersebut jika sesuai. Itu berarti Anda tidak perlu mengetahui apakah pengguna mengetuk layar atau memilih tombol dengan keyboard; Modifier.clickable mengetahui bahwa pengguna melakukan klik, dan merespons dengan menjalankan kode onClick.

Namun, jika ingin menyesuaikan respons komponen UI terhadap perilaku pengguna, Anda mungkin perlu mengetahui apa yang terjadi lebih lanjut. Bagian ini memberikan beberapa informasi tersebut.

Saat pengguna berinteraksi dengan komponen UI, sistem merepresentasikan perilakunya dengan menghasilkan sejumlah peristiwa Interaction. Misalnya, jika pengguna menyentuh tombol, tombol tersebut akan menghasilkan PressInteraction.Press. Jika pengguna mengangkat jari di dalam tombol, tindakan ini akan menghasilkan PressInteraction.Release, yang memberi tahu tombol bahwa klik telah selesai. Di sisi lain, jika pengguna menarik jari ke luar tombol, lalu mengangkat jari, tombol akan menghasilkan PressInteraction.Cancel, untuk menunjukkan bahwa penekanan pada tombol dibatalkan, bukan diselesaikan.

Interaksi ini tidak terkonfigurasi. Yaitu, peristiwa interaksi level rendah ini tidak bermaksud menafsirkan makna tindakan pengguna, atau urutannya. Peristiwa ini juga tidak menafsirkan tindakan pengguna mana yang mungkin diprioritaskan dari tindakan lainnya.

Interaksi ini biasanya berpasangan, dengan awal dan akhir. Interaksi kedua berisi referensi ke interaksi pertama. Misalnya, jika pengguna menyentuh tombol, lalu mengangkat jarinya, sentuhan tersebut akan menghasilkan interaksi PressInteraction.Press, dan pelepasan akan menghasilkan PressInteraction.Release; Release memiliki properti press yang mengidentifikasi PressInteraction.Press awal.

Anda dapat melihat interaksi untuk komponen tertentu dengan mengamati InteractionSource-nya. InteractionSource di-build di atas alur Kotlin, sehingga Anda dapat mengumpulkan interaksi dari alur tersebut dengan cara yang sama seperti Anda mengerjakan alur lainnya.

Status interaksi

Anda mungkin ingin memperluas fungsi bawaan komponen dengan melacak interaksi sendiri. Misalnya, mungkin Anda ingin tombol berubah warna saat ditekan. Cara termudah untuk melacak interaksi adalah dengan mengamati status interaksi yang sesuai. InteractionSource menawarkan sejumlah metode yang mengungkap berbagai status interaksi sebagai status. Misalnya, jika ingin melihat apakah tombol tertentu ditekan, Anda dapat memanggil metode InteractionSource.collectIsPressedAsState():

val interactionSource = remember { MutableInteractionSource() }
val isPressed by interactionSource.collectIsPressedAsState()

Button(
    onClick = { /* do something */ },
    interactionSource = interactionSource) {
    Text(if (isPressed) "Pressed!" else "Not pressed")
}

Selain collectIsPressedAsState(), Compose juga menyediakan collectIsFocusedAsState(), collectIsDraggedAsState(), dan collectIsHoveredAsState(). Metode ini sebenarnya adalah metode praktis yang dibuat di atas InteractionSource API dengan level yang lebih rendah. Dalam beberapa kasus, Anda mungkin ingin menggunakan fungsi level rendah tersebut secara langsung.

Misalnya, anggaplah Anda perlu mengetahui apakah tombol sedang ditekan, dan apakah tombol sedang ditarik. Jika Anda menggunakan collectIsPressedAsState() dan collectIsDraggedAsState(), Compose akan melakukan banyak tugas duplikat, dan tidak ada jaminan Anda akan mendapatkan semua interaksi dalam urutan yang tepat. Untuk situasi seperti ini, Anda mungkin ingin langsung menggunakan InteractionSource. Bagian berikut menjelaskan cara melacak interaksi, dengan hanya mendapatkan informasi yang Anda butuhkan.

Menggunakan InteractionSource

Jika memerlukan informasi level rendah terkait interaksi dengan komponen, Anda dapat menggunakan flow API standar untuk InteractionSource komponen tersebut. Misalnya, Anda ingin mempertahankan daftar interaksi tekan dan tarik untuk InteractionSource. Kode ini melakukan separuh pekerjaan, yang menambahkan penekanan baru ke daftar saat masuk:

val interactionSource = remember { MutableInteractionSource() }
val interactions = remember { mutableStateListOf<Interaction>() }

LaunchedEffect(interactionSource) {
    interactionSource.interactions.collect { interaction ->
        when (interaction) {
            is PressInteraction.Press -> {
                interactions.add(interaction)
            }
            is DragInteraction.Start -> {
                interactions.add(interaction)
            }
        }
    }
}

Namun, selain menambahkan interaksi baru, Anda juga harus menghapus interaksi saat interaksi tersebut berakhir (misalnya, saat pengguna mengangkat jarinya kembali dari komponen). Hal ini mudah dilakukan karena interaksi akhir selalu membawa referensi ke interaksi awal yang terkait. Kode ini menunjukkan cara menghapus interaksi yang telah berakhir:

val interactions = remember { mutableStateListOf<Interaction>() }

LaunchedEffect(interactionSource) {
    interactionSource.interactions.collect { interaction ->
        when (interaction) {
            is PressInteraction.Press -> {
                interactions.add(interaction)
            }
            is PressInteraction.Release -> {
                interactions.remove(interaction.press)
            }
            is PressInteraction.Cancel -> {
                interactions.remove(interaction.press)
            }
            is DragInteraction.Start -> {
                interactions.add(interaction)
            }
            is DragInteraction.Stop -> {
                interactions.remove(interaction.start)
            }
            is DragInteraction.Cancel -> {
                interactions.remove(interaction.start)
            }
        }
    }
}

Sekarang, jika ingin mengetahui apakah komponen saat ini ditekan atau ditarik, yang harus Anda lakukan adalah memeriksa apakah interactions kosong:

val isPressedOrDragged = interactions.isNotEmpty()

Jika Anda ingin mengetahui interaksi terbaru, cukup lihat item terakhir dalam daftar. Misalnya, ini adalah cara implementasi ripple Compose mengetahui overlay status yang sesuai untuk digunakan dalam interaksi terbaru:

val lastInteraction = when (interactions.lastOrNull()) {
    is DragInteraction.Start -> "Dragged"
    is PressInteraction.Press -> "Pressed"
    else -> "No state"
}

Mempelajari contoh

Untuk mengetahui cara mem-build komponen dengan respons kustom terhadap input, berikut contoh tombol yang dimodifikasi. Dalam hal ini, misalnya Anda menginginkan tombol yang merespons penekanan dengan mengubah tampilannya:

Animasi tombol yang secara dinamis menambahkan ikon saat diklik

Untuk melakukannya, build composable kustom berdasarkan Button, dan minta parameter icon tambahan untuk menggambar ikon (dalam hal ini, keranjang belanja). Anda memanggil collectIsPressedAsState() untuk melacak apakah pengguna mengarahkan kursor ke tombol; saat itu terjadi, Anda menambahkan ikon. Kode akan terlihat seperti berikut ini:

@Composable
fun PressIconButton(
    onClick: () -> Unit,
    icon: @Composable () -> Unit,
    text: @Composable () -> Unit,
    modifier: Modifier = Modifier,
    interactionSource: MutableInteractionSource =
        remember { MutableInteractionSource() },
) {
    val isPressed by interactionSource.collectIsPressedAsState()
    Button(onClick = onClick, modifier = modifier,
        interactionSource = interactionSource) {
        AnimatedVisibility(visible = isPressed) {
            if (isPressed) {
                Row {
                    icon()
                    Spacer(Modifier.size(ButtonDefaults.IconSpacing))
                }
            }
        }
        text()
    }
}

Dan berikut tampilan penggunaan composable baru tersebut:

PressIconButton(
    onClick = {},
    icon = { Icon(Icons.Filled.ShoppingCart, contentDescription = null) },
    text = { Text("Add to cart") }
)

Karena PressIconButton baru ini di-build di atas Button Material yang ada, kode ini bereaksi terhadap interaksi pengguna dengan cara yang biasa. Saat pengguna menekannya, tombol akan sedikit mengubah opasitasnya, seperti Button Material biasa. Selain itu, berkat kode baru, HoverIconButton secara dinamis merespons pengarahan kursor dengan menambahkan ikon.