تخصيص الصور المتحركة

تقبل العديد من واجهات برمجة التطبيقات الخاصة بالرسوم المتحركة عادةً مَعلمات لتخصيص سلوكها.

تخصيص الصور المتحركة باستخدام المَعلمة AnimationSpec

تسمح معظم واجهات برمجة تطبيقات الحركات للمطوّرين بتخصيص مواصفات الحركات باستخدام المَعلمة الاختيارية AnimationSpec.

val alpha: Float by animateFloatAsState(
    targetValue = if (enabled) 1f else 0.5f,
    // Configure the animation duration and easing.
    animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing),
    label = "alpha"
)

تتوفّر أنواع مختلفة من AnimationSpec لإنشاء أنواع مختلفة من الرسوم المتحركة.

إنشاء صور متحركة مستنِدة إلى الفيزياء باستخدام spring

تنشئ spring صورة متحركة مستندة إلى الفيزياء بين قيمتَي البدء والانتهاء. تتضمّن هذه الدالة مَعلمتَين: dampingRatio وstiffness.

تحدّد هذه السمة مدى مرونة الحركة التوافقية البسيطة.dampingRatio القيمة التلقائية هي Spring.DampingRatioNoBouncy.

الشكل 1. ضبط نسب التخميد المختلفة للنابض

تحدّد stiffness مدى سرعة حركة النابض نحو القيمة النهائية. القيمة التلقائية هي Spring.StiffnessMedium.

الشكل 2. ضبط صلابة نابض مختلفة

val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = spring(
        dampingRatio = Spring.DampingRatioHighBouncy,
        stiffness = Spring.StiffnessMedium
    ),
    label = "spring spec"
)

يمكن أن يتعامل النوع spring مع الانقطاعات بسلاسة أكبر من الأنواع المستندة إلى المدة، وذلك لأنّه يضمن استمرار السرعة عند تغيُّر القيمة المستهدَفة أثناء الصور المتحركة.AnimationSpec يتم استخدام spring كإعداد تلقائي لـ AnimationSpec من خلال العديد من واجهات برمجة التطبيقات الخاصة بالرسوم المتحركة، مثل animate*AsState وupdateTransition.

على سبيل المثال، إذا طبّقنا إعداد spring على الصورة المتحركة التالية التي يتم تشغيلها من خلال لمس المستخدم، وعند مقاطعة الصورة المتحركة أثناء تقدّمها، يمكنك ملاحظة أنّ استخدام tween لا يستجيب بسلاسة كما هو الحال عند استخدام spring.

الشكل 3. ضبط مواصفات tween مقابل spring للصور المتحركة وإيقافها

تحريك العناصر بين قيمتَي البداية والنهاية باستخدام منحنى التباطؤ والتسارع مع tween

تنشئ tween صورة متحركة بين قيمتَي البداية والنهاية خلال الفترة الزمنية المحددة durationMillis باستخدام منحنى التباطؤ والتسارع. tween هو اختصار للكلمة between - لأنّها تتوسط قيمتين.

يمكنك أيضًا تحديد delayMillis لتأجيل بدء الحركة.

val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = tween(
        durationMillis = 300,
        delayMillis = 50,
        easing = LinearOutSlowInEasing
    ),
    label = "tween delay"
)

يمكنك الاطّلاع على التخفيف لمزيد من المعلومات.

تحريك العناصر إلى قيم محدّدة في أوقات معيّنة باستخدام keyframes

يتم تحريك keyframes استنادًا إلى قيم اللقطة المحدّدة في الطوابع الزمنية المختلفة خلال مدة الحركة. في أي وقت، سيتم إدخال قيمة الصورة المتحركة بين قيمتَي إطار رئيسي. بالنسبة إلى كل إطار رئيسي من هذه الإطارات، يمكن تحديد التباطؤ والتسارع لتحديد منحنى الاستيفاء.

يمكنك تحديد القيم عند 0 ملي ثانية وعند مدة العرض. في حال عدم تحديد هذه القيم، سيتم ضبطها تلقائيًا على قيمتَي البدء والانتهاء للرسم المتحرك، على التوالي.

