تحريك الحركة باستخدام الفيزياء الربيعية

تجربة طريقة ComposeAllowed
Jetpack Compose هي مجموعة أدوات واجهة المستخدم التي ننصح بها لنظام التشغيل Android. تعرَّف على كيفية استخدام "الصور المتحركة" في Compose.

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

إذا كنت تريد أن تنخفض سرعة الصور المتحركة في تطبيقك في اتجاه واحد فقط، ننصحك باستخدام صورة متحركة ومستندة إلى الاحتكاك بدلاً من ذلك.

دورة حياة الصور المتحركة في فصل الربيع

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

على سبيل المثال، إذا سحبت رمز تطبيق على الشاشة ثم تركته في وقت لاحق من خلال رفع إصبعك من الرمز، سيعود الرمز إلى مكانه الأصلي بقوة غير مرئية ولكن مألوفة.

يوضح الشكل 1 تأثيرًا ربيعيًا مشابهًا. تشير علامة الجمع (+) في منتصف الدائرة إلى القوة التي يتم تطبيقها من خلال إيماءة اللمس.

إصدار الربيع
الشكل 1. تأثير إصدار الربيع

صمِّم صورًا متحركة مستوحاة من فصل الربيع

في ما يلي الخطوات العامة لإنشاء صور متحركة نابضة بالحياة لتطبيقك:

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

إضافة مكتبة الدعم

لاستخدام مكتبة الدعم المستنِدة إلى الفيزياء، يجب إضافتها إلى مشروعك على النحو التالي:

  1. افتح ملف build.gradle الخاص بوحدة تطبيقك.
  2. أضِف مكتبة الدعم إلى القسم dependencies.

    رائع

            dependencies {
                def dynamicanimation_version = '1.0.0'
                implementation "androidx.dynamicanimation:dynamicanimation:$dynamicanimation_version"
            }
            

    Kotlin

            dependencies {
                val dynamicanimation_version = "1.0.0"
                implementation("androidx.dynamicanimation:dynamicanimation:$dynamicanimation_version")
            }
            

    لعرض الإصدارات الحالية لهذه المكتبة، اطّلِع على معلومات عن الرسوم المتحركة في صفحة الإصدارات.

إنشاء صور متحركة بأسلوب الربيع

تتيح لك الفئة SpringAnimation إنشاء صورة متحركة نابضة بالحياة لكائن. لإنشاء رسم متحرك نابض بالحياة، يجب إنشاء مثيل للفئة SpringAnimation وتقديم عنصر، وخاصية الكائن الذي تريد تحريكه، وتحديد موضع ربيع نهائي اختياري تريد الاحتفاظ بالصورة المتحركة فيه.

ملاحظة: عند إنشاء صورة متحركة في الربيع، يكون الموضع النهائي للربيع اختياريًا. ومع ذلك، يجب تحديده قبل بدء تشغيل الصورة المتحركة.

Kotlin

val springAnim = findViewById<View>(R.id.imageView).let { img ->
    // Setting up a spring animation to animate the view’s translationY property with the final
    // spring position at 0.
    SpringAnimation(img, DynamicAnimation.TRANSLATION_Y, 0f)
}

Java

final View img = findViewById(R.id.imageView);
// Setting up a spring animation to animate the view’s translationY property with the final
// spring position at 0.
final SpringAnimation springAnim = new SpringAnimation(img, DynamicAnimation.TRANSLATION_Y, 0);

يمكن للصور المتحركة المستندة إلى الربيع تحريك طرق العرض على الشاشة من خلال تغيير الخصائص الفعلية في كائنات العرض. تتوفّر طرق العرض التالية في النظام:

  • ALPHA: لتمثيل شفافية ألفا في طريقة العرض وتكون القيمة 1 (غير مرئية) تلقائيًا، وتمثل القيمة 0 الشفافية الكاملة (غير مرئية).
  • TRANSLATION_X وTRANSLATION_Y وTRANSLATION_Z: تتحكّم هذه السمات في مكان العرض كدلتا من الإحداثيات والإحداثيات العلوية والارتفاع الأيسر، والتي يتم ضبطها من خلال حاوية التنسيق.
    • تصف السمة TRANSLATION_X الإحداثي الأيسر.
    • تصف السمة TRANSLATION_Y الإحداثيات العليا.
    • تصف السمة TRANSLATION_Z عمق العرض بالنسبة إلى ارتفاعه.
  • ROTATION وROTATION_X وROTATION_Y: تتحكّم هذه السمات في التدوير في الوضع الثنائي الأبعاد (السمة rotation) والوضع الثلاثي الأبعاد حول النقطة المحورية.
  • SCROLL_X وSCROLL_Y: تشير هاتان السمتان إلى إزاحة الانتقال للأعلى أو للأسفل في المصدر على اليمين والحافة العلوية بالبكسل. ويشير أيضًا إلى موضع الانتقال للأعلى أو للأسفل في الصفحة.
  • SCALE_X وSCALE_Y: تتحكّم هذه السمات في إمكانية القياس الثنائي الأبعاد لطريقة عرض حول نقطتها المحورية.
  • X وY وZ: هذه هي خصائص الأداة الأساسية التي تصف الموقع النهائي للملف الشخصي في حاويته.

تسجيل المستمعين

وتوفّر الفئة DynamicAnimation مستمعَين: OnAnimationUpdateListener وOnAnimationEndListener. يستمع هؤلاء المستمعون إلى التعديلات في الصورة المتحركة، مثلاً عند حدوث تغيير في قيمة الصورة المتحركة وعند انتهاء الصورة المتحركة.

OnAnimationUpdateListener

عندما تريد تحريك عدة ملفات شخصية لإنشاء صورة متحركة متسلسلة، يمكنك إعداد OnAnimationUpdateListener لتلقّي استدعاء في كل مرة يحدث فيها تغيير في خاصية العرض الحالي. يرسل رد الاتصال إشعارًا إلى الملف الشخصي الآخر لتعديل موضع الربيع استنادًا إلى التغيير الذي يحدث في موقع الملف الشخصي الحالي. لتسجيل المستمع، اتّبِع الخطوات التالية:

  1. وعليك استدعاء الطريقة addUpdateListener() وإرفاق المستمع بالحركة.

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

  2. يمكنك إلغاء الطريقة onAnimationUpdate() لإشعار المتصل بالتغيير في العنصر الحالي. ويوضّح نموذج الرمز التالي الاستخدام العام لـ OnAnimationUpdateListener.

Kotlin

// Setting up a spring animation to animate the view1 and view2 translationX and translationY properties
val (anim1X, anim1Y) = findViewById<View>(R.id.view1).let { view1 ->
    SpringAnimation(view1, DynamicAnimation.TRANSLATION_X) to
            SpringAnimation(view1, DynamicAnimation.TRANSLATION_Y)
}
val (anim2X, anim2Y) = findViewById<View>(R.id.view2).let { view2 ->
    SpringAnimation(view2, DynamicAnimation.TRANSLATION_X) to
            SpringAnimation(view2, DynamicAnimation.TRANSLATION_Y)
}

// Registering the update listener
anim1X.addUpdateListener { _, value, _ ->
    // Overriding the method to notify view2 about the change in the view1’s property.
    anim2X.animateToFinalPosition(value)
}

anim1Y.addUpdateListener { _, value, _ -> anim2Y.animateToFinalPosition(value) }

Java

// Creating two views to demonstrate the registration of the update listener.
final View view1 = findViewById(R.id.view1);
final View view2 = findViewById(R.id.view2);

// Setting up a spring animation to animate the view1 and view2 translationX and translationY properties
final SpringAnimation anim1X = new SpringAnimation(view1,
        DynamicAnimation.TRANSLATION_X);
final SpringAnimation anim1Y = new SpringAnimation(view1,
    DynamicAnimation.TRANSLATION_Y);
final SpringAnimation anim2X = new SpringAnimation(view2,
        DynamicAnimation.TRANSLATION_X);
final SpringAnimation anim2Y = new SpringAnimation(view2,
        DynamicAnimation.TRANSLATION_Y);

// Registering the update listener
anim1X.addUpdateListener(new DynamicAnimation.OnAnimationUpdateListener() {

// Overriding the method to notify view2 about the change in the view1’s property.
    @Override
    public void onAnimationUpdate(DynamicAnimation dynamicAnimation, float value,
                                  float velocity) {
        anim2X.animateToFinalPosition(value);
    }
});

anim1Y.addUpdateListener(new DynamicAnimation.OnAnimationUpdateListener() {

  @Override
    public void onAnimationUpdate(DynamicAnimation dynamicAnimation, float value,
                                  float velocity) {
        anim2Y.animateToFinalPosition(value);
    }
});

OnAnimationEndListener

يُعلِم OnAnimationEndListener بنهاية صورة متحركة. يمكنك إعداد المستمع لتلقّي معاودة الاتصال عند وصول الصورة المتحركة إلى التوازن أو عند إلغائها. لتسجيل المستمع، اتّبِع الخطوات التالية:

  1. وعليك استدعاء الطريقة addEndListener() وإرفاق المستمع بالحركة.
  2. يمكنك إلغاء طريقة onAnimationEnd() لتلقّي إشعار عند وصول الصورة المتحركة إلى حالة التوازن أو عند إلغائها.

إزالة المستمعين

لإيقاف تلقّي استدعاءات تحديث الصور المتحركة واستدعاءات إنهاء الصور المتحركة، يمكنك طلب الطريقتين removeUpdateListener() وremoveEndListener() على التوالي.

ضبط قيمة بداية الصورة المتحركة

لضبط قيمة بداية الصورة المتحركة، يمكنك استدعاء الطريقة setStartValue() وتمرير القيمة الأولى الخاصة بالحركة. إذا لم يتم ضبط قيمة البداية، ستستخدم الحركة القيمة الحالية لخاصية الكائن كقيمة البداية.

ضبط نطاق قيمة الرسوم المتحركة

يمكنك ضبط الحد الأدنى والأقصى لقيم الحركة عندما تريد حصر قيمة السمة على نطاق معيّن. ويساعدك ذلك أيضًا على التحكّم في النطاق في حال إضافة تأثيرات حركية إلى خصائص ذات نطاق أساسي، مثل ألفا (من 0 إلى 1).

  • لضبط الحدّ الأدنى للقيمة، عليك استدعاء الطريقة setMinValue() وتمرير الحدّ الأدنى لقيمة السمة.
  • لضبط الحدّ الأقصى للقيمة، عليك استدعاء الطريقة setMaxValue() وضبط الحد الأقصى لقيمة السمة.

تُرجع كلتا الطريقتين الرسوم المتحركة التي يتم تعيين القيمة لها.

ملاحظة: إذا ضبطت قيمة البدء وتحديد نطاق قيمة الحركة، تأكَّد من أنّ قيمة البدء ضمن نطاق القيمة الأدنى والحدّ الأقصى.

ضبط سرعة البدء

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

لضبط السرعة، عليك استدعاء الطريقة setStartVelocity() وتمرير السرعة بالبكسل في الثانية. وتُرجع الطريقة كائن القوة النابض الذي تم تعيين السرعة عليه.

ملاحظة: استخدِم طريقة الفئة GestureDetector.OnGestureListener أو VelocityTracker لاسترداد وحساب سرعة إيماءات اللمس.

Kotlin

findViewById<View>(R.id.imageView).also { img ->
    SpringAnimation(img, DynamicAnimation.TRANSLATION_Y).apply {
        …
        // Compute velocity in the unit pixel/second
        vt.computeCurrentVelocity(1000)
        val velocity = vt.yVelocity
        setStartVelocity(velocity)
    }
}

Java

final View img = findViewById(R.id.imageView);
final SpringAnimation anim = new SpringAnimation(img, DynamicAnimation.TRANSLATION_Y);
…
// Compute velocity in the unit pixel/second
vt.computeCurrentVelocity(1000);
float velocity = vt.getYVelocity();
anim.setStartVelocity(velocity);

جارٍ تحويل وحدات بكسل مستقلة الكثافة في الثانية إلى وحدات بكسل في الثانية

يجب أن تكون سرعة النابض بالبكسل في الثانية. إذا اخترت تقديم قيمة ثابتة كبداية للسرعة، قدِّم القيمة بالبكسل المستقل في الثانية ثم حوِّلها إلى وحدات بكسل في الثانية. للإحالة الناجحة، استخدِم الطريقة applyDimension() من الفئة TypedValue. راجِع نموذج الرمز التالي:

Kotlin

val pixelPerSecond: Float =
    TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpPerSecond, resources.displayMetrics)

Java

float pixelPerSecond = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpPerSecond, getResources().getDisplayMetrics());

ضبط خصائص الربيع

وتحدّد الفئة SpringForce طريقة دالة getter وطُرق الإعداد لكل خاصية من خصائص النابض، مثل نسبة التخميد والصلابة. لضبط سمات الربيع، من المهم إما استرداد كائن قوة النوابض أو إنشاء قوة نبرة مخصّصة يمكنك ضبط السمات عليها. لمزيد من المعلومات حول إنشاء قوة نوابض مخصصة، راجِع القسم إنشاء قوة نوابض مخصصة.

ملاحظة: أثناء استخدام طرق setter، يمكنك إنشاء سلسلة طريقة لأنّ جميع طرق setter تُرجع كائن قوة النابض.

نسبة التخميد

تصف نسبة التخميد انخفاضًا تدريجيًا في اهتزاز الربيع. وباستخدام نسبة التخميد، يمكنك تحديد مدى سرعة تناقص الاهتزازات من ارتداد إلى آخر. هناك أربع طرق مختلفة لتخميد زنبرك:

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

لإضافة نسبة التخميد إلى الربيع، نفِّذ الخطوات التالية:

  1. وعليك طلب الطريقة getSpring() لاسترداد الربيع لإضافة نسبة التخميد.
  2. استدعِ الطريقة setDampingRatio() وعدِّل نسبة التخميد التي تريد إضافتها إلى الربيع. وتُرجع الطريقة كائن قوة النوابض الذي تم تعيين نسبة التخميد عليه.

    ملاحظة: يجب أن تكون نسبة التخميد رقمًا غير سالب. في حال ضبط نسبة التخميد على صفر، لن يصل الربيع أبدًا إلى موضع الراحة. بمعنى آخر، هذا الجهاز يتأرجح إلى الأبد.

تتوفّر ثوابت نسبة التخميد التالية في النظام:

الشكل 2: ارتداد مرتفع

الشكل 3: ارتداد متوسط

الشكل 4: مستوى ارتداد منخفض

الشكل 5: ما مِن ارتداد

تم ضبط نسبة التخميد التلقائية على DAMPING_RATIO_MEDIUM_BOUNCY.

Kotlin

findViewById<View>(R.id.imageView).also { img ->
    SpringAnimation(img, DynamicAnimation.TRANSLATION_Y).apply {
        …
        // Setting the damping ratio to create a low bouncing effect.
        spring.dampingRatio = SpringForce.DAMPING_RATIO_LOW_BOUNCY
        …
    }
}

Java

final View img = findViewById(R.id.imageView);
final SpringAnimation anim = new SpringAnimation(img, DynamicAnimation.TRANSLATION_Y);
…
// Setting the damping ratio to create a low bouncing effect.
anim.getSpring().setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
…

تيبس

يحدد الصلب ثابت الربيع، الذي يقيس قوة الربيع. يستخدم النوابض الصلب المزيد من القوة على الجسم المتصل عندما لا يكون النبع في موضع الراحة. لإضافة الصلابة إلى الربيع، نفِّذ الخطوات التالية:

  1. استدعِ الطريقة getSpring() لاسترداد الربيع لإضافة الصلابة.
  2. استدعِ الطريقة setStiffness() وأدخِل قيمة الصلابة التي تريد إضافتها إلى عنصر الربيع. وتُرجع الطريقة كائن القوة الربيعية الذي تم تثبيت الصلابة عليه.

    ملاحظة: يجب أن تكون قيمة الصلابة رقمًا موجبًا.

تتوفر ثوابت الصلابة التالية في النظام:

الشكل 6: صلابة عالية

الشكل 7: صلابة متوسطة

الشكل 8: تيبس منخفض

الشكل 9: تيبس منخفض جدًا

تم ضبط درجة الصلابة التلقائية على STIFFNESS_MEDIUM.

Kotlin

findViewById<View>(R.id.imageView).also { img ->
    SpringAnimation(img, DynamicAnimation.TRANSLATION_Y).apply {
        …
        // Setting the spring with a low stiffness.
        spring.stiffness = SpringForce.STIFFNESS_LOW
        …
    }
}

Java

final View img = findViewById(R.id.imageView);
final SpringAnimation anim = new SpringAnimation(img, DynamicAnimation.TRANSLATION_Y);
…
// Setting the spring with a low stiffness.
anim.getSpring().setStiffness(SpringForce.STIFFNESS_LOW);
…

أنشِئ قوة زنبركية مخصّصة

يمكنك إنشاء قوة نوابض مخصَّصة كبديل لاستخدام قوة النوابض التلقائية. تتيح لك قوة النوابض المخصصة مشاركة مثيل قوة الربيع نفسه عبر العديد من صور الربيع المتحركة. بعد إنشاء قوة النابض، يمكنك ضبط خصائص مثل نسبة التخميد والصلابة.

  1. أنشئ عنصر SpringForce.

    SpringForce force = new SpringForce();

  2. عليك تعيين السمات من خلال استدعاء الطرق المعنية. يمكنك أيضًا إنشاء سلسلة طرق.

    force.setDampingRatio(DAMPING_RATIO_LOW_BOUNCY).setStiffness(STIFFNESS_LOW);

  3. ويمكنك استدعِ الطريقة setSpring() لضبط نقطة النابض بالحركة.

    setSpring(force);

بدء الصورة المتحركة

هناك طريقتان يمكنك من خلالهما إنشاء صورة متحركة الربيع: من خلال طلب start() أو باستخدام طريقة animateToFinalPosition(). يجب طلب كلتا الطريقتين في سلسلة التعليمات الرئيسية.

تؤدي طريقة animateToFinalPosition() إلى تنفيذ مهمتَين:

  • لضبط الموضع النهائي للزنبرك.
  • يؤدي إلى بدء تشغيل الصورة المتحركة في حال لم تكن قد بدأت.

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

يوضّح الشكل 10 صورة متحركة متسلسلة متسلسلة تعتمد على عرض آخر.

عرض توضيحي لسلسلة زنبركية
الشكل 10. عرض توضيحي لسلسلة زنبركية

لاستخدام الطريقة animateToFinalPosition()، يمكنك استدعاء الطريقة animateToFinalPosition() وتمرير بقية موضع النابض. يمكنك أيضًا ضبط الموضع المتبقي للزنبرك من خلال طلب الطريقة setFinalPosition().

لا تضبط الطريقة start() قيمة السمة على قيمة البداية على الفور. تتغير قيمة السمة عند كل نبضة متحرّكة، وهو ما يحدث قبل ممر الرسم. ونتيجةً لذلك، تظهر التغييرات في الإطار التالي، كما لو تم ضبط القيم على الفور.

Kotlin

findViewById<View>(R.id.imageView).also { img ->
    SpringAnimation(img, DynamicAnimation.TRANSLATION_Y).apply {
        …
        // Starting the animation
        start()
        …
    }
}

Java

final View img = findViewById(R.id.imageView);
final SpringAnimation anim = new SpringAnimation(img, DynamicAnimation.TRANSLATION_Y);
…
// Starting the animation
anim.start();
…

إلغاء الصورة المتحركة

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

هناك طريقتان يمكنك استخدامهما لإنهاء الحركة. تؤدي الطريقة cancel() إلى إنهاء الحركة بالقيمة حيث تكون عليها. وتعمل الطريقة skipToEnd() على تخطّي الحركة إلى القيمة النهائية ثم إنهاؤها.

قبل إنهاء الصورة المتحركة، من المهم التحقق أولاً من حالة الربيع. وإذا كانت الحالة غير مثبَّتة، لن تصل الصورة المتحركة أبدًا إلى الموضع المتبقي. لمعرفة حالة الربيع، يمكنك طلب طريقة canSkipToEnd(). إذا تم تفريغ الربيع، ستعرض الطريقة true، أو تعرض false.

بعد معرفة حالة الربيع، يمكنك إنهاء الصورة المتحركة باستخدام طريقة skipToEnd() أو cancel(). ويجب استدعاء طريقة cancel() في سلسلة المحادثات الرئيسية فقط.

ملاحظة: بشكل عام، تؤدي الطريقة skipToEnd() إلى حدوث انتقال مرئي.