Geser untuk menutup atau memperbarui

Komponen SwipeToDismissBox memungkinkan pengguna menutup atau memperbarui item dengan menggesernya ke kiri atau kanan.

Platform API

Gunakan composable SwipeToDismissBox untuk menerapkan tindakan yang dipicu oleh gestur geser. Parameter utamanya meliputi:

  • state: Status SwipeToDismissBoxState yang dibuat untuk menyimpan nilai yang dihasilkan oleh penghitungan pada item geser, yang memicu peristiwa saat dibuat.
  • backgroundContent: Composable yang dapat disesuaikan yang ditampilkan di belakang konten item yang ditampilkan saat konten digeser.

Contoh dasar: Memperbarui atau menutup dengan menggeser

Cuplikan dalam contoh ini menunjukkan implementasi geser yang memperbarui item saat digeser dari awal hingga akhir, atau menutup item saat digeser dari akhir ke awal.

data class TodoItem(
    val itemDescription: String,
    var isItemDone: Boolean = false
)

@Composable
fun TodoListItem(
    todoItem: TodoItem,
    onToggleDone: (TodoItem) -> Unit,
    onRemove: (TodoItem) -> Unit,
    modifier: Modifier = Modifier,
) {
    val swipeToDismissBoxState = rememberSwipeToDismissBoxState(
        confirmValueChange = {
            if (it == StartToEnd) onToggleDone(todoItem)
            else if (it == EndToStart) onRemove(todoItem)
            // Reset item when toggling done status
            it != StartToEnd
        }
    )

    SwipeToDismissBox(
        state = swipeToDismissBoxState,
        modifier = modifier.fillMaxSize(),
        backgroundContent = {
            when (swipeToDismissBoxState.dismissDirection) {
                StartToEnd -> {
                    Icon(
                        if (todoItem.isItemDone) Icons.Default.CheckBox else Icons.Default.CheckBoxOutlineBlank,
                        contentDescription = if (todoItem.isItemDone) "Done" else "Not done",
                        modifier = Modifier
                            .fillMaxSize()
                            .background(Color.Blue)
                            .wrapContentSize(Alignment.CenterStart)
                            .padding(12.dp),
                        tint = Color.White
                    )
                }
                EndToStart -> {
                    Icon(
                        imageVector = Icons.Default.Delete,
                        contentDescription = "Remove item",
                        modifier = Modifier
                            .fillMaxSize()
                            .background(Color.Red)
                            .wrapContentSize(Alignment.CenterEnd)
                            .padding(12.dp),
                        tint = Color.White
                    )
                }
                Settled -> {}
            }
        }
    ) {
        ListItem(
            headlineContent = { Text(todoItem.itemDescription) },
            supportingContent = { Text("swipe me to update or remove.") }
        )
    }
}

Poin-poin penting tentang kode

  • swipeToDismissBoxState mengelola status komponen. Tindakan ini memicu callback confirmValueChange setelah interaksi dengan item selesai. Isi callback menangani berbagai kemungkinan tindakan. Callback menampilkan boolean yang memberi tahu komponen apakah harus menampilkan animasi penonaktifan. Dalam hal ini:
    • Jika item digeser dari awal hingga akhir, item tersebut akan memanggil lambda onToggleDone, yang meneruskan todoItem saat ini. Hal ini sesuai dengan memperbarui item daftar tugas.
    • Jika item digeser dari akhir ke awal, item tersebut akan memanggil lambda onRemove, yang meneruskan todoItem saat ini. Tindakan ini sesuai dengan penghapusan item daftar tugas.
    • it != StartToEnd: Baris ini menampilkan true jika arah geser bukan StartToEnd, dan false jika sebaliknya. Menampilkan false mencegah SwipeToDismissBox langsung menghilang setelah geser "tombol selesai", sehingga memungkinkan konfirmasi atau animasi visual.
  • SwipeToDismissBox memungkinkan interaksi geser horizontal pada setiap item. Dalam istirahat, komponen ini menampilkan konten bagian dalam komponen, tetapi saat pengguna mulai menggeser, konten akan dipindahkan dan backgroundContent akan muncul. Konten normal dan backgroundContent mendapatkan batasan penuh penampung induk untuk merendernya. content digambar di atas backgroundContent. Dalam hal ini:
    • backgroundContent diterapkan sebagai Icon dengan warna latar belakang berdasarkan SwipeToDismissBoxValue:
    • Blue saat menggeser StartToEnd — mengalihkan item daftar tugas.
    • Red saat menggeser EndToStart — menghapus item daftar tugas.
    • Tidak ada yang ditampilkan di latar belakang untuk Settled — saat item tidak digeser, tidak ada yang ditampilkan di latar belakang.
    • Demikian pula, Icon yang ditampilkan akan menyesuaikan dengan arah geser:
    • StartToEnd menampilkan ikon CheckBox saat item daftar tugas selesai dan ikon CheckBoxOutlineBlank saat belum selesai.
    • EndToStart menampilkan ikon Delete.

@Composable
private fun SwipeItemExample() {
    val todoItems = remember {
        mutableStateListOf(
            TodoItem("Pay bills"), TodoItem("Buy groceries"),
            TodoItem("Go to gym"), TodoItem("Get dinner")
        )
    }

    LazyColumn {
        items(
            items = todoItems,
            key = { it.itemDescription }
        ) { todoItem ->
            TodoListItem(
                todoItem = todoItem,
                onToggleDone = { todoItem ->
                    todoItem.isItemDone = !todoItem.isItemDone
                },
                onRemove = { todoItem ->
                    todoItems -= todoItem
                },
                modifier = Modifier.animateItem()
            )
        }
    }
}

Poin-poin penting tentang kode

  • mutableStateListOf(...) membuat daftar yang dapat diamati yang dapat menyimpan objek TodoItem. Saat item ditambahkan atau dihapus dari daftar ini, Compose akan merekomposisi bagian UI yang bergantung padanya.
    • Di dalam mutableStateListOf(), empat objek TodoItem diinisialisasi dengan deskripsi masing-masing: "Bayar tagihan", "Beli bahan makanan", "Pergi ke gym", dan "Beli makan malam".
  • LazyColumn menampilkan daftar todoItems yang di-scroll secara vertikal.
  • onToggleDone = { todoItem -> ... } adalah fungsi callback yang dipanggil dari dalam TodoListItem saat pengguna menandai objek sebagai selesai. Class ini memperbarui properti isItemDone dari todoItem. Karena todoItems adalah mutableStateListOf, perubahan ini memicu rekomposisi, yang mengupdate UI.
  • onRemove = { todoItem -> ... } adalah fungsi callback yang dipicu saat pengguna menghapus item. Tindakan ini akan menghapus todoItem tertentu dari daftar todoItems. Tindakan ini juga menyebabkan rekomposisi, dan item akan dihapus dari daftar yang ditampilkan.
  • Pengubah animateItem diterapkan ke setiap TodoListItem sehingga placementSpec pengubah dipanggil saat item ditutup. Tindakan ini akan menganimasikan penghapusan item, serta pengurutan ulang item lain dalam daftar.

Hasil

Video berikut menunjukkan fungsi geser untuk menutup dasar dari cuplikan sebelumnya:

Gambar 1. Implementasi dasar geser untuk menutup yang dapat menandai item sebagai selesai dan menampilkan animasi tutup untuk item dalam daftar.

Lihat file sumber GitHub untuk mengetahui kode contoh lengkap.

Contoh lanjutan: Menganimasikan warna latar belakang saat menggeser

Cuplikan berikut menunjukkan cara menggabungkan nilai minimum posisi untuk menganimasikan warna latar belakang item saat menggeser.

data class TodoItem(
    val itemDescription: String,
    var isItemDone: Boolean = false
)