val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = keyframes {
        durationMillis = 375
        0.0f at 0 using LinearOutSlowInEasing // for 0-15 ms
        0.2f at 15 using FastOutLinearInEasing // for 15-75 ms
        0.4f at 75 // ms
        0.4f at 225 // ms
    },
    label = "keyframe"
)

إنشاء صور متحركة سلسة بين الإطارات الرئيسية باستخدام keyframesWithSplines

لإنشاء صورة متحركة تتبع منحنى سلسًا أثناء انتقالها بين القيم، يمكنك استخدام keyframesWithSplines بدلاً من مواصفات الصورة المتحركة keyframes.

val offset by animateOffsetAsState(
    targetValue = Offset(300f, 300f),
    animationSpec = keyframesWithSpline {
        durationMillis = 6000
        Offset(0f, 0f) at 0
        Offset(150f, 200f) atFraction 0.5f
        Offset(0f, 100f) atFraction 0.7f
    }
)

تكون إطارات المفاتيح المستندة إلى الانحناءات الملساء مفيدة بشكل خاص للحركة الثنائية الأبعاد للعناصر على الشاشة.

تعرض الفيديوهات التالية الاختلافات بين keyframes وkeyframesWithSpline مع توفّر مجموعة الإحداثيات x وy نفسها التي يجب أن تتبعها الدائرة.

keyframes keyframesWithSplines

كما تلاحظ، توفّر الإطارات الرئيسية المستندة إلى الانحناءات السلسة انتقالات أكثر سلاسة بين النقاط، لأنّها تستخدم منحنيات بيزير لتحريك العناصر بسلاسة. هذه المواصفات مفيدة لحركة مُعدّة مسبقًا. ومع ذلك، إذا كنت تعمل مع نقاط مستندة إلى المستخدم، من الأفضل استخدام النوابض لتحقيق سلاسة مماثلة بين النقاط لأنّها قابلة للمقاطعة.

تكرار صورة متحركة باستخدام repeatable

تعرض repeatable حركة تستند إلى المدة (مثل tween أو keyframes) بشكل متكرر إلى أن يتم الوصول إلى عدد التكرارات المحدّد. يمكنك تمرير المَعلمة repeatMode لتحديد ما إذا كان يجب تكرار الحركة من خلال البدء من البداية (RepeatMode.Restart) أو من النهاية (RepeatMode.Reverse).

val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = repeatable(
        iterations = 3,
        animation = tween(durationMillis = 300),
        repeatMode = RepeatMode.Reverse
    ),
    label = "repeatable spec"
)

تكرار صورة متحركة بلا حدود باستخدام infiniteRepeatable

تشبه infiniteRepeatable الدالة repeatable، ولكنها تتكرر لعدد لا نهائي من المرات.

val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = infiniteRepeatable(
        animation = tween(durationMillis = 300),
        repeatMode = RepeatMode.Reverse
    ),
    label = "infinite repeatable"
)

في الاختبارات التي تستخدم ComposeTestRule، لا يتم تشغيل الصور المتحركة التي تستخدم infiniteRepeatable. سيتم عرض المكوّن باستخدام القيمة الأولية لكل قيمة متحركة.

الانتقال فورًا إلى القيمة النهائية باستخدام snap

snap هو AnimationSpec خاص يؤدي إلى تبديل القيمة فورًا إلى القيمة النهائية. يمكنك تحديد delayMillis لتأخير بدء الحركة.

val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = snap(delayMillis = 50),
    label = "snap spec"
)

ضبط دالة تباطؤ/تسارع مخصّصة

تستخدِم عمليات AnimationSpec المستندة إلى المدة (مثل tween أو keyframes) Easing لضبط جزء من صورة متحركة. يتيح ذلك تسريع القيمة المتحركة وتبطئتها بدلاً من تحرّكها بمعدّل ثابت. الكسر هو قيمة تتراوح بين 0 (البداية) و1.0 (النهاية) وتشير إلى النقطة الحالية في الصورة المتحركة.

في الواقع، التباطؤ أو التسارع هو دالة تأخذ قيمة كسرية بين 0 و1.0 وتعرض عددًا عشريًا. يمكن أن تكون القيمة المعروضة خارج الحدود لتمثيل التجاوز أو النقص. يمكن إنشاء دالة تسوية مخصّصة مثل الرمز البرمجي أدناه.

val CustomEasing = Easing { fraction -> fraction * fraction }

@Composable
fun EasingUsage() {
    val value by animateFloatAsState(
        targetValue = 1f,
        animationSpec = tween(
            durationMillis = 300,
            easing = CustomEasing
        ),
        label = "custom easing"
    )
    // ……
}

توفّر Compose العديد من دوال Easing المضمّنة التي تغطي معظم حالات الاستخدام. يمكنك الاطّلاع على السرعة - التصميم المتوافق مع الأجهزة المختلفة للحصول على مزيد من المعلومات حول نوع التباطؤ أو التسارع الذي يجب استخدامه حسب السيناريو.

تحريك أنواع البيانات المخصّصة من خلال التحويل إلى AnimationVector والتحويل منها

تتيح معظم واجهات برمجة التطبيقات الخاصة بالرسوم المتحركة في Compose استخدام Float وColor وDp وأنواع البيانات الأساسية الأخرى كقيم للرسوم المتحركة تلقائيًا، ولكن في بعض الأحيان، تحتاج إلى تحريك أنواع بيانات أخرى، بما في ذلك الأنواع المخصّصة. أثناء الحركة، يتم تمثيل أي قيمة متحركة على شكل AnimationVector. يتم تحويل القيمة إلى AnimationVector والعكس بواسطة TwoWayConverter مطابق، ليتمكّن نظام الرسوم المتحركة الأساسي من التعامل معها بشكل موحّد. على سبيل المثال، يتم تمثيل Int كـ AnimationVector1D يحتوي على قيمة عائمة واحدة. يبدو TwoWayConverter الخاص بـ Int على النحو التالي:

val IntToVector: TwoWayConverter<Int, AnimationVector1D> =
    TwoWayConverter({ AnimationVector1D(it.toFloat()) }, { it.value.toInt() })

Color هي في الأساس مجموعة من 4 قيم، وهي الأحمر والأخضر والأزرق والشفافية، لذا يتم تحويل Color إلى AnimationVector4D يحتوي على 4 قيم عائمة. بهذه الطريقة، يتم تحويل كل نوع بيانات مستخدَم في الرسوم المتحركة إلى AnimationVector1D أو AnimationVector2D أو AnimationVector3D أو AnimationVector4D، وذلك حسب عدد أبعاده. يتيح ذلك تحريك مكوّنات مختلفة من العنصر بشكل مستقل، مع تتبُّع سرعة كل مكوّن على حدة. يمكن الوصول إلى أدوات التحويل المضمّنة لأنواع البيانات الأساسية باستخدام أدوات تحويل مثل Color.VectorConverter أو Dp.VectorConverter.

عندما تريد إتاحة نوع بيانات جديد كقيمة متحركة، يمكنك إنشاء TwoWayConverter خاص بك وتقديمه إلى واجهة برمجة التطبيقات. على سبيل المثال، يمكنك استخدام animateValueAsState لتحريك نوع البيانات المخصّص على النحو التالي:

data class MySize(val width: Dp, val height: Dp)

@Composable
fun MyAnimation(targetSize: MySize) {
    val animSize: MySize by animateValueAsState(
        targetSize,
        TwoWayConverter(
            convertToVector = { size: MySize ->
                // Extract a float value from each of the `Dp` fields.
                AnimationVector2D(size.width.value, size.height.value)
            },
            convertFromVector = { vector: AnimationVector2D ->
                MySize(vector.v1.dp, vector.v2.dp)
            }
        ),
        label = "size"
    )
}

تتضمّن القائمة التالية بعض VectorConverters المضمّنة: