النقاط الرئيسية
Easing (دالة التخفيف) هي دالة رياضية f(t) تُسقط الزمن الخطي t ∈ [0,1] إلى قيمة حركة غير خطية. إذا غيَّرت حركة موقع جسم من A إلى B في 500 مللي ثانية، يُحدد التخفيف كيف يتحرك الجسم بين هاتين النقطتين في كل لحظة زمنية. التخفيف الخطي (f(t) = t) يعني سرعة ثابتة — يتحرك الجسم ميكانيكياً. التخفيف غير الخطي يبدو طبيعياً: يتسارع الجسم (ease-in)، يتباطأ (ease-out)، أو يكون له بداية ونهاية سلسة (ease-in-out).
تُوصف دوال التخفيف بمنحنيات بيزيه المكعبة، حيث المحور 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، يجب أن تتوافق منحنيات التخفيف مع السياق: للظهور — ease-out (بداية سريعة)، للاختفاء — ease-in (نهاية سريعة).
في iOS، يعمل التخفيف بشكل أساسي من خلال الفئة CAMediaTimingFunction من Core Animation. تقبل منحنى بيزيه مكعب كنقطتي تحكم (c1x, c1y, c2x, c2y) وتُطبق على CAAnimation (CABasicAnimation, CAKeyframeAnimation). تدعم CAMediaTimingFunction 5 دوال محددة مسبقاً: linear، easeIn، easeOut، easeInEaseOut، و default (المكافئ لـ easeInEaseOut).
تعمل CAMediaTimingFunction على مستوى CALayer — يتم تنفيذ الحركة في عملية خادم عرض منفصلة، مما يضمن 60 إطاراً في الثانية حتى مع التحولات المعقدة. لتحريك خصائص UIView (frame، alpha، transform)، تستخدم UIKit UIView.animate مع المعامل options: .curveEaseInOut، والذي ينشئ داخلياً أيضاً CAMediaTimingFunction بالمنحنى المقابل. في iOS 17+، تمت إضافة UIViewAnimationCurve.custom مع BlockParameters لبيزيه مخصص — مما يلغي الحاجة لكتابة CAAnimation مخصصة. تُستخدم CAMediaTimingFunction أيضاً في UIKit Dynamics و UIScrollView للتباطؤ (UIScrollViewDecelerationRate).
في SwiftUI، يتم ضبط التخفيف من خلال البنية Animation مع المعدِّلات .easeIn و .easeOut و .easeInOut و .spring و .interpolatingSpring. على عكس UIKit، حيث الحركة إجرائية (beginAnimations/commitAnimations)، يستخدم SwiftUI نهجاً تصريحياً: الحركة هي خاصية حالة تتغير، والإطار نفسه يُحدد كيفية تحريك الانتقال.
المعدِّل .timingCurve(c0x, c0y, c1x, c1y) يقبل نقاط تحكم بيزيه المكعب مباشرة — وهو مكافئ كامل لـ CAMediaTimingFunction. تدعم Animation في SwiftUI المنحنيات المحددة مسبقاً كخصائص ثابتة: Animation.easeInOut مكافئ لـ ease-in-out (0.42, 0.0, 0.58, 1.0). لحركة الربيع (spring)، يُستخدم Animation.spring(response:dampingFraction:blendDuration)، حيث يُنشئ dampingFraction < 1 تأثير زنبرك مع تخميد. في WWDC 2023، قدمت Apple Animation.keyframes — حركة إطار بإطار مع تخفيف مخصص في كل إطار رئيسي، مما يعطي تحكماً كاملاً في المسار.
في Android، يتم تطبيق التخفيف من خلال PathInterpolator (API 21+) — فئة تقبل كائن Path بمنحنى بيزيه مكعب. تُحدد نقاط التحكم عبر 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 من حزمة androidx.compose.animation. تتوفر FastOutSlowInEasing و LinearOutSlowInEasing و FastOutLinearInEasing، بالإضافة إلى Easing مخصص عبر CubicBezierEasing. يُطبق Easing في Compose في animate*AsState و AnimatedVisibility و AnimatedContent. الفرق الرئيسي عن نظام View: يقوم Compose بتحليل التخفيف في جانب Kotlin (ليس في RenderNode)، مما يعطي مرونة أكبر للحركات المخصصة ولكنه يتطلب تحسيناً في شاشات 120Hz.
حركة أساسية لموقع الطبقة بمنحنى ease-in-out مخصص. تقبل CAMediaTimingFunction نقاط تحكم بيزيه المكعب التي تُحدد بداية ونهاية سلسة للحركة.
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 إطاراً في الثانية.
حركة تصريحية في 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) يُنشئ حركة تخفيف مخصصة في 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، يتم ضبط التخفيف عبر 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 يقبل نفس معاملات نقاط التحكم الأربعة. يقوم 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 (بيزيه مكعب). في SwiftUI، تتوفر Animation.easeIn و Animation.easeOut و Animation.easeInOut و Animation.spring و Animation.interpolatingSpring. للمنحنيات المخصصة، استخدم Animation.timingCurve(c0x, c0y, c1x, c1y, duration:).
نعم، إذا تم تعيين نفس منحنى بيزيه المكعب. تقبل CAMediaTimingFunction في iOS و PathInterpolator في Android نقاط تحكم بيزيه المكعب (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) عندما لا تصل الحركة إلى 60 إطاراً في الثانية. تحقق: هل تستخدم CAMediaTimingFunction (وليس مؤقتاً) في iOS، PathInterpolator (وليس Handler.post) في Android. الحسابات الثقيلة في الخيط الرئيسي، أو التسلسل الهرمي المعقد للطبقات، أو رسم كبير يمكن أن يسبب فقدان الإطارات. في iOS، استخدم Core Animation مع shouldRasterize للطبقات المعقدة؛ في Android — تسريع الأجهزة (Hardware Acceleration) وعرض GPU.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا