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

تجربة طريقة الإنشاء
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 طريقتي التربيع والتربيع لكل خاصية من خصائص النوابض، مثل نسبة التخميد والصلابة. لضبط خصائص النابض، من المهم استرداد كائن قوة النابض أو إنشاء قوة زنبركية مخصّصة يمكنك ضبط الخصائص من خلالها. لمزيد من المعلومات حول إنشاء قوة زنبركية مخصّصة، راجِع القسم إنشاء قوة زنبركية مخصّصة.

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

نسبة التخميد

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

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

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

  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() إلى حدوث قفزة بصرية.