Easing: ماهیت، توابع نرم‌سازی در انیمیشن موبایل

نویسنده: IT Sectr منتشر شده: 2026-03-02 زمان مطالعه: 8 دقیقه
Easing (تابع نرم‌سازی) — منحنی ریاضی است که سرعت تغییر پارامتر انیمیشن را در طول زمان تعیین می‌کند. به جای حرکت خطی با شتاب ثابت، easing رفتار فیزیکی را شبیه‌سازی می‌کند: جسم می‌تواند شتاب بگیرد (ease-in)، کند شود (ease-out) یا هر دو را انجام دهد (ease-in-out). در iOS، easing از طریق کلاس CAMediaTimingFunction (Core Animation) و اصلاح‌کننده‌های SwiftUI (Animation.easeInOut) پیاده‌سازی شده است. در Android — از طریق PathInterpolator و TimeInterpolator. طبق مستندات Apple Developer، منحنی easing به درستی انتخاب شده زمان درک شده انیمیشن را 20–35% بدون تغییر مدت زمان واقعی کاهش می‌دهد. در IT Sectr ما منحنی ease-in-out (0.42, 0.0, 0.58, 1.0) را به عنوان استاندارد شرکتی برای تمام پروژه‌های موبایل استانداردسازی کردیم.

نکات کلیدی

  • Easing — تابع سرعت انیمیشن که حرکت فیزیکی را شبیه‌سازی می‌کند: شتاب، کاهش سرعت یا ترکیبی.
  • CAMediaTimingFunction — کلاس iOS Core Animation با 5 منحنی از پیش تعریف شده و Bézier مکعبی سفارشی.
  • SwiftUI Animation — اصلاح‌کننده‌های .easeIn، .easeOut، .easeInOut، .spring و .timingCurve برای easing اعلامی.
  • PathInterpolator — معادل easing در Android با منحنی Bézier مکعبی از طریق دو نقطه کنترل.
  • Bézier مکعبی — قالب جهانی برای تعریف منحنی easing، مشترک بین iOS، Android و وب.

Easing چیست؟

Easing (تابع نرم‌سازی) — تابع ریاضی f(t) است که مسیر خطی زمان t ∈ [0,1] را به یک مقدار غیرخطی انیمیشن نگاشت می‌کند. اگر انیمیشن موقعیت جسم را از A به B در 500 میلی‌ثانیه تغییر دهد، easing تعیین می‌کند که جسم در هر لحظه از زمان بین این نقاط چگونه حرکت می‌کند. Easing خطی (f(t) = t) به معنای سرعت ثابت است — جسم به صورت مکانیکی حرکت می‌کند. Easing غیرخطی — طبیعی: جسم شتاب می‌گیرد (ease-in)، کند می‌شود (ease-out) یا شروع و پایان نرمی دارد (ease-in-out).

توابع Easing با منحنی‌های Bézier مکعبی (Cubic Bézier) توصیف می‌شوند، جایی که محور 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 Motion، منحنی‌های easing باید با زمینه مطابقت داشته باشند: برای ظهور — ease-out (شروع سریع)، برای ناپدید شدن — ease-in (پایان سریع).

Easing در iOS: CAMediaTimingFunction

در iOS کار اصلی با easing از طریق کلاس CAMediaTimingFunction از Core Animation انجام می‌شود. این کلاس منحنی Bézier مکعبی را به صورت دو نقطه کنترل (c1x, c1y, c2x, c2y) دریافت می‌کند و به CAAnimation (CABasicAnimation, CAKeyframeAnimation) اعمال می‌شود. CAMediaTimingFunction از 5 تابع از پیش تعریف شده پشتیبانی می‌کند: linear, easeIn, easeOut, easeInEaseOut و default (معادل easeInEaseOut).

CAMediaTimingFunction در سطح CALayer کار می‌کند — انیمیشن در یک فرآیند جداگانه به نام render server اجرا می‌شود که حتی در تبدیل‌های پیچیده 60fps را تضمین می‌کند. برای انیمیشن ویژگی‌های UIView (frame, alpha, transform)، UIKit از UIView.animate با پارامتر options: .curveEaseInOut استفاده می‌کند که در داخل نیز CAMediaTimingFunction با منحنی مربوطه ایجاد می‌کند. در iOS 17+، UIViewAnimationCurve.custom با BlockParameters برای Bézier سفارشی اضافه شده است — این نیاز به نوشتن CAAnimation اختصاصی را برطرف می‌کند. CAMediaTimingFunction همچنین در UIKit Dynamics و UIScrollView برای کاهش سرعت (UIScrollViewDecelerationRate) استفاده می‌شود.

Easing در SwiftUI

در SwiftUI easing از طریق ساختار Animation با اصلاح‌کننده‌های .easeIn، .easeOut، .easeInOut، .spring و .interpolatingSpring تعریف می‌شود. برخلاف UIKit که انیمیشن رویه‌ای است (beginAnimations/commitAnimations)، SwiftUI از رویکرد اعلامی استفاده می‌کند: انیمیشن یک ویژگی وضعیت است که تغییر می‌کند و چارچوب خود تعیین می‌کند که چگونه انتقال را انیمیشن کند.

اصلاح‌کننده .timingCurve(c0x, c0y, c1x, c1y) مستقیماً نقاط کنترل Bézier مکعبی را دریافت می‌کند — این معادل کامل 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 سفارشی در هر فریم کلیدی را معرفی کرد که کنترل کاملی بر مسیر می‌دهد.

Easing در Android: PathInterpolator

در Android easing از طریق PathInterpolator (API 21+) پیاده‌سازی شده است — کلاسی که یک شی Path با منحنی Bézier مکعبی دریافت می‌کند. نقاط کنترل از طریق 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 نیاز به بهینه‌سازی دارد.

نمونه کدها

iOS: CAMediaTimingFunction با CABasicAnimation

انیمیشن پایه موقعیت لایه با منحنی سفارشی ease-in-out. CAMediaTimingFunction نقاط کنترل Bézier مکعبی را دریافت می‌کند که شروع و پایان نرم حرکت را تعریف می‌کنند.

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

نقاط کنترل (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 انیمیشن را در render server با فرکانس 60/120 fps اجرا می‌کند.

SwiftUI: Easing با timingCurve

انیمیشن اعلامی در SwiftUI با منحنی سفارشی و اثر فنری. اصلاح‌کننده animation به تغییر 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()
            }
        }
    }
}

اصلاح‌کننده .timingCurve(c0x, c0y, c1x, c1y, duration) یک انیمیشن easing سفارشی در SwiftUI ایجاد می‌کند. پارامتر value انیمیشن را به یک وضعیت خاص متصل می‌کند — با تغییر isAnimated، SwiftUI به طور خودکار منحنی را اعمال می‌کند. برای انیمیشن فنری به جای timingCurve از .spring(response: 0.4, dampingFraction: 0.6) استفاده کنید.

Android: PathInterpolator با easing سفارشی

معادل ease-in-out در Android از طریق PathInterpolator. نقاط کنترل (0.42, 0.0, 0.58, 1.0) همان منحنی 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 با نقاط کنترل (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 توصیه می‌کند.

Jetpack Compose: Easing با CubicBezierEasing

در Compose easing از طریق CubicBezierEasing با همان نقاط کنترل تعریف می‌شود. نتیجه — ظهور نرم یک عنصر با شفافیت و جابجایی.

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 در Compose همان 4 پارامتر نقاط کنترل را دریافت می‌کند. animateFloatAsState به طور خودکار مقدار alpha را با منحنی داده شده درون‌یابی می‌کند. برای مقایسه: FastOutSlowInEasing در Compose معادل (0.4, 0.0, 0.2, 1.0) — منحنی استاندارد Material Design 3 است. منحنی‌های سفارشی از طریق CubicBezierEasing کنترل کاملی برای انیمیشن‌های برند می‌دهند.

سوالات متداول

تفاوت easing و interpolator چیست؟

Easing — مفهوم کلی تابع نرم‌سازی است که در iOS (CAMediaTimingFunction)، وب (CSS ease-in-out) و طراحی استفاده می‌شود. Interpolator — پیاده‌سازی این مفهوم در Android (TimeInterpolator) است. در اصل یکسان هستند — منحنی ریاضی سرعت انیمیشن. تفاوت فقط در نام و API پلتفرم است. در پروژه‌های بین‌پلتفرمی، اصطلاح easing به عنوان جهانی استفاده می‌شود.

چه توابع easing در iOS موجود هستند؟

iOS CAMediaTimingFunction با 5 تابع از پیش تعریف شده ارائه می‌دهد: linear، easeIn، easeOut، easeInEaseOut و custom (Bézier مکعبی). در SwiftUI Animation.easeIn، Animation.easeOut، Animation.easeInOut، Animation.spring و Animation.interpolatingSpring در دسترس هستند. برای منحنی‌های سفارشی از Animation.timingCurve(c0x, c0y, c1x, c1y, duration:) استفاده کنید.

آیا می‌توان از یک تابع easing در iOS و Android استفاده کرد؟

بله، اگر منحنی Bézier مکعبی یکسانی تنظیم کنید. CAMediaTimingFunction در iOS و PathInterpolator در Android نقاط کنترل Bézier مکعبی (x1, y1, x2, y2) را دریافت می‌کنند. به عنوان مثال، منحنی ease-in-out (0.42, 0.0, 0.58, 1.0) رفتار یکسانی در هر دو پلتفرم خواهد داشت. Material Design از این رویکرد برای انیمیشن‌های بین‌پلتفرمی استفاده می‌کند.

چگونه منحنی easing مناسب برای برنامه خود را انتخاب کنم؟

از دستورالعمل‌های Material Design Motion استفاده کنید: برای ظهور عناصر — ease-out (شروع سریع، پایان نرم)، برای ناپدید شدن — ease-in (پایان سریع)، برای جابجایی بین صفحات — ease-in-out. از انیمیشن فنری برای عناصر تعاملی (دکمه‌ها، کارت‌ها) استفاده کنید. از linear اجتناب کنید — غیرطبیعی به نظر می‌رسد.

چرا انیمیشن من با easing تکان‌دار به نظر می‌رسد؟

تکان‌داری (jank) زمانی رخ می‌دهد که انیمیشن به 60fps نمی‌رسد. بررسی کنید: آیا از CAMediaTimingFunction (نه تایمر) در iOS، PathInterpolator (نه Handler.post) در Android استفاده می‌کنید. محاسبات سنگین در نخ اصلی، سلسله‌مراتب پیچیده لایه‌ها یا drawable بزرگ می‌تواند باعث افت فریم شود. در iOS از Core Animation با shouldRasterize برای لایه‌های پیچیده استفاده کنید؛ در Android — از Hardware Acceleration و GPU rendering.

خلاصه

  • Easing — تابع سرعت انیمیشن است که حرکت را از طریق تغییر غیرخطی پارامترها در طول زمان طبیعی می‌کند.
  • iOS CAMediaTimingFunction و SwiftUI Animation.timingCurve easing را از طریق منحنی Bézier مکعبی با دو نقطه کنترل پیاده‌سازی می‌کنند.
  • Android PathInterpolator و Compose CubicBezierEasing — معادل‌های کامل iOS برای منحنی‌های نرم‌سازی سفارشی.
  • منحنی‌های easing بین‌پلتفرمی با نقاط کنترل یکسان تعریف می‌شوند — اساس یکپارچه‌سازی انیمیشن‌ها.
  • Material Design 3 fast-out-slow-in را برای ظهور، linear-out-slow-in را برای ناپدید شدن توصیه می‌کند.
  • انیمیشن فنری در SwiftUI (Animation.spring) و Compose (spring()) یک اثر فنری فیزیکی با dampingFraction ایجاد می‌کند.
  • Linear easing برای انیمیشن‌های رابط کاربری توصیه نمی‌شود — غیرطبیعی و مکانیکی به نظر می‌رسد.

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید