ทำความเข้าใจท่าทางสัมผัส

มีคำศัพท์และแนวคิดหลายอย่างที่สำคัญต่อการทำความเข้าใจเมื่อทำงานเกี่ยวกับการจัดการท่าทางสัมผัสในแอปพลิเคชัน หน้านี้จะอธิบายคำว่าพอยเตอร์ เหตุการณ์พอยเตอร์ และท่าทางสัมผัส รวมถึงแนะนำระดับนามธรรมต่างๆ สำหรับท่าทางสัมผัส นอกจากนี้ยังเจาะลึกรายละเอียดเพิ่มเติมเกี่ยวกับการใช้และการเผยแพร่เหตุการณ์ด้วย

คำจำกัดความ

หากต้องการทำความเข้าใจแนวคิดต่างๆ ในหน้านี้ คุณต้องเข้าใจคำศัพท์บางคำที่ใช้ดังนี้

  • ตัวชี้: วัตถุที่จับต้องได้ซึ่งคุณใช้โต้ตอบกับแอปพลิเคชันได้ สำหรับอุปกรณ์เคลื่อนที่ ตัวชี้ที่พบได้บ่อยที่สุดคือนิ้วของคุณที่โต้ตอบกับหน้าจอสัมผัส หรือจะใช้สไตลัสแทนนิ้วก็ได้ สำหรับหน้าจอขนาดใหญ่ คุณสามารถใช้เมาส์หรือแทร็กแพดเพื่อโต้ตอบกับจอแสดงผลโดยอ้อมได้ อุปกรณ์อินพุตต้องสามารถ "ชี้" ไปที่พิกัดเพื่อถือว่าเป็นตัวชี้ ดังนั้นแป้นพิมพ์จึงไม่ถือว่าเป็นตัวชี้ ใน Compose ประเภทตัวชี้จะรวมอยู่ในการเปลี่ยนแปลงตัวชี้โดยใช้ PointerType
  • เหตุการณ์ตัวชี้: อธิบายการโต้ตอบระดับต่ำของตัวชี้อย่างน้อย 1 ตัวกับแอปพลิเคชัน ณ เวลาหนึ่งๆ การโต้ตอบด้วยตัวชี้ เช่น การวางนิ้วบนหน้าจอหรือการลากเมาส์ จะทริกเกอร์เหตุการณ์ ใน Compose ข้อมูลที่เกี่ยวข้องทั้งหมดสำหรับเหตุการณ์ดังกล่าวจะอยู่ในคลาส PointerEvent
  • ท่าทางสัมผัส: ลำดับของเหตุการณ์เคอร์เซอร์ที่ตีความได้ว่าเป็นการดำเนินการเดียว ตัวอย่างเช่น ท่าทางสัมผัสการแตะถือเป็นลำดับของเหตุการณ์การกดลงตามด้วยเหตุการณ์การยกขึ้น มีท่าทางสัมผัสทั่วไปที่แอปจำนวนมากใช้ เช่น แตะ ลาก หรือเปลี่ยนรูปแบบ แต่คุณก็สร้างท่าทางสัมผัสที่กำหนดเองได้เมื่อจำเป็น

ระดับต่างๆ ของการแยกส่วนข้อมูล

Jetpack Compose มีระดับนามธรรมที่แตกต่างกันสำหรับการจัดการท่าทางสัมผัส ระดับบนสุดคือการรองรับคอมโพเนนต์ คอมโพสิเบิลอย่าง Button จะรวมการรองรับท่าทางสัมผัสโดยอัตโนมัติ หากต้องการเพิ่มการรองรับท่าทางสัมผัสให้กับคอมโพเนนต์ที่กำหนดเอง คุณสามารถเพิ่มตัวปรับแต่งท่าทางสัมผัส เช่น clickable ลงใน Composable ใดก็ได้ สุดท้าย หากต้องการท่าทางสัมผัสที่กำหนดเอง คุณสามารถใช้ตัวปรับแต่ง pointerInput ได้

