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

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

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

تتيح معظم Animation API للمطوّرين تخصيص مواصفات الحركة من خلال مَعلمة 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 هو اختصار لكلمة "بين"، لأنّه يقع بين قيمتَين.

يمكنك أيضًا تحديد 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 في ضوء مجموعة الإحداثيات نفسها التي يجب أن تتبعها الدائرة.

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 المضمّنة التي تغطي معظم حالات الاستخدام. يُرجى الاطّلاع على السرعة - التصميم المتعدد الأبعاد لمزيد من المعلومات حول نوع التخفيف الذي يجب استخدامه حسب السيناريو.

  • FastOutSlowInEasing
  • LinearOutSlowInEasing
  • FastOutLinearEasing
  • LinearEasing
  • CubicBezierEasing
  • عرض المزيد

تحريك أنواع البيانات المخصّصة من خلال التحويل من وإلى 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 المضمّنة: