ปัดไปด้านข้างเพื่อปิดหรืออัปเดต

คอมโพเนนต์ SwipeToDismissBox ช่วยให้ผู้ใช้ปิดหรืออัปเดตรายการได้ด้วยการปัดไปทางซ้ายหรือขวา

แพลตฟอร์ม API

ใช้คอมโพสิเบิล SwipeToDismissBox เพื่อใช้การดำเนินการที่เรียกให้แสดงโดยท่าทางสัมผัสด้วยการปัด พารามิเตอร์หลักๆ มีดังนี้

  • state: สถานะ SwipeToDismissBoxState ที่สร้างขึ้นเพื่อจัดเก็บค่าที่เกิดจากการคํานวณในรายการการปัด ซึ่งจะทริกเกอร์เหตุการณ์เมื่อสร้างขึ้น
  • backgroundContent: Composable ที่ปรับแต่งได้ซึ่งแสดงอยู่หลังรายการ เนื้อหาที่จะแสดงเมื่อมีการปัดเนื้อหา

ตัวอย่างพื้นฐาน: อัปเดตหรือปิดเมื่อปัด

ตัวอย่างนี้คือการใช้การปัดที่อัปเดตรายการเมื่อปัดจากต้นไปสิ้นสุด หรือปิดรายการเมื่อปัดจากสิ้นสุดไปต้น

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

ประเด็นสำคัญเกี่ยวกับรหัส

  • swipeToDismissBoxState จัดการสถานะคอมโพเนนต์ ซึ่งจะทริกเกอร์การเรียกกลับ confirmValueChange เมื่อการโต้ตอบกับรายการเสร็จสิ้น เนื้อหาของคอลแบ็กจะจัดการการดำเนินการต่างๆ ที่เป็นไปได้ ฟังก์ชันการเรียกกลับจะแสดงผลเป็นค่าบูลีนซึ่งบอกคอมโพเนนต์ว่าควรแสดงภาพเคลื่อนไหวการปิดหรือไม่ ในกรณีนี้
    • หากปัดรายการจากต้นจนจบ ระบบจะเรียกใช้ onToggleDone lambda โดยส่ง todoItem ปัจจุบัน ซึ่งสอดคล้องกับการอัปเดตรายการสิ่งที่ต้องทำ
    • หากปัดรายการจากท้ายไปต้น ระบบจะเรียกใช้ Lambda onRemove โดยส่ง todoItem ปัจจุบัน ซึ่งสอดคล้องกับการลบรายการที่ต้องทำ
    • it != StartToEnd: บรรทัดนี้จะแสดงผล true หากทิศทางการปัดไม่ใช่ StartToEnd และแสดงผล false ในกรณีอื่นๆ false ที่กลับมาจะป้องกันไม่ให้SwipeToDismissBoxหายไปทันทีหลังจากการปัด "สลับเสร็จแล้ว" ซึ่งช่วยให้เห็นภาพยืนยันหรือภาพเคลื่อนไหว
  • SwipeToDismissBox เปิดใช้การโต้ตอบด้วยการปัดแนวนอนในแต่ละรายการ เมื่ออยู่ในสถานะ "พัก" ไอคอนจะแสดงเนื้อหาภายในของคอมโพเนนต์ แต่เมื่อผู้ใช้เริ่มปัด เนื้อหาจะเลื่อนออกไปและ backgroundContent จะปรากฏขึ้น ทั้งเนื้อหาปกติและ backgroundContent จะได้รับข้อจำกัดทั้งหมดของคอนเทนเนอร์หลักเพื่อแสดงผล content วาดอยู่ด้านบนของ backgroundContent ในกรณีนี้
    • backgroundContent ติดตั้งใช้งานเป็น Icon ที่มีสีพื้นหลังตาม SwipeToDismissBoxValue ดังนี้
    • Blue เมื่อปัด StartToEnd - สลับรายการสิ่งที่ต้องทำ
    • Red เมื่อปัด EndToStart - ลบรายการสิ่งที่ต้องทํา
    • ไม่มีสิ่งใดแสดงในเบื้องหลังสำหรับ Settled — เมื่อไม่ได้ปัดรายการ ระบบจะไม่แสดงสิ่งใดในเบื้องหลัง
    • ในทำนองเดียวกัน Icon ที่แสดงจะปรับตามทิศทางการปัด
    • StartToEnd จะแสดงไอคอน CheckBox เมื่องานในรายการสิ่งที่ต้องทำเสร็จแล้ว และไอคอน CheckBoxOutlineBlank เมื่องานยังไม่เสร็จ
    • EndToStart แสดงไอคอน 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()
            )
        }
    }
}

ประเด็นสำคัญเกี่ยวกับรหัส

  • mutableStateListOf(...) สร้างลิสต์ที่สังเกตได้ซึ่งเก็บออบเจ็กต์ TodoItem ได้ เมื่อมีการเพิ่มหรือนำรายการออกจากรายการนี้ คอมโพซจะจัดเรียงองค์ประกอบ UI ที่ขึ้นอยู่กับรายการนั้นใหม่
    • ภายใน mutableStateListOf() ระบบจะเริ่มต้นออบเจ็กต์ TodoItem 4 รายการพร้อมคำอธิบายที่เกี่ยวข้อง ได้แก่ "จ่ายบิล" "ซื้อของใช้ทั่วไป" "ไปฟิตเนส" และ "ทานอาหารเย็น"
  • LazyColumn แสดงรายการ todoItems แบบเลื่อนในแนวตั้ง
  • onToggleDone = { todoItem -> ... } คือฟังก์ชันการเรียกกลับที่เรียกจากภายใน TodoListItem เมื่อผู้ใช้ทำเครื่องหมายวัตถุว่าเสร็จแล้ว โดยจะอัปเดตพร็อพเพอร์ตี้ isItemDone ของ todoItem เนื่องจาก todoItems เป็น mutableStateListOf การเปลี่ยนแปลงนี้จึงทริกเกอร์การจัดองค์ประกอบใหม่เพื่ออัปเดต UI
  • onRemove = { todoItem -> ... } คือฟังก์ชัน Callback ที่ทริกเกอร์เมื่อผู้ใช้นำรายการออก ซึ่งจะนำ todoItem ที่เฉพาะเจาะจงออกจากรายการ todoItems ซึ่งจะทําให้เกิดการจัดองค์ประกอบใหม่ด้วย และระบบจะนำรายการออกจากรายการที่แสดง
  • ระบบจะใช้ตัวแก้ไข animateItem กับ TodoListItem แต่ละรายการเพื่อให้ระบบเรียก placementSpec ของตัวแก้ไขเมื่อมีการปิดรายการ ซึ่งจะแสดงภาพการนำรายการออก รวมถึงการจัดเรียงรายการอื่นๆ ในรายการใหม่

ผลลัพธ์

วิดีโอต่อไปนี้แสดงฟังก์ชันการปัดเพื่อปิดพื้นฐานจากตัวอย่างข้อมูลก่อนหน้า

รูปที่ 1 การใช้งานการปัดเพื่อปิดแบบพื้นฐานที่ทําได้ทั้งการทําเครื่องหมายรายการว่าเสร็จแล้วและแสดงภาพเคลื่อนไหวการปิดสําหรับรายการในรายการ

ดูโค้ดตัวอย่างแบบเต็มได้ที่ไฟล์ซอร์ส GitHub

ตัวอย่างขั้นสูง: แสดงสีพื้นหลังเป็นภาพเคลื่อนไหวเมื่อปัด

ข้อมูลโค้ดต่อไปนี้แสดงวิธีใช้เกณฑ์ตำแหน่งเพื่อทำให้สีพื้นหลังของรายการเคลื่อนไหวเมื่อปัด

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

ประเด็นสำคัญเกี่ยวกับรหัส

  • drawBehind จะวาดลงในผืนผ้าใบโดยตรงหลังเนื้อหาของ Icon composable
    • drawRect() วาดสี่เหลี่ยมผืนผ้าบนผืนผ้าและเติมColorที่ระบุไว้ทั่วทั้งขอบเขตการวาด
  • เมื่อปัด พื้นหลังของรายการจะเปลี่ยนอย่างราบรื่นโดยใช้ lerp
    • สำหรับการปัดจาก StartToEnd สีพื้นหลังจะค่อยๆ เปลี่ยนจากสีเทาอ่อนเป็นสีน้ำเงิน
    • สำหรับการปัดจาก EndToStart สีพื้นหลังจะค่อยๆ เปลี่ยนจากสีเทาอ่อนเป็นสีแดง
    • ปริมาณการเปลี่ยนจากสีหนึ่งไปยังอีกสีหนึ่งจะกำหนดโดย swipeToDismissBoxState.progress
  • OutlinedCard เพิ่มการแยกภาพอย่างละเอียดระหว่างรายการในลิสต์

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

ประเด็นสำคัญเกี่ยวกับรหัส

  • ดูประเด็นสําคัญเกี่ยวกับโค้ดนี้ได้ที่ประเด็นสําคัญจากส่วนก่อนหน้า ซึ่งอธิบายข้อมูลโค้ดที่เหมือนกัน

ผลลัพธ์

วิดีโอต่อไปนี้แสดงฟังก์ชันขั้นสูงที่มีพื้นหลังเคลื่อนไหว

รูปที่ 2 การใช้การปัดเพื่อแสดงหรือลบ โดยมีสีพื้นหลังเคลื่อนไหวและเกณฑ์ที่นานขึ้นก่อนที่จะบันทึกการดำเนินการ

ดูโค้ดตัวอย่างแบบเต็มได้ที่ไฟล์ซอร์ส GitHub

แหล่งข้อมูลเพิ่มเติม