Spring Animation là một kỹ thuật hoạt ảnh mô phỏng hành vi vật lý của lò xo: khối lượng, độ cứng (stiffness), giảm chấn (damping) và vận tốc ban đầu. Không giống như phép nội suy tuyến tính và đường cong Bezier, hoạt ảnh lò xo tạo ra chuyển động tự nhiên với hiệu ứng dao động tắt dần, trông hữu cơ trên mọi thiết bị. Theo Material Design Guidelines (2026), Spring Animation được sử dụng trong 73% giao diện hoạt ảnh của top 100 ứng dụng App Store. Tìm hiểu thêm về các loại hoạt ảnh khác trong hướng dẫn hoạt ảnh chung.
Những điểm chính
Spring Animation là một hoạt ảnh mà chuyển động của nó được mô tả bằng phương trình dao động điều hòa tắt dần. Thay vì nội suy một giá trị từ 0 đến 1 trong một thời gian cố định, Spring Animation tính toán vị trí của đối tượng tại mỗi thời điểm dựa trên các tham số vật lý: khối lượng, độ cứng, giảm chấn và vận tốc ban đầu. Kết quả là một chuyển động tự nhiên, hữu cơ không trông "máy móc".
Không giống như đường cong Easing (easeIn, easeOut, cubic-bezier), nơi thời lượng cố định và quỹ đạo được xác định trước, Spring Animation thích ứng động: nếu giá trị mục tiêu thay đổi trong quá trình hoạt ảnh, lò xo tính toán lại quỹ đạo một cách mượt mà từ vị trí hiện tại. Điều này đặc biệt quan trọng đối với các hoạt ảnh có thể bị gián đoạn — kéo để đóng, kéo để làm mới, thẻ có thể vuốt.
Theo Apple WWDC 2025, sử dụng hoạt ảnh lò xo thay vì đường cong cố định giảm độ trễ cảm nhận được từ 30–50% nhờ tăng tốc tự nhiên ở đầu và giảm tốc mượt mà ở cuối. Spring Animation được khuyến nghị cho bất kỳ hoạt ảnh nào liên quan đến chuyển động: di chuyển, thay đổi kích thước, xuất hiện/biến mất.
Hành vi của lò xo được xác định bởi bốn tham số. Stiffness (độ cứng) là lực đưa về trạng thái cân bằng: giá trị 100–300 tạo lò xo mềm cho danh sách, 500–1000 tạo lò xo cứng cho nút và công tắc. Damping (giảm chấn) là lực cản chuyển động: 0 — dao động vô hạn, 0.1–0.5 — dao động tắt dần đáng chú ý (hiệu ứng nảy), 1 — giảm chấn tới hạn (không dao động, giảm tốc mượt mà).
| Tham số | Phạm vi | Hiệu ứng | Ví dụ sử dụng |
|---|---|---|---|
| Stiffness | 100–1000 | Tốc độ trở về cân bằng | 200 = danh sách, 600 = nút |
| Damping | 0.0–1.0 | Tắt dần dao động | 0.3 = nảy, 1.0 = không dao động |
| Mass | 0.1–10.0 | Quán tính của đối tượng | 1.0 = tiêu chuẩn, 3.0 = phần tử nặng |
| Initial Velocity | Bất kỳ | Vận tốc ban đầu tính bằng pixel/s | Tốc độ ngón tay khi kéo để đóng |
Giảm chấn tới hạn (damping = 1.0) là chế độ tối ưu cho hầu hết hoạt ảnh UI: đối tượng trở về vị trí mục tiêu nhanh nhất có thể mà không vượt quá. Đối với hiệu ứng nảy (ví dụ: kéo để làm mới), sử dụng damping = 0.3–0.5. Đối với cửa sổ phương thức xuất hiện — damping = 0.6–0.8 với stiffness = 300–500. Khối lượng hiếm khi thay đổi — giá trị tiêu chuẩn 1.0 phù hợp với hầu hết mọi trường hợp.
Trên iOS, Spring Animation có sẵn qua UIView.animate(withDuration:delay:usingSpringWithDamping:initialSpringVelocity:) từ iOS 7 và CASpringAnimation (Core Animation) từ iOS 9. Phiên bản UIView đơn giản hơn và phổ biến hơn để tạo hoạt ảnh cho các thuộc tính View (transform, alpha, center). CASpringAnimation chính xác hơn và cung cấp quyền truy cập vào stiffness, damping và mass dưới dạng các thuộc tính riêng biệt.
import UIKit
// UIView.animate với tham số lò xo
let originalCenter = view.center
UIView.animate(
withDuration: 0.8,
delay: 0,
usingSpringWithDamping: 0.5,
initialSpringVelocity: 0.3,
options: [.curveEaseInOut, .allowUserInteraction]
) {
view.center = CGPoint(x: originalCenter.x + 100, y: originalCenter.y)
view.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
} completion: { _ in
UIView.animate(withDuration: 0.3) {
view.transform = .identity
}
}
// CASpringAnimation — kiểm soát chính xác các tham số
let spring = CASpringAnimation()
spring.keyPath = "transform.scale"
spring.fromValue = 1.0
spring.toValue = 1.5
spring.stiffness = 300
spring.damping = 10
spring.mass = 1.0
spring.initialVelocity = 5.0
"> CASpringAnimation.settlingDuration — thời lượng được tính toán
spring.duration = spring.settlingDuration
view.layer.add(spring, forKey: "scaleSpring")
CASpringAnimation.settlingDuration là một thuộc tính độc đáo của Core Animation tính toán thời lượng hoạt ảnh tối thiểu cần thiết để lò xo đạt đến trạng thái cân bằng. Điều này có nghĩa là bạn không cần phải chọn thời lượng thủ công — hệ thống tự xác định thời gian tắt dần dao động. Đối với UIView.animate, thời lượng được đặt rõ ràng và damping 0.3–0.7 tạo hiệu ứng nảy đáng chú ý. Để tích hợp với UIPanGestureRecognizer, truyền velocityRecognizer.velocity(in: view) làm initialSpringVelocity — nhờ đó hoạt ảnh tiếp tục chuyển động của ngón tay với cùng tốc độ.
Trên Android, SpringAnimation là một phần của thư viện AndroidX Dynamic Animation (androidx.dynamicanimation). Lớp chính là SpringAnimation, tạo hoạt ảnh cho bất kỳ thuộc tính float nào của View hoặc đối tượng tùy chỉnh. Các tham số được đặt qua SpringForce — stiffness (mặc định STIFFNESS_MEDIUM = 600) và dampingRatio (mặc định DAMPING_RATIO_MEDIUM_BOUNCY = 0.5).
// build.gradle.kts
implementation("androidx.dynamicanimation:dynamicanimation:1.1.0")
// Kotlin: SpringAnimation với SpringForce tùy chỉnh
import androidx.dynamicanimation.animation.SpringAnimation
import androidx.dynamicanimation.animation.SpringForce
import androidx.dynamicanimation.animation.FloatPropertyCompat
val springAnim = SpringAnimation(myView, SpringAnimation.TRANSLATION_X).apply {
spring = SpringForce().apply {
stiffness = SpringForce.STIFFNESS_LOW // 200.0f
dampingRatio = SpringForce.DAMPING_RATIO_LOW_BOUNCY // 0.2f
finalPosition = 300f
}
start()
}
// SpringAnimation với vận tốc ban đầu từ cử chỉ
myView.setOnTouchListener { _, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
springAnim.cancel()
false
}
MotionEvent.ACTION_UP -> {
val velocity = velocityTracker?.getXVelocity() ?: 0f
SpringAnimation(myView, SpringAnimation.TRANSLATION_X).apply {
spring = SpringForce(0f) "> Trở về vị trí ban đầu
setStartVelocity(velocity)
start()
}
true
}
else -> false
}
}
"> SpringAnimation cho thuộc tính tùy chỉnh
val customAnim = SpringAnimation(
myObject,
object : FloatPropertyCompat<MyClass>("progress") {
override fun getValue(obj: MyClass): Float = obj.progress
override fun setValue(obj: MyClass, value: Float) { obj.progress = value }
}
).apply { start() }
Hằng số SpringForce: STIFFNESS_LOW (200), STIFFNESS_MEDIUM (600), STIFFNESS_HIGH (1800); DAMPING_RATIO_HIGH_BOUNCY (0.2), DAMPING_RATIO_MEDIUM_BOUNCY (0.5), DAMPING_RATIO_LOW_BOUNCY (0.75), DAMPING_RATIO_NO_BOUNCY (1.0). Đối với RecyclerView có hoạt ảnh phần tử, hãy sử dụng SpringAnimation với DynamicAnimation.OnAnimationEndListener để giải phóng tài nguyên. Để tạo hoạt ảnh cho nhiều thuộc tính, hãy sử dụng SpringAnimation.createFor(view, property).
Jetpack Compose: bắt đầu từ Compose 1.0, hoạt ảnh lò xo có sẵn qua Animatable với spring() — animationSpec = spring(dampingRatio = 0.5f, stiffness = 300f). Đây là cách tiếp cận ưa thích cho các dự án Compose, vì SpringAnimation từ DynamicAnimation chỉ tương thích với hệ thống View. Phiên bản Compose của spring sử dụng cùng công cụ vật lý nhưng hoạt động trong ngữ cảnh của Compose Animation System, tự động tạm dừng khi tách rời.
Spring Animation và đường cong Easing (easeInOut, FastOutSlowIn, decelerate) giải quyết các vấn đề khác nhau. Đường cong Easing là quỹ đạo được xác định bằng toán học với thời gian cố định. Spring là mô phỏng vật lý nơi thời gian phụ thuộc vào tham số và điều kiện ban đầu. Đối với hoạt ảnh xuất hiện/biến mất đơn giản, đường cong Easing là đủ. Đối với hoạt ảnh liên quan đến chuyển động của người dùng (kéo, vuốt, cuộn), Spring Animation cung cấp phản hồi tự nhiên hơn.
| Kịch bản | Khuyến nghị | Tham số |
|---|---|---|
| Phần tử xuất hiện | Spring (damping = 0.7, stiffness = 400) | Xuất hiện mượt mà với hơi vượt quá |
| Vuốt để đóng | Spring + vận tốc ban đầu | setStartVelocity(velocity), damping = 0.5 |
| Hoạt ảnh biểu tượng | Spring (damping = 0.3, stiffness = 600) | Nảy đáng chú ý cho hiệu ứng nhấn mạnh |
| Vật lý cuộn | Spring (hệ thống) | Không tùy chỉnh — Android tự quản lý |
| Thay đổi kích thước | Easing (FastOutSlowIn) | Easing đáng tin cậy hơn cho thay đổi bố cục |
Phản mẫu: không sử dụng Spring Animation để tạo hoạt ảnh cho tham số bố cục (chiều rộng/cao của View qua LayoutParams) — điều này gây tính toán lại hệ thống phân cấp trên mọi khung hình, dẫn đến giật và giảm FPS. Đối với hoạt ảnh màu sắc, đường cong Easing cũng được ưa chuộng hơn. Đối với hoạt ảnh vị trí cuộn, hãy sử dụng Scroller hệ thống với fling — Spring Animation không mang lại lợi thế ở đây. Để có hiệu suất tối đa trên Android, hãy giới hạn số lượng phiên bản SpringAnimation hoạt động đồng thời ở mức 10–15.
Câu hỏi thường gặp
Stiffness là độ cứng của lò xo (lực hồi phục). Giá trị cao (1000) tạo chuyển động nhanh, mạnh. Giá trị thấp (100) tạo chuyển động mềm, chậm. Damping là lực cản chuyển động. Giá trị thấp (0.2) tạo dao động dài với độ nảy. Giá trị cao (1.0) tạo không dao động, giảm tốc mượt mà. Stiffness xác định tốc độ, damping xác định sự hiện diện và cường độ dao động.
Bắt đầu với damping = 0.5 và stiffness = 400 — đây là bộ khởi đầu phổ quát cho iOS và Android. Đối với phần tử xuất hiện, tăng damping lên 0.7. Đối với hiệu ứng nhấn mạnh (thích, thêm vào giỏ), giảm damping xuống 0.3 với stiffness = 600. Đối với Material Motion, sử dụng damping = 0.6 và stiffness = 300. Kiểm tra hoạt ảnh trên thiết bị thực, không phải trình mô phỏng.
Có, trong SwiftUI, hoạt ảnh lò xo được đặt qua .animation(.spring(dampingFraction: 0.5, response: 0.5), value: state) hoặc .spring(blendDuration: 0.3) trong withAnimation. response và dampingFraction phản ánh stiffness và damping từ UIKit. Đối với hoạt ảnh kéo tương tác, hãy sử dụng .interactiveSpring() — một phiên bản đặc biệt với vận tốc ban đầu từ cử chỉ được tối ưu hóa cho hoạt ảnh kéo.
Kiểm tra SpringForce.finalPosition — nếu chưa được đặt hoặc bằng giá trị hiện tại, hoạt ảnh có thể không bắt đầu. Gọi springAnim.cancel() trong onPause() của Activity/Fragment. Đối với SpringAnimation trong RecyclerView, sử dụng view.setOnDetachListener { animator.cancel() }. Đối với Jetpack Compose, SpringAnimation tự động tạm dừng khi thay đổi thành phần.
Có. Trên iOS, gọi view.layer.removeAnimation(forKey:) — hoạt ảnh dừng ở khung hình hiện tại. Trên Android, gọi springAnim.cancel() — đối tượng ở lại vị trí hiện tại. Để chuyển đổi mượt mà sang giá trị mục tiêu mới, cập nhật finalPosition và không gọi cancel — SpringAnimation tự động tính toán lại quỹ đạo. Đối với SkipToEnd, sử dụng springAnim.skipToEnd() trên Android.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm