Easing: hàm làm mịn trong hoạt hình di động

Tác giả: IT Sectr Đã đăng: 2026-03-02 Thời gian đọc: 8 phút
Easing (hàm làm mịn) là một đường cong toán học xác định tốc độ thay đổi của tham số hoạt hình theo thời gian. Thay vì chuyển động tuyến tính được tăng tốc đều, easing mô phỏng hành vi vật lý: một đối tượng có thể tăng tốc (ease-in), giảm tốc (ease-out) hoặc làm cả hai (ease-in-out). Trong iOS, easing được triển khai qua lớp CAMediaTimingFunction (Core Animation) và các bộ sửa đổi SwiftUI (Animation.easeInOut). Trong Android — qua PathInterpolator và TimeInterpolator. Theo Tài liệu nhà phát triển Apple, một đường cong easing được chọn đúng sẽ giảm thời gian hoạt hình cảm nhận từ 20–35% mà không thay đổi thời lượng thực tế. Tại IT Sectr, chúng tôi đã chuẩn hóa đường cong ease-in-out (0.42, 0.0, 0.58, 1.0) làm tiêu chuẩn doanh nghiệp cho tất cả các dự án di động.

Điểm chính

  • Easing — hàm tốc độ hoạt hình mô phỏng chuyển động vật lý: tăng tốc, giảm tốc hoặc kết hợp.
  • CAMediaTimingFunction — lớp iOS Core Animation với 5 đường cong định sẵn và Bézier bậc ba tùy chỉnh.
  • SwiftUI Animation — các bộ sửa đổi .easeIn, .easeOut, .easeInOut, .spring và .timingCurve cho easing khai báo.
  • PathInterpolator — tương đương easing trong Android với đường cong Bézier bậc ba qua hai điểm điều khiển.
  • Bézier bậc ba — định dạng phổ quát để chỉ định đường cong easing, chung cho iOS, Android và web.

Easing là gì?

Easing (hàm làm mịn) là một hàm toán học f(t) ánh xạ thời gian tuyến tính t ∈ [0,1] thành một giá trị hoạt hình phi tuyến. Nếu một hoạt hình thay đổi vị trí của đối tượng từ A đến B trong 500 ms, easing xác định cách đối tượng di chuyển giữa các điểm này tại mỗi thời điểm. Easing tuyến tính (f(t) = t) có nghĩa là tốc độ không đổi — đối tượng di chuyển một cách máy móc. Easing phi tuyến cảm giác tự nhiên: đối tượng tăng tốc (ease-in), giảm tốc (ease-out) hoặc có bắt đầu và kết thúc mượt mà (ease-in-out).

Các hàm easing được mô tả bằng đường cong Bézier bậc ba, trong đó trục X là thời gian được chuẩn hóa và trục Y là tiến trình được chuẩn hóa. Hai điểm điều khiển (P1, P2) xác định hình dạng đường cong. Các đường cong tiêu chuẩn được quy định bởi thông số kỹ thuật 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). Các giá trị tương tự này được sử dụng trong CAMediaTimingFunction trên iOS và PathInterpolator trên Android. Theo Hướng dẫn Chuyển động Material Design, các đường cong easing phải phù hợp với ngữ cảnh: cho sự xuất hiện — ease-out (bắt đầu nhanh), cho sự biến mất — ease-in (kết thúc nhanh).

Easing trong iOS: CAMediaTimingFunction

Trong iOS, easing hoạt động chủ yếu qua lớp CAMediaTimingFunction của Core Animation. Nó chấp nhận một đường cong Bézier bậc ba dưới dạng hai điểm điều khiển (c1x, c1y, c2x, c2y) và áp dụng cho CAAnimation (CABasicAnimation, CAKeyframeAnimation). CAMediaTimingFunction hỗ trợ 5 hàm định sẵn: linear, easeIn, easeOut, easeInEaseOut và default (tương đương easeInEaseOut).

CAMediaTimingFunction hoạt động ở cấp CALayer — hoạt hình chạy trong một tiến trình máy chủ kết xuất riêng, đảm bảo 60fps ngay cả với các biến đổi phức tạp. Đối với hoạt hình thuộc tính UIView (frame, alpha, transform), UIKit sử dụng UIView.animate với tham số options: .curveEaseInOut, bên trong cũng tạo một CAMediaTimingFunction với đường cong tương ứng. Trong iOS 17+, UIViewAnimationCurve.custom với BlockParameters cho Bézier tùy chỉnh đã được thêm vào — điều này loại bỏ nhu cầu viết CAAnimation tùy chỉnh. CAMediaTimingFunction cũng được sử dụng trong UIKit Dynamics và UIScrollView để giảm tốc (UIScrollViewDecelerationRate).

Easing trong SwiftUI

Trong SwiftUI, easing được thiết lập qua cấu trúc Animation với các bộ sửa đổi .easeIn, .easeOut, .easeInOut, .spring và .interpolatingSpring. Không giống UIKit, nơi hoạt hình là thủ tục (beginAnimations/commitAnimations), SwiftUI sử dụng cách tiếp cận khai báo: hoạt hình là một thuộc tính trạng thái thay đổi và khung làm việc tự xác định cách hoạt hình chuyển tiếp.

Bộ sửa đổi .timingCurve(c0x, c0y, c1x, c1y) chấp nhận các điểm điều khiển Bézier bậc ba trực tiếp — đây là một chức năng tương tự đầy đủ của CAMediaTimingFunction. SwiftUI Animation hỗ trợ các đường cong định sẵn dưới dạng thuộc tính tĩnh: Animation.easeInOut tương đương với ease-in-out (0.42, 0.0, 0.58, 1.0). Cho hoạt hình spring, Animation.spring(response:dampingFraction:blendDuration) được sử dụng, trong đó dampingFraction < 1 tạo hiệu ứng lò xo có giảm chấn. Tại WWDC 2023, Apple đã giới thiệu Animation.keyframes — hoạt hình từng khung hình với easing tùy chỉnh trên mỗi khung hình chính, mang lại toàn quyền kiểm soát quỹy đạo.

Easing trong Android: PathInterpolator

Trong Android, easing được triển khai qua PathInterpolator (API 21+) — một lớp chấp nhận đối tượng Path với một đường cong Bézier bậc ba. Các điểm điều khiển được chỉ định qua Path.cubicTo(x1, y1, x2, y2, x3, y3), trong đó (x1,y1) là điểm điều khiển đầu tiên, (x2,y2) là điểm thứ hai và (x3,y3) là điểm cuối (luôn là 1,1). PathInterpolator là bắt buộc nếu cần một đường cong tùy chỉnh; cho các kịch bản tiêu chuẩn, có sẵn AccelerateDecelerateInterpolator (ease-in-out), AccelerateInterpolator (ease-in) và DecelerateInterpolator (ease-out).

Trong Jetpack Compose, easing được thiết lập qua đối tượng Easing từ gói androidx.compose.animation. Có sẵn FastOutSlowInEasing, LinearOutSlowInEasing, FastOutLinearInEasing, cũng như Easing tùy chỉnh qua CubicBezierEasing. Compose Easing được áp dụng trong animate*AsState, AnimatedVisibility và AnimatedContent. Sự khác biệt chính so với hệ thống View: Compose nội suy easing ở phía Kotlin (không phải trong RenderNode), mang lại sự linh hoạt hơn cho các hoạt hình tùy chỉnh nhưng yêu cầu tối ưu hóa trên màn hình 120Hz.

Ví dụ mã

iOS: CAMediaTimingFunction với CABasicAnimation

Hoạt hình vị trí lớp cơ bản với đường cong ease-in-out tùy chỉnh. CAMediaTimingFunction chấp nhận các điểm điều khiển Bézier bậc ba xác định sự bắt đầu và kết thúc mượt mà của chuyển động.

swift
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")
}

Các điểm điều khiển (0.42, 0.0, 0.58, 1.0) xác định đường cong ease-in-out tiêu chuẩn. SwiftUI sử dụng cùng các điểm cho Animation.easeInOut. Cho ease-in, sử dụng (0.42, 0.0, 1.0, 1.0); cho ease-out — (0.0, 0.0, 0.58, 1.0). Core Animation thực thi hoạt hình trong máy chủ kết xuất ở 60/120 fps.

SwiftUI: Easing với timingCurve

Hoạt hình khai báo trong SwiftUI với đường cong tùy chỉnh và hiệu ứng spring. Bộ sửa đổi hoạt hình liên kết với sự thay đổi của isAnimated.

swift
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()
            }
        }
    }
}

Bộ sửa đổi .timingCurve(c0x, c0y, c1x, c1y, duration) tạo một hoạt hình easing tùy chỉnh trong SwiftUI. Tham số value liên kết hoạt hình với một trạng thái cụ thể — khi isAnimated thay đổi, SwiftUI tự động áp dụng đường cong. Cho hoạt hình spring, sử dụng .spring(response: 0.4, dampingFraction: 0.6) thay vì timingCurve.

Android: PathInterpolator với Easing tùy chỉnh

Tương đương ease-in-out trong Android qua PathInterpolator. Các điểm điều khiển (0.42, 0.0, 0.58, 1.0) xác định cùng một đường cong như trong iOS.

kotlin
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 với các điểm điều khiển (0.42, 0.0, 0.58, 1.0) giống hệt CAMediaTimingFunction ease-in-out trên iOS. Cho các dự án đa nền tảng, hãy sử dụng cùng các điểm điều khiển — điều này đảm bảo hành vi hoạt hình giống hệt về mặt trực quan. Material Design khuyến nghị FastOutSlowInInterpolator (0.4, 0.0, 0.2, 1.0) như một sự thay thế cho các giao diện dành riêng cho Android.

Jetpack Compose: Easing với CubicBezierEasing

Trong Compose, easing được thiết lập qua CubicBezierEasing với các điểm điều khiển tương tự. Kết quả là sự xuất hiện mượt mà của phần tử với độ trong suốt và độ dịch chuyển.

kotlin
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 trong Compose chấp nhận cùng 4 tham số điểm điều khiển. animateFloatAsState tự động nội suy giá trị alpha với đường cong được chỉ định. Để so sánh: FastOutSlowInEasing trong Compose tương đương với (0.4, 0.0, 0.2, 1.0) — đường cong tiêu chuẩn của Material Design 3. Các đường cong tùy chỉnh qua CubicBezierEasing mang lại toàn quyền kiểm soát cho các hoạt hình thương hiệu.

Câu hỏi thường gặp

Easing khác gì so với interpolator?

Easing là một khái niệm chung về hàm làm mịn được sử dụng trong iOS (CAMediaTimingFunction), web (CSS ease-in-out) và thiết kế. Interpolator là sự triển khai của khái niệm này trong Android (TimeInterpolator). Về bản chất, chúng là cùng một thứ — một đường cong toán học của tốc độ hoạt hình. Sự khác biệt chỉ ở tên gọi và API nền tảng. Trong các dự án đa nền tảng, thuật ngữ easing được sử dụng như một thuật ngữ phổ quát.

Những hàm easing nào có sẵn trong iOS?

iOS cung cấp CAMediaTimingFunction với 5 hàm định sẵn: linear, easeIn, easeOut, easeInEaseOut và custom (Bézier bậc ba). Trong SwiftUI, có sẵn Animation.easeIn, Animation.easeOut, Animation.easeInOut, Animation.spring và Animation.interpolatingSpring. Cho các đường cong tùy chỉnh, hãy sử dụng Animation.timingCurve(c0x, c0y, c1x, c1y, duration:).

Có thể sử dụng cùng một hàm easing trên iOS và Android không?

Có, nếu cài đặt cùng một đường cong Bézier bậc ba. CAMediaTimingFunction trên iOS và PathInterpolator trên Android chấp nhận các điểm điều khiển Bézier bậc ba (x1, y1, x2, y2). Ví dụ, đường cong ease-in-out (0.42, 0.0, 0.58, 1.0) sẽ cho hành vi giống hệt trên cả hai nền tảng. Material Design sử dụng cách tiếp cận này cho các hoạt hình đa nền tảng.

Làm thế nào để chọn đường cong easing phù hợp cho ứng dụng của tôi?

Sử dụng Hướng dẫn Chuyển động Material Design: cho sự xuất hiện của phần tử — ease-out (bắt đầu nhanh, kết thúc mượt), cho sự biến mất — ease-in (kết thúc nhanh), cho chuyển động giữa các màn hình — ease-in-out. Sử dụng hoạt hình spring cho các phần tử tương tác (nút, thẻ). Tránh linear — nó trông không tự nhiên.

Tại sao hoạt hình easing của tôi bị giật?

Giật (jank) xảy ra khi hoạt hình không đạt được 60fps. Hãy kiểm tra: bạn có đang sử dụng CAMediaTimingFunction (không phải timer) trong iOS, PathInterpolator (không phải Handler.post) trong Android không? Các tính toán nặng trên luồng chính, hệ thống phân cấp lớp phức tạp hoặc một drawable lớn có thể gây rụng khung hình. Trong iOS, hãy sử dụng Core Animation với shouldRasterize cho các lớp phức tạp; trong Android — Tăng tốc phần cứng (Hardware Acceleration) và kết xuất GPU.

Tổng kết

  • Easing là một hàm tốc độ hoạt hình làm cho chuyển động trở nên tự nhiên thông qua các thay đổi tham số phi tuyến theo thời gian.
  • iOS CAMediaTimingFunction và SwiftUI Animation.timingCurve triển khai easing qua đường cong Bézier bậc ba với hai điểm điều khiển.
  • Android PathInterpolator và Compose CubicBezierEasing là các chức năng tương tự đầy đủ của iOS cho các đường cong làm mịn tùy chỉnh.
  • Các đường cong easing đa nền tảng được xác định bởi cùng các điểm điều khiển — cơ sở cho sự thống nhất hoạt hình.
  • Material Design 3 khuyến nghị fast-out-slow-in cho sự xuất hiện, linear-out-slow-in cho sự biến mất.
  • Hoạt hình spring trong SwiftUI (Animation.spring) và Compose (spring()) tạo hiệu ứng lò xo vật lý với dampingFraction.
  • Easing tuyến tính không được khuyến nghị cho hoạt hình giao diện — nó trông không tự nhiên và máy móc.

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.

Thảo luận dự án

Đọc thêm