نکات کلیدی
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 (پایان سریع).
در 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) استفاده میشود.
در 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 سفارشی در هر فریم کلیدی را معرفی کرد که کنترل کاملی بر مسیر میدهد.
در 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 نیاز به بهینهسازی دارد.
انیمیشن پایه موقعیت لایه با منحنی سفارشی ease-in-out. CAMediaTimingFunction نقاط کنترل Bézier مکعبی را دریافت میکند که شروع و پایان نرم حرکت را تعریف میکنند.
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 با منحنی سفارشی و اثر فنری. اصلاحکننده animation به تغییر 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 به طور خودکار منحنی را اعمال میکند. برای انیمیشن فنری به جای timingCurve از .spring(response: 0.4, dampingFraction: 0.6) استفاده کنید.
معادل 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 (Bézier مکعبی). در SwiftUI Animation.easeIn، Animation.easeOut، Animation.easeInOut، Animation.spring و Animation.interpolatingSpring در دسترس هستند. برای منحنیهای سفارشی از Animation.timingCurve(c0x, c0y, c1x, c1y, duration:) استفاده کنید.
بله، اگر منحنی 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 از این رویکرد برای انیمیشنهای بینپلتفرمی استفاده میکند.
از دستورالعملهای Material Design Motion استفاده کنید: برای ظهور عناصر — 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 از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید