ตัวอย่างภาพเคลื่อนไหวขั้นสูง: ท่าทางสัมผัส
จัดทุกอย่างให้เป็นระเบียบอยู่เสมอด้วยคอลเล็กชัน
บันทึกและจัดหมวดหมู่เนื้อหาตามค่ากำหนดของคุณ
เราต้องพิจารณาหลายอย่างเมื่อทำงานกับเหตุการณ์การแตะและภาพเคลื่อนไหว
เมื่อเทียบกับตอนที่ทำงานกับภาพเคลื่อนไหวเพียงอย่างเดียว ก่อนอื่น เราอาจต้องหยุดภาพเคลื่อนไหวที่กำลังทำงานอยู่
เมื่อเหตุการณ์การแตะเริ่มต้น เนื่องจากปฏิสัมพันธ์ของผู้ใช้ควรมีความสำคัญสูงสุด
ในตัวอย่างด้านล่าง เราใช้ Animatable
เพื่อแสดงตำแหน่งออฟเซ็ตของ
คอมโพเนนต์วงกลม เหตุการณ์การแตะจะได้รับการประมวลผลด้วยตัวแก้ไข
pointerInput
เมื่อตรวจพบเหตุการณ์แตะใหม่ เราจะเรียกใช้ animateTo
เพื่อเคลื่อนไหวค่าออฟเซ็ตไปยังตำแหน่งที่แตะ เหตุการณ์การแตะอาจเกิดขึ้นระหว่างภาพเคลื่อนไหวด้วย และในกรณีนั้น animateTo
จะขัดจังหวะภาพเคลื่อนไหวที่กำลังดำเนินอยู่และเริ่ม
ภาพเคลื่อนไหวไปยังตำแหน่งเป้าหมายใหม่พร้อมทั้งรักษาความเร็วของ
ภาพเคลื่อนไหวที่ถูกขัดจังหวะ
@Composable
fun Gesture() {
val offset = remember { Animatable(Offset(0f, 0f), Offset.VectorConverter) }
Box(
modifier = Modifier
.fillMaxSize()
.pointerInput(Unit) {
coroutineScope {
while (true) {
// Detect a tap event and obtain its position.
awaitPointerEventScope {
val position = awaitFirstDown().position
launch {
// Animate to the tap position.
offset.animateTo(position)
}
}
}
}
}
) {
Circle(modifier = Modifier.offset { offset.value.toIntOffset() })
}
}
private fun Offset.toIntOffset() = IntOffset(x.roundToInt(), y.roundToInt())
อีกรูปแบบที่พบบ่อยคือเราต้องซิงค์ค่าภาพเคลื่อนไหวกับค่าที่มาจากเหตุการณ์การแตะ เช่น การลาก ในตัวอย่างด้านล่าง เราจะเห็นว่า "ปัดเพื่อ
ปิด" มีการใช้งานเป็น Modifier
(แทนที่จะใช้
SwipeToDismiss
ที่ประกอบได้) ออฟเซ็ตแนวนอนขององค์ประกอบจะแสดงเป็น
Animatable
API นี้มีลักษณะที่ใช้ประโยชน์ในการเคลื่อนไหวท่าทางได้ โดย
ค่าของแอตทริบิวต์นี้สามารถเปลี่ยนแปลงได้ด้วยเหตุการณ์การแตะและภาพเคลื่อนไหว เมื่อได้รับเหตุการณ์
การแตะลง เราจะหยุด Animatable
ด้วยวิธี stop
เพื่อให้
ภาพเคลื่อนไหวที่กำลังดำเนินการอยู่ถูกขัดจังหวะ
ในระหว่างเหตุการณ์การลาก เราจะใช้ snapTo
เพื่ออัปเดตค่า Animatable
ด้วยค่าที่คำนวณจากเหตุการณ์การแตะ สำหรับ Fling, Compose จะมี VelocityTracker
เพื่อบันทึกเหตุการณ์การลากและคำนวณความเร็ว ความเร็วสามารถ
ป้อนไปยัง animateDecay
โดยตรงสำหรับภาพเคลื่อนไหวการปัด เมื่อต้องการเลื่อนค่าออฟเซ็ตกลับไปยังตำแหน่งเดิม เราจะระบุค่าออฟเซ็ตเป้าหมายเป็น 0f
ด้วยเมธอด animateTo
fun Modifier.swipeToDismiss(
onDismissed: () -> Unit
): Modifier = composed {
val offsetX = remember { Animatable(0f) }
pointerInput(Unit) {
// Used to calculate fling decay.
val decay = splineBasedDecay<Float>(this)
// Use suspend functions for touch events and the Animatable.
coroutineScope {
while (true) {
val velocityTracker = VelocityTracker()
// Stop any ongoing animation.
offsetX.stop()
awaitPointerEventScope {
// Detect a touch down event.
val pointerId = awaitFirstDown().id
horizontalDrag(pointerId) { change ->
// Update the animation value with touch events.
launch {
offsetX.snapTo(
offsetX.value + change.positionChange().x
)
}
velocityTracker.addPosition(
change.uptimeMillis,
change.position
)
}
}
// No longer receiving touch events. Prepare the animation.
val velocity = velocityTracker.calculateVelocity().x
val targetOffsetX = decay.calculateTargetValue(
offsetX.value,
velocity
)
// The animation stops when it reaches the bounds.
offsetX.updateBounds(
lowerBound = -size.width.toFloat(),
upperBound = size.width.toFloat()
)
launch {
if (targetOffsetX.absoluteValue <= size.width) {
// Not enough velocity; Slide back.
offsetX.animateTo(
targetValue = 0f,
initialVelocity = velocity
)
} else {
// The element was swiped away.
offsetX.animateDecay(velocity, decay)
onDismissed()
}
}
}
}
}
.offset { IntOffset(offsetX.value.roundToInt(), 0) }
}
แนะนำสำหรับคุณ
ตัวอย่างเนื้อหาและโค้ดในหน้าเว็บนี้ขึ้นอยู่กับใบอนุญาตที่อธิบายไว้ในใบอนุญาตการใช้เนื้อหา Java และ OpenJDK เป็นเครื่องหมายการค้าหรือเครื่องหมายการค้าจดทะเบียนของ Oracle และ/หรือบริษัทในเครือ
อัปเดตล่าสุด 2025-08-28 UTC
[[["เข้าใจง่าย","easyToUnderstand","thumb-up"],["แก้ปัญหาของฉันได้","solvedMyProblem","thumb-up"],["อื่นๆ","otherUp","thumb-up"]],[["ไม่มีข้อมูลที่ฉันต้องการ","missingTheInformationINeed","thumb-down"],["ซับซ้อนเกินไป/มีหลายขั้นตอนมากเกินไป","tooComplicatedTooManySteps","thumb-down"],["ล้าสมัย","outOfDate","thumb-down"],["ปัญหาเกี่ยวกับการแปล","translationIssue","thumb-down"],["ตัวอย่าง/ปัญหาเกี่ยวกับโค้ด","samplesCodeIssue","thumb-down"],["อื่นๆ","otherDown","thumb-down"]],["อัปเดตล่าสุด 2025-08-28 UTC"],[],[],null,["There are several things we have to take into consideration when we are working\nwith touch events and animations, compared to when we are working with\nanimations alone. First of all, we might need to interrupt an ongoing animation\nwhen touch events begin as user interaction should have the highest priority.\n\nIn the example below, we use an `Animatable` to represent the offset position of\na circle component. Touch events are processed with the\n[`pointerInput`](/reference/kotlin/androidx/compose/ui/input/pointer/package-summary#(androidx.compose.ui.Modifier).pointerInput(kotlin.Any,%20kotlin.coroutines.SuspendFunction1))\nmodifier. When we detect a new tap event, we call `animateTo` to animate the\noffset value to the tap position. A tap event can happen during the animation\ntoo, and in that case, `animateTo` interrupts the ongoing animation and starts\nthe animation to the new target position while maintaining the velocity of the\ninterrupted animation.\n\n\n```kotlin\n@Composable\nfun Gesture() {\n val offset = remember { Animatable(Offset(0f, 0f), Offset.VectorConverter) }\n Box(\n modifier = Modifier\n .fillMaxSize()\n .pointerInput(Unit) {\n coroutineScope {\n while (true) {\n // Detect a tap event and obtain its position.\n awaitPointerEventScope {\n val position = awaitFirstDown().position\n\n launch {\n // Animate to the tap position.\n offset.animateTo(position)\n }\n }\n }\n }\n }\n ) {\n Circle(modifier = Modifier.offset { offset.value.toIntOffset() })\n }\n}\n\nprivate fun Offset.toIntOffset() = IntOffset(x.roundToInt(), y.roundToInt())https://github.com/android/snippets/blob/7a0ebbee11495f628cf9d574f6b6069c2867232a/compose/snippets/src/main/java/com/example/compose/snippets/animations/AdvancedAnimationSnippets.kt#L62-L88\n```\n\n\u003cbr /\u003e\n\nAnother frequent pattern is we need to synchronize animation values with values\ncoming from touch events, such as drag. In the example below, we see \"swipe to\ndismiss\" implemented as a `Modifier` (rather than using the\n[`SwipeToDismiss`](/reference/kotlin/androidx/compose/material/package-summary#SwipeToDismiss(androidx.compose.material.DismissState,%20androidx.compose.ui.Modifier,%20kotlin.collections.Set,%20kotlin.Function1,%20kotlin.Function1,%20kotlin.Function1))\ncomposable). The horizontal offset of the element is represented as an\n`Animatable`. This API has a characteristic useful in gesture animation. Its\nvalue can be changed by touch events as well as the animation. When we receive a\ntouch down event, we stop the `Animatable` by the `stop` method so that any\nongoing animation is intercepted.\n\nDuring a drag event, we use `snapTo` to update the `Animatable` value with the\nvalue calculated from touch events. For fling, Compose provides\n`VelocityTracker` to record drag events and calculate velocity. The velocity can\nbe fed directly to `animateDecay` for the fling animation. When we want to slide\nthe offset value back to the original position, we specify the target offset\nvalue of `0f` with the `animateTo` method.\n\n\n```kotlin\nfun Modifier.swipeToDismiss(\n onDismissed: () -\u003e Unit\n): Modifier = composed {\n val offsetX = remember { Animatable(0f) }\n pointerInput(Unit) {\n // Used to calculate fling decay.\n val decay = splineBasedDecay\u003cFloat\u003e(this)\n // Use suspend functions for touch events and the Animatable.\n coroutineScope {\n while (true) {\n val velocityTracker = VelocityTracker()\n // Stop any ongoing animation.\n offsetX.stop()\n awaitPointerEventScope {\n // Detect a touch down event.\n val pointerId = awaitFirstDown().id\n\n horizontalDrag(pointerId) { change -\u003e\n // Update the animation value with touch events.\n launch {\n offsetX.snapTo(\n offsetX.value + change.positionChange().x\n )\n }\n velocityTracker.addPosition(\n change.uptimeMillis,\n change.position\n )\n }\n }\n // No longer receiving touch events. Prepare the animation.\n val velocity = velocityTracker.calculateVelocity().x\n val targetOffsetX = decay.calculateTargetValue(\n offsetX.value,\n velocity\n )\n // The animation stops when it reaches the bounds.\n offsetX.updateBounds(\n lowerBound = -size.width.toFloat(),\n upperBound = size.width.toFloat()\n )\n launch {\n if (targetOffsetX.absoluteValue \u003c= size.width) {\n // Not enough velocity; Slide back.\n offsetX.animateTo(\n targetValue = 0f,\n initialVelocity = velocity\n )\n } else {\n // The element was swiped away.\n offsetX.animateDecay(velocity, decay)\n onDismissed()\n }\n }\n }\n }\n }\n .offset { IntOffset(offsetX.value.roundToInt(), 0) }\n}https://github.com/android/snippets/blob/7a0ebbee11495f628cf9d574f6b6069c2867232a/compose/snippets/src/main/java/com/example/compose/snippets/animations/AdvancedAnimationSnippets.kt#L94-L152\n```\n\n\u003cbr /\u003e\n\nRecommended for you\n\n- Note: link text is displayed when JavaScript is off\n- [Value-based animations](/develop/ui/compose/animation/value-based)\n- [Drag, swipe, and fling](/develop/ui/compose/touch-input/pointer-input/drag-swipe-fling)\n- [Understand gestures](/develop/ui/compose/touch-input/pointer-input/understand-gestures)"]]