ตามกฎแล้ว ให้สร้างในระดับ Abstraction สูงสุดที่มีฟังก์ชันการทำงานที่คุณต้องการ วิธีนี้จะช่วยให้คุณได้รับประโยชน์จากแนวทางปฏิบัติแนะนำที่รวมอยู่ในเลเยอร์ ตัวอย่างเช่น Button มีข้อมูลเชิงความหมายมากกว่า ซึ่งใช้สำหรับการช่วยเหลือพิเศษ มากกว่า clickable ซึ่งมีข้อมูลมากกว่าการใช้งาน pointerInput แบบดิบ

การสนับสนุนคอมโพเนนต์

คอมโพเนนต์ที่พร้อมใช้งานทันทีจำนวนมากใน Compose มีการจัดการท่าทางสัมผัสภายในบางประเภท ตัวอย่างเช่น LazyColumn จะตอบสนองต่อท่าทางการลากด้วยการเลื่อนเนื้อหา Button จะแสดงเอฟเฟกต์ริปเปิลเมื่อคุณกด และคอมโพเนนต์ SwipeToDismiss จะมีตรรกะการปัดเพื่อปิดองค์ประกอบ การจัดการท่าทางสัมผัสประเภทนี้จะทำงานโดยอัตโนมัติ

นอกจากการจัดการท่าทางสัมผัสภายในแล้ว คอมโพเนนต์จำนวนมากยังกำหนดให้ผู้เรียกจัดการท่าทางสัมผัสด้วย เช่น Button จะตรวจจับการแตะและทริกเกอร์กิจกรรมการคลิกโดยอัตโนมัติ คุณส่ง Lambda onClick ไปยัง Button เพื่อตอบสนองต่อท่าทางสัมผัส ในทำนองเดียวกัน คุณเพิ่ม onValueChange lambda ลงใน Slider เพื่อตอบสนองต่อผู้ใช้ที่ลากแฮนเดิลแถบเลื่อน

เมื่อเหมาะกับ Use Case ของคุณ ให้เลือกใช้ท่าทางสัมผัสที่รวมอยู่ในคอมโพเนนต์ เนื่องจากมีการรองรับโฟกัสและการช่วยเหลือพิเศษที่พร้อมใช้งานทันที และได้รับการทดสอบมาเป็นอย่างดี เช่น Button จะมีการทำเครื่องหมายด้วยวิธีพิเศษเพื่อให้บริการการช่วยเหลือพิเศษอธิบายได้อย่างถูกต้องว่าเป็นปุ่ม แทนที่จะเป็นเพียงองค์ประกอบที่คลิกได้

// Talkback: "Click me!, Button, double tap to activate"
Button(onClick = { /* TODO */ }) { Text("Click me!") }
// Talkback: "Click me!, double tap to activate"
Box(Modifier.clickable { /* TODO */ }) { Text("Click me!") }

ดูข้อมูลเพิ่มเติมเกี่ยวกับการช่วยเหลือพิเศษใน Compose ได้ที่การช่วยเหลือพิเศษใน Compose

เพิ่มท่าทางสัมผัสที่เฉพาะเจาะจงลงใน Composable ที่กำหนดเองด้วยตัวแก้ไข

คุณสามารถใช้ตัวปรับแต่งท่าทางสัมผัสกับ Composable ใดก็ได้เพื่อให้ Composable ฟังท่าทางสัมผัส เช่น คุณสามารถอนุญาตให้ Box ทั่วไปจัดการท่าทางการแตะโดยทำให้เป็น clickable หรืออนุญาตให้ Column จัดการการเลื่อนแนวตั้งโดยใช้ verticalScroll

มีตัวแก้ไขหลายตัวสำหรับจัดการท่าทางสัมผัสประเภทต่างๆ ดังนี้

ตามกฎแล้ว ให้เลือกใช้ตัวแก้ไขท่าทางสัมผัสที่พร้อมใช้งานทันทีมากกว่าการจัดการท่าทางสัมผัสที่กำหนดเอง ตัวแก้ไขจะเพิ่มฟังก์ชันการทำงานเพิ่มเติมจากการจัดการเหตุการณ์ตัวชี้แบบล้วนๆ ตัวอย่างเช่น ตัวแก้ไข clickable ไม่เพียงเพิ่มการตรวจจับการกดและการแตะเท่านั้น แต่ยังเพิ่มข้อมูลเชิงความหมาย การบ่งชี้ภาพในการโต้ตอบ การวางเมาส์เหนือ การโฟกัส และการรองรับแป้นพิมพ์ด้วย คุณสามารถตรวจสอบซอร์สโค้ดของ clickable เพื่อดูวิธีเพิ่มฟังก์ชันการทำงานได้

เพิ่มท่าทางสัมผัสที่กำหนดเองไปยัง Composable ใดก็ได้ด้วยตัวแก้ไข pointerInput

ท่าทางสัมผัสบางอย่างไม่ได้ใช้ตัวปรับแต่งท่าทางสัมผัสที่พร้อมใช้งานทันที ตัวอย่างเช่น คุณไม่สามารถใช้ตัวปรับแต่งเพื่อตอบสนองต่อการลากหลังจากกดค้าง การกดปุ่ม Control ค้างไว้แล้วคลิก หรือการแตะด้วย 3 นิ้ว แต่คุณสามารถเขียนตัวแฮนเดิลท่าทางสัมผัสของคุณเองเพื่อระบุท่าทางสัมผัสที่กำหนดเองเหล่านี้ได้ คุณสามารถสร้างตัวแฮนเดิลท่าทางสัมผัสได้ด้วยตัวแก้ไข pointerInput ซึ่งช่วยให้คุณเข้าถึงเหตุการณ์ตัวชี้ข้อมูลดิบได้

โค้ดต่อไปนี้จะรอรับเหตุการณ์ตัวชี้ข้อมูลดิบ

@Composable
private fun LogPointerEvents(filter: PointerEventType? = null) {
    var log by remember { mutableStateOf("") }
    Column {
        Text(log)
        Box(
            Modifier
                .size(100.dp)
                .background(Color.Red)
                .pointerInput(filter) {
                    awaitPointerEventScope {
                        while (true) {
                            val event = awaitPointerEvent()
                            // handle pointer event
                            if (filter == null || event.type == filter) {
                                log = "${event.type}, ${event.changes.first().position}"
                            }
                        }
                    }
                }
        )
    }
}

หากคุณแยกข้อมูลโค้ดนี้ องค์ประกอบหลักจะมีดังนี้

  • ตัวแก้ไข pointerInput คุณส่งคีย์อย่างน้อย 1 รายการให้เมธอด เมื่อค่าของคีย์ใดคีย์หนึ่งเปลี่ยนแปลง ระบบจะเรียกใช้ Lambda ของเนื้อหาตัวแก้ไขอีกครั้ง ตัวอย่างจะส่งตัวกรองที่ไม่บังคับไปยัง Composable หากค่าของตัวกรองนั้นเปลี่ยนแปลง ควรเรียกใช้ตัวแฮนเดิลเหตุการณ์ตัวชี้อีกครั้งเพื่อให้แน่ใจว่าระบบบันทึกเหตุการณ์ที่ถูกต้อง
  • awaitPointerEventScope สร้างขอบเขตโครูทีนที่ใช้รอเหตุการณ์เคอร์เซอร์ได้
  • awaitPointerEvent จะระงับโครูทีนจนกว่าจะเกิดเหตุการณ์ตัวชี้ถัดไป

แม้ว่าการฟังเหตุการณ์อินพุตดิบจะมีประสิทธิภาพ แต่ก็มีความซับซ้อนในการเขียนท่าทางสัมผัสที่กำหนดเองโดยอิงตามข้อมูลดิบนี้ มีเมธอดของยูทิลิตีมากมายที่ช่วยให้สร้างท่าทางสัมผัสที่กำหนดเองได้ง่ายขึ้น

ตรวจจับท่าทางสัมผัสแบบเต็ม

คุณสามารถรอให้เกิดท่าทางสัมผัสที่เฉพาะเจาะจงและตอบสนองอย่างเหมาะสม แทนที่จะจัดการเหตุการณ์ตัวชี้ดิบ AwaitPointerEventScope มีเมธอดสำหรับการฟังเหตุการณ์ต่อไปนี้

ตัวตรวจจับเหล่านี้เป็นตัวตรวจจับระดับบนสุด คุณจึงไม่สามารถเพิ่มตัวตรวจจับหลายรายการภายในตัวแก้ไข pointerInput รายการเดียวได้ ข้อมูลโค้ดต่อไปนี้จะตรวจจับเฉพาะการแตะ ไม่ใช่การลาก

var log by remember { mutableStateOf("") }
Column {
    Text(log)
    Box(
        Modifier
            .size(100.dp)
            .background(Color.Red)
            .pointerInput(Unit) {
                detectTapGestures { log = "Tap!" }
                // Never reached
                detectDragGestures { _, _ -> log = "Dragging" }
            }
    )
}

ภายใน เมธอด detectTapGestures จะบล็อกโครูทีน และระบบจะไม่เข้าถึงตัวตรวจจับที่ 2 หากต้องการเพิ่ม Listener ท่าทางสัมผัสมากกว่า 1 รายการลงใน Composable ให้ใช้อินสแตนซ์ตัวแก้ไข pointerInput แยกกันแทน ดังนี้

var log by remember { mutableStateOf("") }
Column {
    Text(log)
    Box(
        Modifier
            .size(100.dp)
            .background(Color.Red)
            .pointerInput(Unit) {
                detectTapGestures { log = "Tap!" }
            }
            .pointerInput(Unit) {
                // These drag events will correctly be triggered
                detectDragGestures { _, _ -> log = "Dragging" }
            }
    )
}

จัดการเหตุการณ์ต่อท่าทางสัมผัส

ตามคำจำกัดความ ท่าทางสัมผัสจะเริ่มต้นด้วยเหตุการณ์การกดตัวชี้ลง คุณสามารถใช้เมธอดตัวช่วย awaitEachGesture แทนลูป while(true) ที่ส่งผ่านเหตุการณ์ข้อมูลดิบแต่ละรายการได้ เมธอด awaitEachGesture จะรีสตาร์ทบล็อกที่มีอยู่เมื่อยกเคอร์เซอร์ทั้งหมดขึ้น ซึ่งบ่งชี้ว่าท่าทางสัมผัสเสร็จสมบูรณ์แล้ว

@Composable
private fun SimpleClickable(onClick: () -> Unit) {
    Box(
        Modifier
            .size(100.dp)
            .pointerInput(onClick) {
                awaitEachGesture {
                    awaitFirstDown().also { it.consume() }
                    val up = waitForUpOrCancellation()
                    if (up != null) {
                        up.consume()
                        onClick()
                    }
                }
            }
    )
}

ในทางปฏิบัติ คุณมักจะต้องการใช้ awaitEachGesture เสมอ เว้นแต่คุณจะตอบสนองต่อเหตุการณ์เคอร์เซอร์โดยไม่ระบุท่าทางสัมผัส ตัวอย่างเช่น hoverable ซึ่งไม่ตอบสนองต่อเหตุการณ์การกดหรือปล่อยตัวชี้ แต่ต้องการทราบเพียงว่าตัวชี้เข้าหรือออกจากขอบเขตเมื่อใด

รอเหตุการณ์หรือท่าทางสัมผัสย่อยที่เฉพาะเจาะจง

มีชุดเมธอดที่ช่วยระบุส่วนที่เหมือนกันของท่าทางสัมผัส ดังนี้

  • ระงับจนกว่าตัวชี้จะเลื่อนลงด้วย awaitFirstDown หรือรอให้ตัวชี้ทั้งหมดเลื่อนขึ้นด้วย waitForUpOrCancellation
  • สร้าง Listener การลากระดับต่ำโดยใช้ awaitTouchSlopOrCancellation และ awaitDragOrCancellation ตัวแฮนเดิลท่าทางสัมผัสจะระงับก่อนจนกว่าตัวชี้จะถึงระยะการแตะที่อนุญาต จากนั้นจะระงับจนกว่าเหตุการณ์การลากครั้งแรกจะเกิดขึ้น หากคุณสนใจเฉพาะการลากตามแกนเดียว ให้ใช้ awaitHorizontalTouchSlopOrCancellation บวก awaitHorizontalDragOrCancellation หรือ awaitVerticalTouchSlopOrCancellation บวก awaitVerticalDragOrCancellation แทน
  • ระงับจนกว่าจะมีการกดค้างด้วย awaitLongPressOrCancellation
  • ใช้เมธอด drag เพื่อฟังเหตุการณ์การลากอย่างต่อเนื่อง หรือ horizontalDrag หรือ verticalDrag เพื่อฟังเหตุการณ์การลากในแกนเดียว

ใช้การคำนวณสำหรับเหตุการณ์แบบมัลติทัช

เมื่อผู้ใช้ใช้ท่าทางสัมผัสแบบมัลติทัชโดยใช้ตัวชี้มากกว่า 1 ตัว การทำความเข้าใจการเปลี่ยนรูปแบบที่จำเป็นตามค่าดิบนั้นเป็นเรื่องที่ซับซ้อน หากตัวแก้ไข transformable หรือเมธอด detectTransformGestures ไม่ได้ให้การควบคุมที่ละเอียดเพียงพอสำหรับกรณีการใช้งานของคุณ คุณสามารถฟังเหตุการณ์ดิบและใช้การคำนวณกับเหตุการณ์เหล่านั้นได้ เมธอดตัวช่วยเหล่านี้ได้แก่ calculateCentroid, calculateCentroidSize, calculatePan, calculateRotation และ calculateZoom

การส่งเหตุการณ์และการทดสอบตัวชี้

ระบบจะไม่ส่งเหตุการณ์เคอร์เซอร์ทุกรายการไปยังตัวแก้ไข pointerInput ทุกตัว การส่งเหตุการณ์จะทำงานดังนี้

  • ระบบจะส่งเหตุการณ์ตัวชี้ไปยังลำดับชั้นที่ประกอบกันได้ ทันทีที่ตัวชี้ใหม่ทริกเกอร์เหตุการณ์ตัวชี้แรก ระบบจะเริ่มการทดสอบตัวชี้กับ Composable ที่ "มีสิทธิ์" Composable จะถือว่ามีสิทธิ์เมื่อมีความสามารถในการจัดการอินพุตตัวชี้ การทดสอบตัวชี้จะเริ่มจากด้านบนของโครงสร้าง UI ไปยังด้านล่าง ระบบจะ "Hit" Composable เมื่อเหตุการณ์เคอร์เซอร์เกิดขึ้นภายในขอบเขตของ Composable นั้น กระบวนการนี้ส่งผลให้เกิดเชนของ Composable ที่ผ่านการทดสอบตัวชี้
  • โดยค่าเริ่มต้น เมื่อมี Composable ที่มีสิทธิ์หลายรายการในระดับเดียวกันของต้นไม้ ระบบจะ "Hit" เฉพาะ Composable ที่มีดัชนีลำดับ Z สูงสุดเท่านั้น ตัวอย่างเช่น เมื่อคุณเพิ่ม Button composable ที่ซ้อนทับกัน 2 รายการลงใน Box จะมีเพียงรายการที่วาดไว้ด้านบนเท่านั้นที่ได้รับเหตุการณ์ตัวชี้ ในทางทฤษฎี คุณสามารถลบล้างลักษณะการทำงานนี้ได้โดยสร้างการติดตั้งใช้งาน PointerInputModifierNode ของคุณเองและตั้งค่า sharePointerInputWithSiblings เป็น "จริง"
  • ระบบจะส่งเหตุการณ์เพิ่มเติมสำหรับตัวชี้เดียวกันไปยังเชนของ Composable เดียวกัน และจะไหลตามตรรกะการเผยแพร่เหตุการณ์ ระบบจะไม่ทำการทดสอบตัวชี้สำหรับตัวชี้นี้อีกต่อไป ซึ่งหมายความว่า Composable แต่ละรายการในเชนจะได้รับเหตุการณ์ทั้งหมดสำหรับตัวชี้ดังกล่าว แม้ว่าเหตุการณ์เหล่านั้นจะเกิดขึ้นนอกขอบเขตของ Composable นั้นก็ตาม คอมโพสิเบิลที่ไม่ได้อยู่ในเชนจะไม่ได้รับเหตุการณ์เคอร์เซอร์ แม้ว่าเคอร์เซอร์จะอยู่ภายในขอบเขตก็ตาม

เหตุการณ์การวางเมาส์เหนือองค์ประกอบ ซึ่งทริกเกอร์โดยการวางเมาส์หรือสไตลัสเหนือองค์ประกอบ เป็นข้อยกเว้นของกฎที่กำหนดไว้ที่นี่ ระบบจะส่งเหตุการณ์การวางเมาส์เหนือไปยัง Composable ที่เหตุการณ์นั้นโดน ดังนั้นเมื่อผู้ใช้วางเคอร์เซอร์เหนือขอบเขตของ Composable หนึ่งไปยังอีก Composable หนึ่ง ระบบจะส่งเหตุการณ์ไปยัง Composable ใหม่แทนที่จะส่งเหตุการณ์ไปยัง Composable แรก

การใช้งานตามเหตุการณ์

เมื่อ Composable มากกว่า 1 รายการมีเครื่องจัดการท่าทางสัมผัสที่กำหนดไว้ เครื่องจัดการเหล่านั้นไม่ควรขัดแย้งกัน ตัวอย่างเช่น ลองดู UI ต่อไปนี้

รายการที่มีรูปภาพ คอลัมน์ที่มีข้อความ 2 รายการ และปุ่ม

เมื่อผู้ใช้แตะปุ่มบุ๊กมาร์ก แลมบ์ดา onClick ของปุ่มจะจัดการท่าทางสัมผัสนั้น เมื่อผู้ใช้แตะส่วนอื่นๆ ของรายการในลิสต์ ListItem จะจัดการท่าทางสัมผัสนั้นและไปยังบทความ ในแง่ของอินพุตตัวชี้ ปุ่มต้องใช้เหตุการณ์นี้ เพื่อให้องค์ประกอบหลักทราบว่าไม่ต้องตอบสนองต่อเหตุการณ์นั้นอีก ท่าทางสัมผัสที่รวมอยู่ในคอมโพเนนต์ที่พร้อมใช้งานทันทีและตัวปรับแต่งท่าทางสัมผัสทั่วไปจะรวมลักษณะการใช้งานนี้ไว้ด้วย แต่หากคุณเขียนท่าทางสัมผัสที่กำหนดเอง คุณต้องใช้เหตุการณ์ด้วยตนเอง โดยใช้เมธอด PointerInputChange.consume ดังนี้

Modifier.pointerInput(Unit) {

    awaitEachGesture {
        while (true) {
            val event = awaitPointerEvent()
            // consume all changes
            event.changes.forEach { it.consume() }
        }
    }
}

การใช้เหตุการณ์ไม่ได้หยุดการเผยแพร่เหตุการณ์ไปยัง Composable อื่นๆ แต่ Composable ต้องละเว้นเหตุการณ์ที่ใช้ไปแล้วอย่างชัดเจนแทน เมื่อเขียนท่าทางสัมผัสที่กำหนดเอง คุณควรตรวจสอบว่าองค์ประกอบอื่นใช้เหตุการณ์ไปแล้วหรือไม่ โดยทำดังนี้

Modifier.pointerInput(Unit) {
    awaitEachGesture {
        while (true) {
            val event = awaitPointerEvent()
            if (event.changes.any { it.isConsumed }) {
                // A pointer is consumed by another gesture handler
            } else {
                // Handle unconsumed event
            }
        }
    }
}

การเผยแพร่เหตุการณ์

ดังที่ได้กล่าวไปก่อนหน้านี้ ระบบจะส่งการเปลี่ยนแปลงตัวชี้ไปยัง Composable แต่ละรายการที่ตัวชี้ไปโดน แต่หากมี Composable ดังกล่าวมากกว่า 1 รายการ เหตุการณ์จะเผยแพร่ตามลำดับใด หากคุณดูตัวอย่างจากส่วนที่แล้ว UI นี้จะแปลเป็นแผนผัง UI ต่อไปนี้ ซึ่งมีเพียง ListItem และ Button เท่านั้นที่ตอบสนองต่อเหตุการณ์เคอร์เซอร์

โครงสร้างแบบต้นไม้ เลเยอร์บนสุดคือ ListItem เลเยอร์ที่ 2 มี Image, Column และ Button และ Column จะแยกออกเป็น Text 2 รายการ ListItem และ Button จะไฮไลต์อยู่

เหตุการณ์เคอร์เซอร์จะไหลผ่าน Composable แต่ละรายการเหล่านี้ 3 ครั้งในระหว่าง "การส่งผ่าน" 3 ครั้ง ดังนี้

  • ในการส่งผ่านครั้งแรก เหตุการณ์จะไหลจากด้านบนของแผนผัง UI ไปยังด้านล่าง โฟลว์นี้ช่วยให้ผู้ปกครองสกัดกั้นเหตุการณ์ก่อนที่บุตรหลานจะใช้เหตุการณ์นั้นได้ ตัวอย่างเช่น เคล็ดลับเครื่องมือต้องขัดจังหวะการกดค้างแทนที่จะส่งต่อไปยังองค์ประกอบย่อย ในตัวอย่างของเรา ListItem จะได้รับเหตุการณ์ก่อน Button
  • ในรอบหลัก เหตุการณ์จะไหลจากโหนด Leaf ของแผนผัง UI ไปยังรูทของแผนผัง UI ระยะนี้เป็นระยะที่คุณใช้ท่าทางสัมผัสตามปกติ และเป็นระยะเริ่มต้นเมื่อฟังเหตุการณ์ การจัดการท่าทางสัมผัสในขั้นตอนนี้หมายความว่าโหนดปลายสุดจะมีลำดับความสำคัญเหนือโหนดหลัก ซึ่งเป็นลักษณะการทำงานที่สมเหตุสมผลที่สุดสำหรับท่าทางสัมผัสส่วนใหญ่ ในตัวอย่างของเรา Button จะได้รับเหตุการณ์ก่อน ListItem
  • ในรอบสุดท้าย เหตุการณ์จะไหลอีกครั้งจากด้านบนของแผนผัง UI ไปยังโหนดปลายสุด โฟลว์นี้ช่วยให้องค์ประกอบที่อยู่สูงกว่าในสแต็กตอบสนองต่อการใช้เหตุการณ์โดยองค์ประกอบหลักได้ ตัวอย่างเช่น ปุ่มจะนำการแสดงผลริปเปิลออกเมื่อการกดเปลี่ยนเป็นการลากองค์ประกอบหลักที่เลื่อนได้

ภาพแสดงขั้นตอนของเหตุการณ์มีลักษณะดังนี้

เมื่อใช้การเปลี่ยนแปลงอินพุตแล้ว ระบบจะส่งข้อมูลนี้จากจุดนั้นในขั้นตอนต่อไปนี้

ในโค้ด คุณสามารถระบุบัตรที่สนใจได้ดังนี้

Modifier.pointerInput(Unit) {
    awaitPointerEventScope {
        val eventOnInitialPass = awaitPointerEvent(PointerEventPass.Initial)
        val eventOnMainPass = awaitPointerEvent(PointerEventPass.Main) // default
        val eventOnFinalPass = awaitPointerEvent(PointerEventPass.Final)
    }
}

ในข้อมูลโค้ดนี้ การเรียกใช้เมธอด await แต่ละรายการจะแสดงเหตุการณ์เดียวกัน แม้ว่าข้อมูลเกี่ยวกับการใช้จะมีการเปลี่ยนแปลงก็ตาม

ทดสอบท่าทางสัมผัส

ในเมธอดการทดสอบ คุณสามารถส่งเหตุการณ์ตัวชี้ด้วยตนเองได้โดยใช้เมธอด performTouchInput ซึ่งช่วยให้คุณใช้ท่าทางสัมผัสแบบเต็มระดับสูง (เช่น บีบหรือคลิกค้าง) หรือท่าทางสัมผัสระดับต่ำ (เช่น เลื่อนเคอร์เซอร์ตามจำนวนพิกเซลที่กำหนด) ได้ ดังนี้

composeTestRule.onNodeWithTag("MyList").performTouchInput {
    swipeUp()
    swipeDown()
    click()
}

ดูตัวอย่างเพิ่มเติมในเอกสารประกอบของ performTouchInput

ดูข้อมูลเพิ่มเติม

ดูข้อมูลเพิ่มเติมเกี่ยวกับท่าทางสัมผัสใน Jetpack Compose ได้จากแหล่งข้อมูลต่อไปนี้