ประเด็นสำคัญ
Easing (ฟังก์ชันการทำให้เรียบ) คือฟังก์ชันทางคณิตศาสตร์ f(t) ที่จับคู่เวลาเชิงเส้น t ∈ [0,1] กับค่าแอนิเมชันที่ไม่เป็นเชิงเส้น หากแอนิเมชันเปลี่ยนตำแหน่งของวัตถุจาก A ไป B ใน 500 ms การ easing จะกำหนดว่าวัตถุเคลื่อนที่ระหว่างจุดเหล่านี้ในแต่ละช่วงเวลาอย่างไร Easing เชิงเส้น (f(t) = t) หมายถึงความเร็วคงที่ — วัตถุเคลื่อนที่อย่างกลไก Easing ที่ไม่เป็นเชิงเส้นให้ความรู้สึกเป็นธรรมชาติ: วัตถุเร่งความเร็ว (ease-in) ชะลอความเร็ว (ease-out) หรือมีจุดเริ่มต้นและสิ้นสุดที่ราบรื่น (ease-in-out)
ฟังก์ชัน Easing อธิบายด้วยเส้นโค้ง Bezier แบบลูกบาศก์ โดยแกน X คือเวลาที่ถูกทำให้เป็นมาตรฐาน และแกน Y คือความคืบหน้าที่ถูกทำให้เป็นมาตรฐาน จุดควบคุมสองจุด (P1, P2) กำหนดรูปร่างของเส้นโค้ง เส้นโค้งมาตรฐานถูกกำหนดโดยข้อกำหนด CSS: ease (0.25, 0.1, 0.25, 1.0), ease-in (0.42, 0.0, 1.0, 1.0), ease-out (0.0, 0.0, 0.58, 1.0), ease-in-out (0.42, 0.0, 0.58, 1.0) ค่าเดียวกันนี้ถูกใช้ใน CAMediaTimingFunction ใน iOS และ PathInterpolator ใน Android ตาม แนวทางการเคลื่อนที่ของ Material Design เส้นโค้ง easing ควรสอดคล้องกับบริบท: สำหรับการปรากฏ — ease-out (เริ่มต้นเร็ว) สำหรับการหายไป — ease-in (สิ้นสุดเร็ว)
ใน iOS การ easing ทำงานหลักผ่านคลาส CAMediaTimingFunction จาก Core Animation โดยรับเส้นโค้ง Bezier แบบลูกบาศก์เป็นจุดควบคุมสองจุด (c1x, c1y, c2x, c2y) และนำไปใช้กับ CAAnimation (CABasicAnimation, CAKeyframeAnimation) CAMediaTimingFunction รองรับ 5 ฟังก์ชันที่กำหนดไว้ล่วงหน้า: linear, easeIn, easeOut, easeInEaseOut และ default (เทียบเท่า easeInEaseOut)
CAMediaTimingFunction ทำงานที่ระดับ CALayer — แอนิเมชันทำงานในกระบวนการเซิร์ฟเวอร์เรนเดอร์แยกต่างหาก ซึ่งรับประกัน 60fps แม้กับการแปลงที่ซับซ้อน สำหรับแอนิเมชันคุณสมบัติ UIView (frame, alpha, transform) UIKit ใช้ UIView.animate พร้อมพารามิเตอร์ options: .curveEaseInOut ซึ่งภายในก็สร้าง CAMediaTimingFunction ด้วยเส้นโค้งที่เกี่ยวข้องเช่นกัน ใน iOS 17+ มีการเพิ่ม UIViewAnimationCurve.custom พร้อม BlockParameters สำหรับ Bezier ที่กำหนดเอง — ทำให้ไม่จำเป็นต้องเขียน CAAnimation ที่กำหนดเอง CAMediaTimingFunction ยังใช้ใน UIKit Dynamics และ UIScrollView สำหรับการชะลอความเร็ว (UIScrollViewDecelerationRate)
ใน SwiftUI การ easing ถูกกำหนดผ่านโครงสร้าง Animation ด้วยตัวปรับแต่ง .easeIn, .easeOut, .easeInOut, .spring และ .interpolatingSpring แตกต่างจาก UIKit ที่แอนิเมชันเป็นเชิงกระบวนการ (beginAnimations/commitAnimations) SwiftUI ใช้แนวทางแบบประกาศ: แอนิเมชันเป็นคุณสมบัติสถานะที่เปลี่ยนแปลง และเฟรมเวิร์กจะกำหนดวิธีการทำให้การเปลี่ยนภาพเป็นแอนิเมชันเอง
ตัวปรับแต่ง .timingCurve(c0x, c0y, c1x, c1y) รับจุดควบคุม Bezier แบบลูกบาศก์โดยตรง — ซึ่งเป็นอะนาล็อกที่สมบูรณ์ของ CAMediaTimingFunction SwiftUI Animation รองรับเส้นโค้งที่กำหนดไว้ล่วงหน้าเป็นคุณสมบัติแบบคงที่: Animation.easeInOut เทียบเท่ากับ ease-in-out (0.42, 0.0, 0.58, 1.0) สำหรับแอนิเมชันสปริง ใช้ Animation.spring(response:dampingFraction:blendDuration) โดยที่ dampingFraction < 1 จะสร้างเอฟเฟกต์สปริงพร้อมการหน่วง ใน WWDC 2023 Apple ได้เปิดตัว Animation.keyframes — แอนิเมชันแบบเฟรมต่อเฟรมพร้อม easing ที่กำหนดเองในแต่ละคีย์เฟรม ซึ่งให้การควบคุมวิถีการเคลื่อนที่อย่างสมบูรณ์
ใน Android การ easing ถูกนำไปใช้ผ่าน PathInterpolator (API 21+) — คลาสที่รับออบเจกต์ Path ด้วยเส้นโค้ง Bezier แบบลูกบาศก์ จุดควบคุมถูกระบุผ่าน Path.cubicTo(x1, y1, x2, y2, x3, y3) โดยที่ (x1,y1) คือจุดควบคุมแรก (x2,y2) คือจุดที่สอง และ (x3,y3) คือจุดสิ้นสุด (เป็น 1,1 เสมอ) จำเป็นต้องใช้ PathInterpolator หากต้องการเส้นโค้งที่กำหนดเอง สำหรับสถานการณ์มาตรฐาน มี AccelerateDecelerateInterpolator (ease-in-out), AccelerateInterpolator (ease-in) และ DecelerateInterpolator (ease-out)
ใน Jetpack Compose การ easing ถูกกำหนดผ่านออบเจกต์ Easing จากแพ็กเกจ androidx.compose.animation มี FastOutSlowInEasing, LinearOutSlowInEasing, FastOutLinearInEasing รวมถึง Easing ที่กำหนดเองผ่าน CubicBezierEasing Compose Easing ถูกนำไปใช้ใน animate*AsState, AnimatedVisibility และ AnimatedContent ความแตกต่างสำคัญจากระบบ View: Compose จะสอดแทรก easing ฝั่ง Kotlin (ไม่ใช่ใน RenderNode) ซึ่งให้ความยืดหยุ่นมากขึ้นสำหรับแอนิเมชันที่กำหนดเอง แต่ต้องมีการปรับแต่งบนจอภาพ 120Hz
แอนิเมชันตำแหน่งเลเยอร์พื้นฐานด้วยเส้นโค้ง ease-in-out ที่กำหนดเอง CAMediaTimingFunction รับจุดควบคุม Bezier แบบลูกบาศก์ที่กำหนดจุดเริ่มต้นและสิ้นสุดที่ราบรื่นของการเคลื่อนที่
import UIKit
func animateLayerPosition() {
let animation = CABasicAnimation(keyPath: "position")
animation.fromValue = CGPoint(x: 0, y: 0)
animation.toValue = CGPoint(x: 300, y: 400)
animation.duration = 0.8
let easing = CAMediaTimingFunction(
controlPoints: 0.42, 0.0, 0.58, 1.0
)
animation.timingFunction = easing
animation.fillMode = .forwards
animation.isRemovedOnCompletion = false
layer.add(animation, forKey: "positionAnimation")
}
จุดควบคุม (0.42, 0.0, 0.58, 1.0) กำหนดเส้นโค้งมาตรฐาน ease-in-out SwiftUI ใช้จุดเดียวกันสำหรับ Animation.easeInOut สำหรับ ease-in ให้ใช้ (0.42, 0.0, 1.0, 1.0) สำหรับ ease-out — (0.0, 0.0, 0.58, 1.0) Core Animation ดำเนินการแอนิเมชันในเซิร์ฟเวอร์เรนเดอร์ที่ 60/120 fps
แอนิเมชันแบบประกาศใน SwiftUI ด้วยเส้นโค้งที่กำหนดเองและเอฟเฟกต์สปริง ตัวปรับแต่งแอนิเมชันผูกกับการเปลี่ยนแปลงของ isAnimated
import SwiftUI
struct EasingExampleView: View {
@State private var isAnimated = false
var body: some View {
VStack {
Circle()
.fill(Color.blue)
.frame(width: 60, height: 60)
.offset(x: isAnimated ? 100 : 0, y: 0)
.animation(
.timingCurve(0.42, 0.0, 0.58, 1.0, duration: 0.6),
value: isAnimated
)
Button("Toggle") {
isAnimated.toggle()
}
}
}
}
ตัวปรับแต่ง .timingCurve(c0x, c0y, c1x, c1y, duration) สร้างแอนิเมชัน easing ที่กำหนดเองใน SwiftUI พารามิเตอร์ value ผูกแอนิเมชันกับสถานะเฉพาะ — เมื่อ isAnimated เปลี่ยนแปลง SwiftUI จะใช้เส้นโค้งโดยอัตโนมัติ สำหรับแอนิเมชันสปริง ให้ใช้ .spring(response: 0.4, dampingFraction: 0.6) แทน timingCurve
ค่าที่เทียบเท่า ease-in-out ใน Android ผ่าน PathInterpolator จุดควบคุม (0.42, 0.0, 0.58, 1.0) กำหนดเส้นโค้งเดียวกันกับใน iOS
import android.animation.ObjectAnimator
import android.graphics.Path
import android.view.animation.PathInterpolator
import android.view.View
fun View.fadeInWithEase() {
val path = Path().apply {
cubicTo(0.42f, 0.0f, 0.58f, 1.0f, 1.0f, 1.0f)
}
val interpolator = PathInterpolator(path)
ObjectAnimator.ofFloat(this, "alpha", 0f, 1f).apply {
duration = 500
interpolator = interpolator
start()
}
}
PathInterpolator ที่มีจุดควบคุม (0.42, 0.0, 0.58, 1.0) เหมือนกับ CAMediaTimingFunction ease-in-out บน iOS สำหรับโปรเจกต์ข้ามแพลตฟอร์ม ให้ใช้จุดควบคุมเดียวกัน — ซึ่งรับประกันพฤติกรรมแอนิเมชันที่เหมือนกันทางสายตา Material Design แนะนำ FastOutSlowInInterpolator (0.4, 0.0, 0.2, 1.0) เป็นทางเลือกสำหรับอินเทอร์เฟซเฉพาะของ Android
ใน Compose การ easing ถูกกำหนดผ่าน CubicBezierEasing ด้วยจุดควบคุมเดียวกัน ผลลัพธ์คือการปรากฏที่ราบรื่นขององค์ประกอบด้วยความโปร่งใสและการเลื่อนตำแหน่ง
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.CubicBezierEasing
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
@Composable
fun EasingComposable() {
var visible by remember { mutableStateOf(false) }
val easing = CubicBezierEasing(0.42f, 0.0f, 0.58f, 1.0f)
val alpha by animateFloatAsState(
targetValue = if (visible) 1f else 0f,
animationSpec = tween(durationMillis = 500, easing = easing)
)
Column {
Button(onClick = { visible = !visible }) {
Text("Toggle")
}
Box(
modifier = Modifier
.size(100.dp)
.graphicsLayer(alpha = alpha)
.background(MaterialTheme.colorScheme.primary)
)
}
}
CubicBezierEasing ใน Compose รับพารามิเตอร์จุดควบคุม 4 ตัวเดียวกัน animateFloatAsState จะสอดแทรกค่า alpha โดยอัตโนมัติด้วยเส้นโค้งที่ระบุ สำหรับการเปรียบเทียบ: FastOutSlowInEasing ใน Compose เทียบเท่ากับ (0.4, 0.0, 0.2, 1.0) — เส้นโค้งมาตรฐานของ Material Design 3 เส้นโค้งที่กำหนดเองผ่าน CubicBezierEasing ให้การควบคุมอย่างสมบูรณ์สำหรับแอนิเมชันของแบรนด์
คำถามที่พบบ่อย
Easing เป็นแนวคิดทั่วไปของฟังก์ชันการทำให้เรียบที่ใช้ใน iOS (CAMediaTimingFunction) เว็บ (CSS ease-in-out) และการออกแบบ Interpolator คือการนำแนวคิดนี้ไปใช้ใน Android (TimeInterpolator) โดยพื้นฐานแล้วมันคือ สิ่งเดียวกัน — เส้นโค้งทางคณิตศาสตร์ของความเร็วแอนิเมชัน ความแตกต่างอยู่ที่ชื่อและ API ของแพลตฟอร์มเท่านั้น ในโปรเจกต์ข้ามแพลตฟอร์ม คำว่า easing ถูกใช้เป็นคำสากล
iOS มี CAMediaTimingFunction พร้อมฟังก์ชันที่กำหนดไว้ล่วงหน้า 5 ฟังก์ชัน: linear, easeIn, easeOut, easeInEaseOut และ custom (Bezier แบบลูกบาศก์) ใน SwiftUI มี Animation.easeIn, Animation.easeOut, Animation.easeInOut, Animation.spring และ Animation.interpolatingSpring สำหรับเส้นโค้งที่กำหนดเอง ให้ใช้ Animation.timingCurve(c0x, c0y, c1x, c1y, duration:)
ได้ หากตั้งค่าเส้นโค้ง Bezier แบบลูกบาศก์เดียวกัน CAMediaTimingFunction ใน iOS และ PathInterpolator ใน Android รับจุดควบคุม Bezier แบบลูกบาศก์ (x1, y1, x2, y2) ตัวอย่างเช่น เส้นโค้ง ease-in-out (0.42, 0.0, 0.58, 1.0) จะให้พฤติกรรมเหมือนกันบนทั้งสองแพลตฟอร์ม Material Design ใช้แนวทางนี้สำหรับแอนิเมชันข้ามแพลตฟอร์ม
ใช้แนวทางการเคลื่อนที่ของ Material Design: สำหรับ การปรากฏขององค์ประกอบ — ease-out (เริ่มต้นเร็ว สิ้นสุดราบรื่น) สำหรับ การหายไป — ease-in (สิ้นสุดเร็ว) สำหรับ การเคลื่อนที่ระหว่างหน้าจอ — ease-in-out ใช้แอนิเมชันสปริงสำหรับองค์ประกอบเชิงโต้ตอบ (ปุ่ม การ์ด) หลีกเลี่ยง linear — มันดูไม่เป็นธรรมชาติ
อาการกระตุก (jank) เกิดขึ้นเมื่อแอนิเมชันไม่ถึง 60fps ตรวจสอบ: คุณใช้ CAMediaTimingFunction (ไม่ใช่ตัวจับเวลา) ใน iOS หรือไม่ ใช้ PathInterpolator (ไม่ใช่ Handler.post) ใน Android หรือไม่ การคำนวณหนักบนเธรดหลัก ลำดับชั้นเลเยอร์ที่ซับซ้อน หรือ drawable ขนาดใหญ่อาจทำให้เฟรมลดลง ใน iOS ให้ใช้ Core Animation ด้วย shouldRasterize สำหรับเลเยอร์ที่ซับซ้อน ใน Android — ใช้ Hardware Acceleration และ GPU rendering
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