@Composable
fun TodoListItemWithAnimation(
    todoItem: TodoItem,
    onToggleDone: (TodoItem) -> Unit,
    onRemove: (TodoItem) -> Unit,
    modifier: Modifier = Modifier,
) {
    val swipeToDismissBoxState = rememberSwipeToDismissBoxState(
        confirmValueChange = {
            if (it == StartToEnd) onToggleDone(todoItem)
            else if (it == EndToStart) onRemove(todoItem)
            // Reset item when toggling done status
            it != StartToEnd
        }
    )

    SwipeToDismissBox(
        state = swipeToDismissBoxState,
        modifier = modifier.fillMaxSize(),
        backgroundContent = {
            when (swipeToDismissBoxState.dismissDirection) {
                StartToEnd -> {
                    Icon(
                        if (todoItem.isItemDone) Icons.Default.CheckBox else Icons.Default.CheckBoxOutlineBlank,
                        contentDescription = if (todoItem.isItemDone) "Done" else "Not done",
                        modifier = Modifier
                            .fillMaxSize()
                            .drawBehind {
                                drawRect(lerp(Color.LightGray, Color.Blue, swipeToDismissBoxState.progress))
                            }
                            .wrapContentSize(Alignment.CenterStart)
                            .padding(12.dp),
                        tint = Color.White
                    )
                }
                EndToStart -> {
                    Icon(
                        imageVector = Icons.Default.Delete,
                        contentDescription = "Remove item",
                        modifier = Modifier
                            .fillMaxSize()
                            .background(lerp(Color.LightGray, Color.Red, swipeToDismissBoxState.progress))
                            .wrapContentSize(Alignment.CenterEnd)
                            .padding(12.dp),
                        tint = Color.White
                    )
                }
                Settled -> {}
            }
        }
    ) {
        OutlinedCard(shape = RectangleShape) {
            ListItem(
                headlineContent = { Text(todoItem.itemDescription) },
                supportingContent = { Text("swipe me to update or remove.") }
            )
        }
    }
}

Poin-poin penting tentang kode

  • drawBehind menggambar langsung ke kanvas di belakang konten composable Icon.
    • drawRect() menggambar persegi panjang di kanvas dan mengisi seluruh batas cakupan gambar dengan Color yang ditentukan.
  • Saat menggeser, warna latar belakang item akan bertransisi dengan lancar menggunakan lerp.
    • Untuk geser dari StartToEnd, warna latar belakang secara bertahap berubah dari abu-abu terang menjadi biru.
    • Untuk geser dari EndToStart, warna latar belakang secara bertahap berubah dari abu-abu terang menjadi merah.
    • Jumlah transisi dari satu warna ke warna berikutnya ditentukan oleh swipeToDismissBoxState.progress.
  • OutlinedCard menambahkan pemisahan visual yang halus di antara item daftar.

@Composable
private fun SwipeItemWithAnimationExample() {
    val todoItems = remember {
        mutableStateListOf(
            TodoItem("Pay bills"), TodoItem("Buy groceries"),
            TodoItem("Go to gym"), TodoItem("Get dinner")
        )
    }

    LazyColumn {
        items(
            items = todoItems,
            key = { it.itemDescription }
        ) { todoItem ->
            TodoListItemWithAnimation(
                todoItem = todoItem,
                onToggleDone = { todoItem ->
                    todoItem.isItemDone = !todoItem.isItemDone
                },
                onRemove = { todoItem ->
                    todoItems -= todoItem
                },
                modifier = Modifier.animateItem()
            )
        }
    }
}

Poin-poin penting tentang kode

  • Untuk mengetahui poin-poin penting tentang kode ini, lihat Poin-poin penting dari bagian sebelumnya, yang menjelaskan cuplikan kode yang identik.

Hasil

Video berikut menunjukkan fungsi lanjutan dengan warna latar belakang animasikan:

Gambar 2. Implementasi geser untuk menampilkan atau menghapus, dengan warna latar belakang animasi dan nilai minimum yang lebih lama sebelum tindakan didaftarkan.

Lihat file sumber GitHub untuk mengetahui kode contoh lengkap.

Referensi lainnya