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

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

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

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

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

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

ملاحظة: أثناء استخدام طُرق 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() إلى انتقال مرئي.