0Pricing
Android Academy · บทเรียน

แอนิเมชันท่าทางและสปริง

การเคลื่อนไหวตามธรรมชาติที่อิงหลักฟิสิกส์

แอนิเมชันท่าทางและสปริง เป็นบทเรียน 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 / snapTo
  • spring ปรับแต่งได้ด้วย 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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. เหตุใดแอนิเมชันจึงสำคัญ
  2. พื้นฐาน animate*AsState
  3. AnimatedVisibility และการเปลี่ยนผ่าน
  4. แอนิเมชันท่าทางและสปริง
← กลับไปที่ Android Academy