Easing: دوال التخفيف في الرسوم المتحركة للجوال

المؤلف: IT Sectr نُشر: 2026-03-02 وقت القراءة: 8 دق
Easing (دالة التخفيف) هو منحنى رياضي يُحدد سرعة تغير معامل الحركة مع مرور الوقت. بدلاً من الحركة الخطية المتسارعة بشكل منتظم، يحاكي التخفيف السلوك الطبيعي: يمكن للجسم أن يتسارع (ease-in)، يتباطأ (ease-out)، أو يفعل كليهما (ease-in-out). في iOS، يتم تطبيق التخفيف من خلال الفئة CAMediaTimingFunction (Core Animation) ومعدِّلات SwiftUI (Animation.easeInOut). في Android — من خلال PathInterpolator و TimeInterpolator. وفقاً لوثائق مطوري Apple، فإن منحنى التخفيف المختار بشكل صحيح يُقلل وقت الحركة المٌدرَك بنسبة 20–35% دون تغيير المدة الفعلية. في IT Sectr، قمنا بتوحيد منحنى ease-in-out (0.42, 0.0, 0.58, 1.0) كمعيار مؤسسي لجميع مشاريع الجوال.

النقاط الرئيسية

  • Easing — دالة سرعة للحركة تحاكي الحركة الطبيعية: التسارع، التباطؤ، أو مزيج منهما.
  • CAMediaTimingFunction — فئة Core Animation في iOS مع 5 منحنيات محددة مسبقاً ومنحنى بيزيه مكعب مخصص.
  • SwiftUI Animation — معدِّلات .easeIn و .easeOut و .easeInOut و .spring و .timingCurve للتخفيف التصريحي.
  • PathInterpolator — المكافئ للتخفيف في Android بمنحنى بيزيه مكعب عبر نقطتي تحكم.
  • منحنى بيزيه مكعب — تنسيق عالمي لتحديد منحنيات التخفيف، مشترك بين iOS و Android والويب.

ما هو التخفيف (Easing)؟

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

في 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

في 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

في 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.

أمثلة الكود

iOS: CAMediaTimingFunction مع CABasicAnimation

حركة أساسية لموقع الطبقة بمنحنى ease-in-out مخصص. تقبل CAMediaTimingFunction نقاط تحكم بيزيه المكعب التي تُحدد بداية ونهاية سلسة للحركة.

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 الحركة في خادم العرض بمعدل 60/120 إطاراً في الثانية.

SwiftUI: التخفيف مع timingCurve

حركة تصريحية في SwiftUI بمنحنى مخصص وتأثير زنبرك. يرتبط معدِّل الحركة بتغيير 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) يُنشئ حركة تخفيف مخصصة في SwiftUI. المعامل value يربط الحركة بحالة محددة — عندما يتغير isAnimated، يطبق SwiftUI المنحنى تلقائياً. لحركة الربيع، استخدم .spring(response: 0.4, dampingFraction: 0.6) بدلاً من timingCurve.

Android: PathInterpolator مع تخفيف مخصص

المكافئ لـ 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: التخفيف مع CubicBezierEasing

في Compose، يتم ضبط التخفيف عبر 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 يقبل نفس معاملات نقاط التحكم الأربعة. يقوم animateFloatAsState بتحليل قيمة alpha تلقائياً بالمنحنى المحدد. للمقارنة: FastOutSlowInEasing في Compose مكافئ لـ (0.4, 0.0, 0.2, 1.0) — المنحنى القياسي لـ Material Design 3. المنحنيات المخصصة عبر CubicBezierEasing تعطي تحكماً كاملاً لحركات العلامات التجارية.

الأسئلة الشائعة

ما الفرق بين التخفيف والمُداخِل (interpolator)؟

التخفيف (Easing) هو مفهوم عام لدالة التسهيل المستخدمة في iOS (CAMediaTimingFunction)، والويب (CSS ease-in-out) والتصميم. المُداخِل (Interpolator) هو تطبيق هذا المفهوم في Android (TimeInterpolator). في الجوهر هما نفس الشيء — منحنى رياضي لسرعة الحركة. الفرق فقط في الاسم و API المنصة. في المشاريع عبر المنصات، يُستخدم مصطلح التخفيف (easing) كمصطلح عالمي.

ما هي دوال التخفيف المتاحة في iOS؟

يوفر 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:).

هل يمكن استخدام نفس دالة التخفيف على iOS و Android؟

نعم، إذا تم تعيين نفس منحنى بيزيه المكعب. تقبل 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.

الخلاصة

  • التخفيف (Easing) هو دالة سرعة حركة تجعل الحركة طبيعية من خلال تغييرات غير خطية للمعاملات مع الوقت.
  • iOS CAMediaTimingFunction و SwiftUI Animation.timingCurve يطبقان التخفيف عبر منحنى بيزيه مكعب بنقطتي تحكم.
  • Android PathInterpolator و Compose CubicBezierEasing هما مكافئان كاملان لـ iOS لمنحنيات التسهيل المخصصة.
  • منحنيات التخفيف عبر المنصات تُحدد بنقاط تحكم نفسها — أساس توحيد الحركات.
  • يوصي Material Design 3 بـ fast-out-slow-in للظهور، linear-out-slow-in للاختفاء.
  • حركة الربيع في SwiftUI (Animation.spring) و Compose (spring()) تُنشئ تأثير زنبرك مادي مع dampingFraction.
  • التخفيف الخطي غير موصى به لحركات الواجهة — يبدو غير طبيعي وميكانيكي.

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا