แอนิเมชันท่าทางและสปริง
การเคลื่อนไหวตามธรรมชาติที่อิงหลักฟิสิกส์
แอนิเมชันท่าทางและสปริง เป็นบทเรียน Android Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Android Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Android Academy มีบทเรียนทั้งหมด 4 บทเรียน
การเคลื่อนไหวแบบโต้ตอบและมีแรงทางกายภาพ
แอนิเมชันที่น่าประทับใจที่สุดคือแอนิเมชันที่ตอบสนองต่อนิ้วของผู้ใช้และให้ความรู้สึกเหมือนวัตถุจริง มีแรงเฉื่อยและการเด้ง การสร้างแอนิเมชันเช่นนี้ต้องผสาน ท่าทางเข้ากับแอนิเมชันที่อิงหลักฟิสิกส์
บทเรียนนี้ครอบคลุม Animatable สปริง การเสื่อมของการเหวี่ยง และการเชื่อมสิ่งเหล่านี้เข้ากับท่าทางลาก เพื่อสร้างส่วนติดต่อผู้ใช้ที่เป็นธรรมชาติและโต้ตอบได้
Animatable: การควบคุมระดับล่าง
ขณะที่ animate*AsState ตอบสนองต่อสถานะ Animatable ให้คุณควบคุมแบบ命令 คุณเก็บอินสแตนซ์หนึ่งรายการไว้ใน remember แล้วควบคุมจากโครูทีนด้วยการเรียกใช้ เช่น animateTo หรือ snapTo
นี่คือพื้นฐานของการเคลื่อนไหวที่ขับเคลื่อนด้วยท่าทาง
@Composable
fun BounceOnClick() {
val scale = remember { Animatable(1f) }
val scope = rememberCoroutineScope()
Box(
Modifier
.graphicsLayer { scaleX = scale.value; scaleY = scale.value }
.clickable {
scope.launch {
scale.animateTo(1.3f, spring())
scale.animateTo(1f, spring())
}
}
)
}สปริง: dampingRatio และ stiffness
ข้อกำหนดของ spring กำหนดด้วยค่าสองค่า:
- dampingRatio - ความเด้ง ค่าต่ำจะเด้งมากกว่า ส่วน
1f(หน่วงวิกฤต) จะไม่เลยค่าปลายทาง - stiffness - ความเร็ว ค่าสูงจะไปถึงเป้าหมายเร็วขึ้น
Compose มีค่าคงที่ เช่น Spring.DampingRatioMediumBouncy และ Spring.StiffnessLow ให้ใช้ คุณจึงแทบไม่ต้องระบุตัวเลขดิบ
val spec = spring<Float>(
dampingRatio = Spring.DampingRatioLowBouncy,
stiffness = Spring.StiffnessMedium
)
// later, inside a coroutine:
// offset.animateTo(targetValue = 0f, animationSpec = spec)การตรวจจับท่าทางลาก
หากต้องการทำให้การเคลื่อนไหวโต้ตอบได้ คุณต้องอ่านข้อมูลสัมผัส Modifier.pointerInput ร่วมกับ detectDragGestures จะรายงานระยะการลากขณะที่ผู้ใช้เลื่อนนิ้ว
คุณจะสะสมระยะเหล่านั้นเป็นค่าออฟเซ็ต แล้วนำไปใช้กับคอมโพสิเบิล
@Composable
fun Draggable() {
var offsetX by remember { mutableStateOf(0f) }
Box(
Modifier
.offset { IntOffset(offsetX.roundToInt(), 0) }
.size(80.dp)
.background(Color.Red)
.pointerInput(Unit) {
detectDragGestures { change, drag ->
change.consume()
offsetX += drag.x
}
}
)
}การดีดกลับด้วยสปริง
รวมการลากเข้ากับ Animatable เพื่อสร้างเอฟเฟกต์ "ดีดกลับ" ระหว่างลาก ให้ใช้ snapTo เพื่อย้ายไปยังตำแหน่งนิ้วทันที เมื่อปล่อยนิ้ว ให้ใช้ animateTo(0f) พร้อมสปริง เพื่อให้องค์ประกอบเด้งกลับไปยังตำแหน่งเดิม
@Composable
fun SnapBack() {
val offsetX = remember { Animatable(0f) }
val scope = rememberCoroutineScope()
Box(
Modifier
.offset { IntOffset(offsetX.value.roundToInt(), 0) }
.size(80.dp)
.background(Color.Blue)
.pointerInput(Unit) {
detectDragGestures(
onDrag = { change, drag ->
change.consume()
scope.launch { offsetX.snapTo(offsetX.value + drag.x) }
},
onDragEnd = {
scope.launch { offsetX.animateTo(0f, spring()) }
}
)
}
)
}การเหวี่ยงด้วยความเร็ว
การเหวี่ยงจริงจะยังเคลื่อนที่ต่อหลังจากยกนิ้วออก ติดตามความเร็วด้วย VelocityTracker จากนั้นส่งค่าให้ animateDecay เพื่อให้องค์ประกอบค่อย ๆ เคลื่อนที่จนหยุดอย่างเป็นธรรมชาติ โดยใช้ DecayAnimationSpec ที่อิงแรงเสียดทาน
val decay = rememberSplineBasedDecay<Float>()
// on drag end, with measured velocity:
// scope.launch {
// offsetX.animateDecay(
// initialVelocity = velocity,
// animationSpec = decay
// )
// }การปัดเพื่อปิดด้วยจุดยึด
สำหรับท่าทางปัดที่มีตำแหน่งพักตายตัว เช่น การปัดเพื่อปิด anchoredDraggable จะดีดไปยังจุดยึดที่กำหนดไว้ คุณประกาศสถานะและตำแหน่งเป็นพิกเซล แล้ว Compose จะจัดการการเคลื่อนด้วยสปริงระหว่างตำแหน่งเหล่านั้นให้
enum class DragValue { Start, End }
val state = remember {
AnchoredDraggableState(
initialValue = DragValue.Start,
anchors = DraggableAnchors {
DragValue.Start at 0f
DragValue.End at 300f
}
)
}แอนิเมชันแบบไม่สิ้นสุดและแบบทำซ้ำ
สำหรับการเคลื่อนไหวต่อเนื่อง เช่น จุดที่เต้นเป็นจังหวะหรือเอฟเฟกต์แสงระยิบขณะกำลังโหลด ให้ใช้ rememberInfiniteTransition ร่วมกับ infiniteRepeatable ตั้งค่า RepeatMode เป็น Reverse เพื่อให้เคลื่อนกลับไปกลับมา หรือเป็น Restart เพื่อเริ่มวนซ้ำใหม่
@Composable
fun Pulse() {
val transition = rememberInfiniteTransition(label = "pulse")
val scale by transition.animateFloat(
initialValue = 1f,
targetValue = 1.2f,
animationSpec = infiniteRepeatable(
animation = tween(700),
repeatMode = RepeatMode.Reverse
),
label = "scale"
)
Box(Modifier.graphicsLayer { scaleX = scale; scaleY = scale })
}การผสานท่าทางกับสปริง
รูปแบบการโต้ตอบที่เป็นเอกลักษณ์คือ ลากเพื่อเคลื่อนที่ แล้วปล่อยเพื่อให้เข้าที่ ติดตามความเร็วระหว่างการลาก จากนั้นเลือกว่าจะเหวี่ยงด้วย animateDecay หรือเคลื่อนไปยังจุดยึดด้วย spring โดยพิจารณาจากระยะทางหรือความเร็ว
นี่คือเหตุผลที่แผงด้านล่าง แถบเลื่อนเนื้อหา และการ์ดที่ปัดปิดได้ จึงให้ความรู้สึกเป็นธรรมชาติมาก
// pattern:
// 1) onDrag -> animatable.snapTo(current + delta)
// 2) onDragEnd -> if (fast) animatable.animateDecay(velocity, decay)
// else animatable.animateTo(nearestAnchor, spring())การเข้าที่ด้วยสปริงใน Kotlin ล้วน
หากต้องการทำความเข้าใจว่าสปริงเข้าที่อย่างไร ต่อไปนี้คือลูป Kotlin ล้วนแบบสั้น ๆ ลูปนี้จะใช้แรงดึงกลับเข้าหาเป้าหมายและลดความเร็วลงในแต่ละขั้น ซึ่งเป็นแนวคิดเดียวกับที่ Compose ทำในแต่ละเฟรม
fun main() {
var pos = 0f
var vel = 0f
val target = 200f
val stiffness = 0.2f
val damping = 0.75f
repeat(8) { step ->
vel = (vel + (target - pos) * stiffness) * damping
pos += vel
println("step $step pos=${"%.1f".format(pos)}")
}
}การแตะ การกด และท่าทางหลายรูปแบบ
การลากไม่ใช่ท่าทางเพียงอย่างเดียว pointerInput ยังมี detectTapGestures สำหรับการแตะ การแตะสองครั้ง และการกดค้าง รวมถึง detectTransformGestures สำหรับการบีบเพื่อซูมและการหมุน
ในตัวอย่างนี้ การกดค้างจะเรียกใช้การปรับขนาดด้วยสปริง โดยผสานการตรวจจับท่าทางเข้ากับ Animatable
@Composable
fun LongPressScale() {
val scale = remember { Animatable(1f) }
val scope = rememberCoroutineScope()
Box(
Modifier
.graphicsLayer { scaleX = scale.value; scaleY = scale.value }
.pointerInput(Unit) {
detectTapGestures(
onLongPress = {
scope.launch { scale.animateTo(1.4f, spring()) }
},
onPress = {
tryAwaitRelease()
scope.launch { scale.animateTo(1f, spring()) }
}
)
}
)
}ตรวจสอบความเข้าใจ
คุณกำลังสร้างการ์ดที่ลากได้ และต้องการให้การ์ดเด้งกลับไปยังจุดเริ่มต้นเมื่อปล่อยนิ้ว คุณควรใช้ชุดใดตามแนวทางมาตรฐานของ Compose
สรุป: ท่าทางและสปริง
ตอนนี้คุณสามารถสร้างการเคลื่อนไหวที่เป็นธรรมชาติและโต้ตอบได้แล้ว:
Animatableช่วยควบคุมแบบสั่งการโดยใช้คอร์รูทีน ผ่านanimateTo/snapTospringปรับแต่งได้ด้วยdampingRatio(การเด้ง) และstiffness(ความเร็ว)pointerInput+detectDragGesturesเปลี่ยนการสัมผัสให้เป็นการเคลื่อนไหว ส่วนanimateDecayจะเพิ่มการเหวี่ยงanchoredDraggableจะดีดไปยังตำแหน่งตายตัวสำหรับการโต้ตอบแบบปัดrememberInfiniteTransitionควบคุมเอฟเฟกต์แบบวนซ้ำ เช่น การเต้นเป็นจังหวะและแสงระยิบ
บทเรียนแอนิเมชันใน Jetpack Compose จบลงแล้ว ตอนนี้คุณสามารถทำให้ค่า การมองเห็น เนื้อหา และท่าทางเคลื่อนไหวได้อย่างมั่นใจ
คำถามที่พบบ่อย
บทเรียน “แอนิเมชันท่าทางและสปริง” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “แอนิเมชันท่าทางและสปริง” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Android Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Android Academy มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “แอนิเมชันท่าทางและสปริง”
การเคลื่อนไหวตามธรรมชาติที่อิงหลักฟิสิกส์ คุณปฏิบัติ Android Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Android Academy หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน Android Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน
บทเรียน “แอนิเมชันท่าทางและสปริง” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน Android Academy นี้ได้ไหม
ได้ บทเรียน Android Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- เหตุใดแอนิเมชันจึงสำคัญ
- พื้นฐาน animate*AsState
- AnimatedVisibility และการเปลี่ยนผ่าน
- แอนิเมชันท่าทางและสปริง