الإصدار 03.2 من نظام التشغيل Android المتقدّم: صور متحركة باستخدام MotionLayout

1. قبل البدء

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

MotionLayout هي مكتبة تتيح لك إضافة رسومات متحركة غنية إلى تطبيق Android. وهي تستند إلى ConstraintLayout, وتتيح لك تحريك أي عنصر يمكنك إنشاؤه باستخدام ConstraintLayout.

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

تُعدّ الرسوم المتحركة طريقة رائعة لتحسين تجربة استخدام التطبيق. يمكنك استخدام الصور المتحركة من أجل:

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

المتطلبات الأساسية

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

  • معرفة كيفية إنشاء تطبيق يتضمّن نشاطًا وتصميمًا أساسيًا وتشغيله على جهاز أو محاكي باستخدام "استوديو Android" التعرّف على ConstraintLayout يمكنك الاطّلاع على درس تطبيقي حول الترميز حول Constraint Layout لمعرفة المزيد عن ConstraintLayout.

المهام التي ستنفذها

  • تحديد صورة متحركة باستخدام ConstraintSets وMotionLayout
  • تحريك العناصر استنادًا إلى أحداث السحب
  • تغيير الصورة المتحركة باستخدام KeyPosition
  • تغيير السمات باستخدام KeyAttribute
  • تشغيل الصور المتحركة باستخدام الرموز البرمجية
  • إضافة تأثيرات متحركة إلى العناوين القابلة للتصغير باستخدام MotionLayout

المتطلبات

2. البدء

لتنزيل نموذج التطبيق، يمكنك إجراء أحد الإجراءَين التاليَين:

... أو استنسِخ مستودع GitHub من سطر الأوامر باستخدام الأمر التالي:

$ git clone https://github.com/googlecodelabs/motionlayout.git

3- إنشاء صور متحركة باستخدام MotionLayout

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

لإنشاء صورة متحركة من الرمز الأولي، ستحتاج إلى الأجزاء الرئيسية التالية:

  • MotionLayout, هو فئة فرعية من ConstraintLayout. يمكنك تحديد جميع طرق العرض التي سيتم تحريكها داخل العلامة MotionLayout.
  • MotionScene, وهو ملف XML يصف صورة متحركة لـ MotionLayout.
  • Transition, هو جزء من MotionScene يحدّد مدة الحركة، ومشغّلها، وكيفية نقل طرق العرض.
  • ConstraintSet تحدّد قيود البداية والنهاية للانتقال.

لنلقِ نظرة على كل منها على التوالي، بدءًا من MotionLayout.

الخطوة 1: استكشاف الرمز الحالي

MotionLayout هي فئة فرعية من ConstraintLayout، لذا فهي تتيح استخدام جميع الميزات نفسها مع إضافة الرسوم المتحركة. لاستخدام MotionLayout، عليك إضافة طريقة عرض MotionLayout حيث تستخدم ConstraintLayout..

  1. في res/layout، افتح activity_step1.xml.. هنا لديك ConstraintLayout مع ImageView واحدة لنجمة، مع تطبيق لون داخلها.

activity_step1.xml

<!-- initial code -->
<androidx.constraintlayout.widget.ConstraintLayout
       ...
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       >

   <ImageView
           android:id="@+id/red_star"
           ...
   />

</androidx.constraintlayout.motion.widget.MotionLayout>

لا يتضمّن هذا ConstraintLayout أي قيود، لذا إذا شغّلت التطبيق الآن، ستظهر النجمة بدون قيود، ما يعني أنّها ستكون موضوعة في موقع جغرافي غير معروف. سيصدر &quot;استوديو Android&quot; تحذيرًا بشأن عدم توفّر قيود.

الخطوة 2: التحويل إلى Motion Layout

لإنشاء رسوم متحركة باستخدام MotionLayout,، عليك تحويل ConstraintLayout إلى MotionLayout.

لكي يستخدم التصميم مشهدًا متحركًا، يجب أن يشير إليه.

  1. لإجراء ذلك، افتح مساحة التصميم. في "استوديو Android" 4.0، يمكنك فتح سطح التصميم باستخدام رمز التقسيم أو التصميم في أعلى يسار الشاشة عند عرض ملف XML للتصميم.

a2beea710c2decb7.png

  1. بعد فتح سطح التصميم، انقر بزر الماوس الأيمن على المعاينة واختَر التحويل إلى MotionLayout.

4fa936a98a8393b9.png

يؤدي ذلك إلى استبدال العلامة ConstraintLayout بالعلامة MotionLayout وإضافة motion:layoutDescription إلى العلامة MotionLayout يشير إلى @xml/activity_step1_scene.

activity_step1**.xml**

<!-- explore motion:layoutDescription="@xml/activity_step1_scene" -->
<androidx.constraintlayout.motion.widget.MotionLayout
       ...
       motion:layoutDescription="@xml/activity_step1_scene">

مشهد الحركة هو ملف XML واحد يصف صورة متحركة في MotionLayout.

بمجرد التحويل إلى MotionLayout، ستعرض مساحة التصميم "أداة تعديل الصور المتحركة".

66d0e80d5ab4daf8.png

تتضمّن "أداة تعديل الصور المتحركة" ثلاثة عناصر جديدة في واجهة المستخدِم:

  1. نظرة عامة: هذا هو خيار النافذة المنبثقة الذي يتيح لك اختيار أجزاء مختلفة من الصورة المتحركة. في هذه الصورة، تم اختيار start ConstraintSet. يمكنك أيضًا اختيار الانتقال بين start وend من خلال النقر على السهم بينهما.
  2. القسم: أسفل النظرة العامة، تظهر نافذة قسم تتغيّر استنادًا إلى عنصر النظرة العامة المحدّد حاليًا. في هذه الصورة، يتم عرض معلومات start ConstraintSet في نافذة الاختيار.
  3. السمة: تعرض لوحة السمات سمات العنصر المحدّد حاليًا وتتيح لك تعديلها من نافذة النظرة العامة أو نافذة التحديد. في هذه الصورة، يتم عرض سمات start ConstraintSet.

الخطوة 3: تحديد قيود البدء والانتهاء

يمكن تحديد جميع الحركات من حيث البداية والنهاية. يصف البدء شكل الشاشة قبل الحركة، ويصف النهاية شكل الشاشة بعد اكتمال الحركة. MotionLayout مسؤولة عن تحديد كيفية إنشاء صورة متحركة بين حالتَي البداية والنهاية (بمرور الوقت).

تستخدم MotionScene علامة ConstraintSet لتحديد حالتي البدء والانتهاء. ConstraintSet هو ما يبدو عليه، أي مجموعة من القيود التي يمكن تطبيقها على طرق العرض. ويشمل ذلك قيود العرض والارتفاع وConstraintLayout. يتضمّن أيضًا بعض السمات، مثل alpha. ولا يحتوي على طرق العرض نفسها، بل على القيود المفروضة على طرق العرض هذه فقط.

ستتجاوز أي قيود محدّدة في ConstraintSet القيود المحدّدة في ملف التصميم. إذا حدّدت قيودًا في كلّ من التصميم وMotionScene، سيتم تطبيق القيود في MotionScene فقط.

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

يمكنك إكمال هذه الخطوة باستخدام "أداة تعديل الصور المتحركة" أو من خلال تعديل نص activity_step1_scene.xml مباشرةً.

  1. اختَر start ConstraintSet في لوحة النظرة العامة

6e57661ed358b860.png

  1. في لوحة الاختيار، انقر على red_star. تعرض حاليًا "المصدر" layout، ما يعني أنّه غير مقيّد في هذا ConstraintSet. استخدِم رمز القلم الرصاص في أعلى اليسار لإنشاء قيد.

f9564c574b86ea8.gif

  1. تأكَّد من أنّ red_star تعرض مصدر start عند اختيار start ConstraintSet في لوحة النظرة العامة.
  2. في لوحة "السمات" (Attributes)، مع تحديد red_star في start ConstraintSet، أضِف قيدًا (Constraint) في الأعلى وابدأ بالنقر على أزرار + الزرقاء.

2fce076cd7b04bd.png

  1. افتح xml/activity_step1_scene.xml للاطّلاع على الرمز الذي أنشأته أداة تعديل الصور المتحركة لهذا القيد.

activity_step1_scene.xml

<!-- Constraints to apply at the start of the animation -->
<ConstraintSet android:id="@+id/start">
   <Constraint
           android:id="@+id/red_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           motion:layout_constraintStart_toStartOf="parent"
           motion:layout_constraintTop_toTopOf="parent" />
</ConstraintSet>

يحتوي ConstraintSet على id بقيمة @id/start، ويحدّد جميع القيود التي يجب تطبيقها على جميع طرق العرض في MotionLayout. بما أنّ هذا MotionLayout يتضمّن طريقة عرض واحدة فقط، يحتاج إلى Constraint واحد فقط.

تحدّد Constraint داخل ConstraintSet رقم تعريف طريقة العرض التي يتم تقييدها، ويتم تحديد @id/red_star في activity_step1.xml. من المهم ملاحظة أنّ علامات Constraint تحدّد القيود ومعلومات التنسيق فقط. لا تعرف العلامة Constraint أنّه يتم تطبيقها على ImageView.

يحدّد هذا القيد الارتفاع والعرض والقيدَين الآخرَين اللازمَين لتقييد عرض red_star ببداية الجزء العلوي من العنصر الأصل.

  1. اختَر end ConstraintSet في لوحة "نظرة عامة".

346e1248639b6f1e.png

  1. اتّبِع الخطوات نفسها التي اتّبعتها من قبل لإضافة Constraint إلى red_star في end ConstraintSet.
  2. لاستخدام "أداة تعديل الصور المتحركة" لإكمال هذه الخطوة، أضِف قيدًا إلى bottom وend من خلال النقر على الزرَّين الأزرقَين +.

fd33c779ff83c80a.png

  1. يبدو الرمز في XML على النحو التالي:

activitiy_step1_scene.xml

<!-- Constraints to apply at the end of the animation -->
<ConstraintSet android:id="@+id/end">
   <Constraint
           android:id="@+id/red_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           motion:layout_constraintEnd_toEndOf="parent"
           motion:layout_constraintBottom_toBottomOf="parent" />
</ConstraintSet>

تمامًا مثل @id/start، يحتوي ConstraintSet هذا على Constraint واحد على @id/red_star. في هذه المرة، يتم تحديد موضعها في أسفل الشاشة.

ليس عليك تسميتهما @id/start و@id/end، ولكن من المفيد فعل ذلك.

الخطوة 4: تحديد انتقال

يجب أن يتضمّن كل MotionScene انتقالًا واحدًا على الأقل. يحدّد الانتقال كل جزء من صورة متحركة واحدة، من البداية إلى النهاية.

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

  1. أنشأ "أداة تعديل الصور المتحركة" عملية انتقال تلقائيًا عند إنشاء ملف MotionScene. افتح activity_step1_scene.xml للاطّلاع على الانتقال الذي تم إنشاؤه.

activity_step1_scene.xml

<!-- A transition describes an animation via start and end state -->
<Transition
   motion:constraintSetEnd="@+id/end"
   motion:constraintSetStart="@id/start"
   motion:duration="1000">
  <KeyFrameSet>
  </KeyFrameSet>
</Transition>

هذه هي كل العناصر التي يحتاجها MotionLayout لإنشاء صورة متحركة. في ما يلي نظرة على كل سمة:

  • سيتم تطبيق constraintSetStart على المشاهدات عند بدء الصورة المتحركة.
  • سيتم تطبيق constraintSetEnd على طرق العرض في نهاية الحركة.
  • تحدّد السمة duration المدة التي يجب أن تستغرقها الحركة بالملي ثانية.

ستحدّد MotionLayout بعد ذلك مسارًا بين قيود البدء والانتهاء وتحرّكه للمدة المحدّدة.

الخطوة 5: معاينة الصورة المتحركة في "أداة تعديل الصور المتحركة"

dff9ecdc1f4a0740.gif

صورة متحركة: فيديو يعرض معاينة انتقال في "أداة تعديل الصور المتحركة"

  1. افتح "أداة تعديل الصور المتحركة" (Motion Editor) واختَر الانتقال من خلال النقر على السهم بين start وend في لوحة النظرة العامة.

1dc541ae8c43b250.png

  1. تعرض لوحة الاختيار عناصر التحكّم في التشغيل وشريط التقديم السريع عند اختيار انتقال. انقر على "تشغيل" أو اسحب الموضع الحالي لمعاينة الحركة.

a0fd2593384dfb36.png

الخطوة 6: إضافة معالج عند النقر

يجب أن يكون لديك طريقة لبدء الصورة المتحركة. إحدى طرق إجراء ذلك هي جعل MotionLayout يستجيب لأحداث النقر على @id/red_star.

  1. افتح "أداة تعديل الحركة" (Motion editor) واختَر الانتقال من خلال النقر على السهم بين البداية والنهاية في لوحة النظرة العامة.

b6f94b344ce65290.png

  1. انقر على 699f7ae04024ccf6.png إنشاء معالج النقر أو التمرير السريع في شريط الأدوات للوحة النظرة العامة . يضيف هذا الإجراء معالجًا سيبدأ عملية نقل.
  2. اختَر معالج النقرات من النافذة المنبثقة

ccf92d06335105fe.png

  1. غيِّر مرات الظهور إلى النقرات إلى red_star.

b0d3f0c970604f01.png

  1. انقر على إضافة، ويتم تمثيل معالج النقرات بنقطة صغيرة في "أداة تعديل الصور المتحركة".

cec3913e67fb4105.png

  1. مع تحديد الانتقال في لوحة النظرة العامة، أضِف السمة clickAction بالقيمة toggle إلى معالج OnClick الذي أضفته للتو في لوحة السمات.

9af6fc60673d093d.png

  1. افتح activity_step1_scene.xml للاطّلاع على الرمز الذي أنشأته أداة تعديل الصور المتحركة

activity_step1_scene.xml

<!-- A transition describes an animation via start and end state -->
<Transition
    motion:constraintSetStart="@+id/start"
    motion:constraintSetEnd="@+id/end"
    motion:duration="1000">
    <!-- MotionLayout will handle clicks on @id/red_star to "toggle" the animation between the start and end -->
    <OnClick
        motion:targetId="@id/red_star"
        motion:clickAction="toggle" />
</Transition>

يطلب الرمز Transition من MotionLayout تشغيل الصورة المتحركة استجابةً لأحداث النقر باستخدام العلامة <OnClick>. في ما يلي نظرة على كل سمة:

  • targetId هي طريقة العرض التي يجب مراقبة النقرات فيها.
  • سيؤدي النقر على clickAction من toggle إلى التبديل بين حالتَي البدء والانتهاء. يمكنك الاطّلاع على خيارات أخرى لـ clickAction في المستندات.
  1. شغِّل الرمز، وانقر على الخطوة 1، ثم انقر على النجمة الحمراء وشاهِد الصورة المتحركة.

الخطوة 5: الصور المتحركة في العمل

شغِّل التطبيق. من المفترض أن ترى الحركة تعمل عند النقر على النجمة.

7ba88af963fdfe10.gif

يحدّد ملف مشهد الحركة المكتمل Transition واحدًا يشير إلى ConstraintSet بداية ونهاية.

في بداية الصورة المتحركة (@id/start)، يتم ربط رمز النجمة ببداية أعلى الشاشة. في نهاية الصورة المتحركة (@id/end)، يتم ربط رمز النجمة بالجزء السفلي من الشاشة.

<?xml version="1.0" encoding="utf-8"?>

<!-- Describe the animation for activity_step1.xml -->
<MotionScene xmlns:app="http://schemas.android.com/apk/res-auto"
            xmlns:android="http://schemas.android.com/apk/res/android">
   <!-- A transition describes an animation via start and end state -->
   <Transition
           motion:constraintSetStart="@+id/start"
           motion:constraintSetEnd="@+id/end"
           motion:duration="1000">
       <!-- MotionLayout will handle clicks on @id/star to "toggle" the animation between the start and end -->
       <OnClick
               motion:targetId="@id/red_star"
               motion:clickAction="toggle" />
   </Transition>

   <!-- Constraints to apply at the end of the animation -->
   <ConstraintSet android:id="@+id/start">
       <Constraint
               android:id="@+id/red_star"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               motion:layout_constraintStart_toStartOf="parent"
               motion:layout_constraintTop_toTopOf="parent" />
   </ConstraintSet>

   <!-- Constraints to apply at the end of the animation -->
   <ConstraintSet android:id="@+id/end">
       <Constraint
               android:id="@+id/red_star"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               motion:layout_constraintEnd_toEndOf="parent"
               motion:layout_constraintBottom_toBottomOf="parent" />
   </ConstraintSet>
</MotionScene>

4. تحريك العناصر استنادًا إلى أحداث السحب

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

الخطوة 1: فحص الرمز الأوّلي

  1. للبدء، افتح ملف التصميم activity_step2.xml الذي يحتوي على MotionLayout حالي. ألقِ نظرة على الرمز.

activity_step2.xml

<!-- initial code -->

<androidx.constraintlayout.motion.widget.MotionLayout
       ...
       motion:layoutDescription="@xml/step2" >

   <ImageView
           android:id="@+id/left_star"
           ...
   />

   <ImageView
           android:id="@+id/right_star"
           ...
   />

   <ImageView
           android:id="@+id/red_star"
           ...
   />

   <TextView
           android:id="@+id/credits"
           ...
           motion:layout_constraintTop_toTopOf="parent"
           motion:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.motion.widget.MotionLayout>

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

تتضمّن TextView قيودًا، لأنّها تبقى في الموضع نفسه طوال مدة الحركة ولا تعدّل أي سمات.

الخطوة 2: تحريك المشهد

كما هو الحال في الصورة المتحركة الأخيرة، سيتم تحديد الصورة المتحركة من خلال ConstraintSet, بداية ونهاية وTransition.

تحديد ConstraintSet للبدء

  1. افتح مشهد الحركة xml/step2.xml لتحديد الصورة المتحركة.
  2. أضِف شروطًا لشرط البدء start. في البداية، تكون النجوم الثلاثة في منتصف أسفل الشاشة. تحتوي النجمتان اليمنى واليسرى على القيمة alpha التي تساوي 0.0، ما يعني أنّهما شفافتان تمامًا ومخفيتان.

step2.xml

<!-- TODO apply starting constraints -->

<!-- Constraints to apply at the start of the animation -->
<ConstraintSet android:id="@+id/start">
   <Constraint
           android:id="@+id/red_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           motion:layout_constraintStart_toStartOf="parent"
           motion:layout_constraintEnd_toEndOf="parent"
           motion:layout_constraintBottom_toBottomOf="parent" />

   <Constraint
           android:id="@+id/left_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:alpha="0.0"
           motion:layout_constraintStart_toStartOf="parent"
           motion:layout_constraintEnd_toEndOf="parent"
           motion:layout_constraintBottom_toBottomOf="parent" />

   <Constraint
           android:id="@+id/right_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:alpha="0.0"
           motion:layout_constraintStart_toStartOf="parent"
           motion:layout_constraintEnd_toEndOf="parent"
           motion:layout_constraintBottom_toBottomOf="parent" />
</ConstraintSet>

في هذا ConstraintSet، يمكنك تحديد Constraint واحد لكل نجمة. سيتم تطبيق كل قيد من خلال MotionLayout في بداية الحركة.

يتم توسيط كل عرض نجمة في أسفل الشاشة باستخدام قيود البدء والنهاية والأسفل. يحتوي النجمان @id/left_star و@id/right_star على قيمة ألفا إضافية تجعلهما غير مرئيين وسيتم تطبيقها في بداية الرسوم المتحركة.

تحدّد مجموعتا قيود start وend بداية الحركة ونهايتها. سيؤدي قيد البدء، مثل motion:layout_constraintStart_toStartOf، إلى حصر بداية أحد العروض ببداية عرض آخر. قد يكون ذلك مربكًا في البداية، لأنّ الاسم start يُستخدم لكل من و، ويتم استخدام كليهما في سياق القيود. للمساعدة في توضيح الفرق، يشير start في layout_constraintStart إلى "بداية" العرض، أي الجانب الأيسر في اللغات التي تُكتب من اليسار إلى اليمين والجانب الأيمن في اللغات التي تُكتب من اليمين إلى اليسار. تشير مجموعة قيود start إلى بداية الحركة.

تحديد ConstraintSet النهائي

  1. حدِّد قيد النهاية لاستخدام سلسلة لتحديد موضع النجوم الثلاثة معًا أسفل @id/credits. بالإضافة إلى ذلك، سيتم ضبط القيمة النهائية alpha للنجمتين اليسرى واليمنى على 1.0.

step2.xml

<!-- TODO apply ending constraints -->

<!-- Constraints to apply at the end of the animation -->
<ConstraintSet android:id="@+id/end">

   <Constraint
           android:id="@+id/left_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:alpha="1.0"
           motion:layout_constraintHorizontal_chainStyle="packed"
           motion:layout_constraintStart_toStartOf="parent"
           motion:layout_constraintEnd_toStartOf="@id/red_star"
           motion:layout_constraintTop_toBottomOf="@id/credits" />

   <Constraint
           android:id="@+id/red_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           motion:layout_constraintStart_toEndOf="@id/left_star"
           motion:layout_constraintEnd_toStartOf="@id/right_star"
           motion:layout_constraintTop_toBottomOf="@id/credits" />

   <Constraint
           android:id="@+id/right_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:alpha="1.0"
           motion:layout_constraintStart_toEndOf="@id/red_star"
           motion:layout_constraintEnd_toEndOf="parent"
           motion:layout_constraintTop_toBottomOf="@id/credits" />
</ConstraintSet>

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

بالإضافة إلى ذلك، بما أنّه تم ضبط السمة alpha على @id/right_start و@id/left_star في كلتا الحالتين ConstraintSets، ستتلاشى كلتا طريقتَي العرض تدريجيًا مع تقدّم الحركة.

تحريك العناصر استنادًا إلى تمرير المستخدم

يمكن MotionLayout تتبُّع أحداث السحب التي ينفّذها المستخدم أو التمرير السريع لإنشاء حركة "الرمي" المستندة إلى الفيزياء. وهذا يعني أنّ طرق العرض ستستمر في التحرك إذا حرّكها المستخدم بسرعة، وستتباطأ كما لو كان المستخدم يحرّك جسمًا ماديًا على سطح. يمكنك إضافة هذا النوع من الرسوم المتحركة باستخدام علامة OnSwipe في Transition.

  1. استبدِل TODO بإضافة علامة OnSwipe بعلامة <OnSwipe motion:touchAnchorId="@id/red_star" />.

step2.xml

<!-- TODO add OnSwipe tag -->

<!-- A transition describes an animation via start and end state -->
<Transition
       motion:constraintSetStart="@+id/start"
       motion:constraintSetEnd="@+id/end">
   <!-- MotionLayout will track swipes relative to this view -->
   <OnSwipe motion:touchAnchorId="@id/red_star" />
</Transition>

تحتوي OnSwipe على بعض السمات، وأهمها touchAnchorId.

  • touchAnchorId هي طريقة العرض التي يتم تتبُّعها وتتحرّك استجابةً للمس. ستبقي MotionLayout هذا العرض على المسافة نفسها من الإصبع الذي يتم التمرير به.
  • تحدّد touchAnchorSide الجانب الذي يجب تتبُّعه من طريقة العرض. هذا مهم للعناصر التي يتم تغيير حجمها أو تتبّع مسارات معقّدة أو التي يتحرّك أحد جوانبها بشكل أسرع من الجانب الآخر.
  • تحدّد dragDirection الاتجاه الذي يهمّ لهذه الصورة المتحركة (للأعلى أو للأسفل أو لليمين أو لليسار).

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

للتجربة:

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

fefcdd690a0dcaec.gif

يمكن MotionLayout إنشاء رسوم متحركة بين تصاميم مختلفة جدًا باستخدام الميزات من ConstraintLayout لإنشاء تأثيرات غنية.

في هذه الصورة المتحركة، يتم وضع جميع طرق العرض الثلاثة بالنسبة إلى العنصر الرئيسي في أسفل الشاشة في البداية. في النهاية، يتم وضع طرق العرض الثلاثة بالنسبة إلى @id/credits في سلسلة.

على الرغم من اختلاف التصاميم بشكل كبير، ستنشئ MotionLayout حركة سلسة بين البداية والنهاية.

5- تعديل مسار

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

الخطوة 1: استكشاف الرمز الحالي

  1. افتح layout/activity_step3.xml وxml/step3.xml للاطّلاع على التصميم الحالي ومشهد الحركة. تعرض ImageView وTextView القمر ونص الاعتمادات.
  2. افتح ملف مشهد الحركة (xml/step3.xml). ستلاحظ أنّه تم تحديد Transition من @id/start إلى @id/end. تحرّك الصورة المتحركة صورة القمر من أسفل يسار الشاشة إلى أسفل يمين الشاشة باستخدام ConstraintSets. يظهر نص الاعتمادات تدريجيًا من alpha="0.0" إلى alpha="1.0" أثناء تحرّك القمر.
  3. شغِّل التطبيق الآن وانقر على الخطوة 3. ستلاحظ أنّ القمر يسير في مسار خطي (أو خط مستقيم) من البداية إلى النهاية عند النقر على القمر.

الخطوة 2: تفعيل تصحيح أخطاء المسار

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

للمساعدة في تطوير صور متحركة معقّدة باستخدام MotionLayout، يمكنك رسم مسار الحركة لكل طريقة عرض. يكون ذلك مفيدًا عندما تريد عرض الرسوم المتحركة، ولضبط التفاصيل الصغيرة للحركة بدقة.

  1. لتفعيل مسارات تصحيح الأخطاء، افتح layout/activity_step3.xml وأضِف motion:motionDebug="SHOW_PATH" إلى العلامة MotionLayout.

activity_step3.xml

<!-- Add motion:motionDebug="SHOW_PATH" -->

<androidx.constraintlayout.motion.widget.MotionLayout
       ...
       motion:motionDebug="SHOW_PATH" >

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

23bbb604f456f65c.png

  • تمثّل الدوائر موضع البدء أو الانتهاء لأحد العروض.
  • تمثّل الخطوط مسار عرض واحد.
  • تمثّل الماس KeyPosition يعدّل المسار.

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

الخطوة 3: تعديل مسار

يتم تحديد جميع الحركات في MotionLayout من خلال ConstraintSet بداية ونهاية تحدّدان شكل الشاشة قبل بدء الحركة وبعد انتهائها. تنشئ الدالة MotionLayout تلقائيًا مسارًا خطيًا (خطًا مستقيمًا) بين موضعَي البداية والنهاية لكل طريقة عرض يتغيّر موضعها.

لإنشاء مسارات معقّدة مثل قوس القمر في هذا المثال، تستخدم MotionLayout KeyPosition لتعديل المسار الذي يتّخذه العرض بين البداية والنهاية.

  1. افتح xml/step3.xml وأضِف KeyPosition إلى المشهد. يتم وضع العلامة KeyPosition داخل العلامة Transition.

eae4dae9a12d0410.png

step3.xml

<!-- TODO: Add KeyFrameSet and KeyPosition -->
<KeyFrameSet>
   <KeyPosition
           motion:framePosition="50"
           motion:motionTarget="@id/moon"
           motion:keyPositionType="parentRelative"
           motion:percentY="0.5"
   />
</KeyFrameSet>

KeyFrameSet هو عنصر ثانوي لـ Transition، وهو مجموعة من كل KeyFrames، مثل KeyPosition، التي يجب تطبيقها أثناء الانتقال.

بما أنّ MotionLayout تحتسب مسار القمر بين نقطتَي البداية والنهاية، فإنّها تعدّل المسار استنادًا إلى KeyPosition المحدّد في KeyFrameSet. يمكنك معرفة كيفية تعديل المسار عن طريق تشغيل التطبيق مرة أخرى.

يتضمّن KeyPosition عدة سمات تصف طريقة تعديل المسار. في ما يلي أهمها:

  • framePosition هو رقم يتراوح بين 0 و100. يحدّد هذا السمة الوقت الذي يجب فيه تطبيق KeyPosition في الحركة، حيث يمثّل الرقم 1 نسبة% 1 من الحركة، ويمثّل الرقم 99 نسبة% 99 من الحركة. لذا، إذا كانت القيمة 50، عليك تطبيقها في المنتصف تمامًا.
  • motionTarget هو طريقة العرض التي يعدّل KeyPosition المسار لها.
  • keyPositionType هي الطريقة التي يعدّل بها هذا KeyPosition المسار. يمكن أن يكون parentRelative أو pathRelative أو deltaRelative (كما هو موضّح في الخطوة التالية).
  • percentX | percentY هو مقدار تعديل المسار عند framePosition (القيم بين 0.0 و1.0، مع السماح بالقيم السالبة والقيم الأكبر من 1).

يمكنك التفكير في الأمر على النحو التالي: "في framePosition عدِّل مسار motionTarget عن طريق نقله بمقدار percentX أو percentY وفقًا للإحداثيات التي يحدّدها keyPositionType".

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

جرّب بنفسك

إذا شغّلت التطبيق مرة أخرى، ستظهر لك الصورة المتحركة لهذه الخطوة.

46b179c01801f19e.gif

يتبع القمر قوسًا لأنه يمر بـ KeyPosition محدّد في Transition.

<KeyPosition
       motion:framePosition="50"
       motion:motionTarget="@id/moon"
       motion:keyPositionType="parentRelative"
       motion:percentY="0.5"
/>

يمكن قراءة هذا KeyPosition على النحو التالي: "في framePosition 50 (منتصف مدة الحركة)، عدِّل مسار motionTarget @id/moon من خلال تحريكه بمقدار 50% Y (منتصف الشاشة) وفقًا للإحداثيات التي يحدّدها parentRelative (MotionLayout بالكامل)".

لذا، في منتصف الصورة المتحركة، يجب أن يمر القمر عبر KeyPosition يقع في منتصف الشاشة. لا تعدّل هذه السمة KeyPosition حركة X على الإطلاق، لذا سيظل القمر يتحرّك من البداية إلى النهاية أفقيًا. ستحدّد MotionLayout مسارًا سلسًا يمرّ عبر KeyPosition أثناء التنقّل بين نقطتَي البداية والنهاية.

إذا نظرت عن كثب، ستلاحظ أنّ نص الاعتمادات مقيّد بموضع القمر. لماذا لا يتحرّك عموديًا أيضًا؟

1c7cf779931e45cc.gif

<Constraint
       android:id="@id/credits"
       ...
       motion:layout_constraintBottom_toBottomOf="@id/moon"
       motion:layout_constraintTop_toTopOf="@id/moon"
/>

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

إذا أردت أن تتحرّك الأرصدة مع القمر، يمكنك إضافة KeyPosition إلى الأرصدة أو تعديل قيود البدء في @id/credits.

في القسم التالي، ستتعرّف على الأنواع المختلفة من keyPositionType في MotionLayout.

6. فهم keyPositionType

في الخطوة الأخيرة، استخدمت نوع keyPosition من parentRelative لإزاحة المسار بنسبة% 50 من الشاشة. تحدّد السمة keyPositionType كيفية تعديل MotionLayout للمسار وفقًا للسمة percentX أو percentY.

<KeyFrameSet>
   <KeyPosition
           motion:framePosition="50"
           motion:motionTarget="@id/moon"
           motion:keyPositionType="parentRelative"
           motion:percentY="0.5"
   />
</KeyFrameSet>

هناك ثلاثة أنواع مختلفة من keyPosition: parentRelative وpathRelative وdeltaRelative. سيؤدي تحديد نوع إلى تغيير نظام الإحداثيات الذي يتم من خلاله احتساب percentX وpercentY.

ما هو نظام الإحداثيات؟

يوفر نظام الإحداثيات طريقة لتحديد نقطة في الفضاء. وهي مفيدة أيضًا لوصف موضع على الشاشة.

MotionLayout أنظمة الإحداثيات هي نظام إحداثيات ديكارتية. وهذا يعني أنّ لها محورَين X وY محدَّدَين بخطَّين متعامدين. ويكمن الاختلاف الرئيسي بينهما في موضع المحور السيني على الشاشة (يكون المحور الصادي دائمًا عموديًا على المحور السيني).

تستخدِم جميع أنظمة الإحداثيات في MotionLayout قيمًا تتراوح بين 0.0 و1.0 على المحورين X وY. تسمح هذه الحقول بالقيم السالبة والقيم الأكبر من 1.0. على سبيل المثال، تعني القيمة percentX التي تبلغ -2.0 الانتقال في الاتجاه المعاكس للمحور X مرتين.

إذا كان كل ذلك يبدو مشابهًا لدرس الجبر، يمكنك الاطّلاع على الصور أدناه.

parentRelative coordinates

a7b7568d46d9dec7.png

يستخدم keyPositionType الخاص بـ parentRelative نظام الإحداثيات نفسه المستخدَم في الشاشة. يحدّد (0, 0) أعلى يمين MotionLayout بالكامل، ويحدّد (1, 1) أسفل يسار MotionLayout.

يمكنك استخدام parentRelative متى أردت إنشاء صورة متحركة تنتقل خلال MotionLayout بالكامل، مثل قوس القمر في هذا المثال.

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

deltaRelative coordinates

5680bf553627416c.png

دلتا هو مصطلح رياضي يعني التغيير، لذا فإنّ deltaRelative هي طريقة للتعبير عن "التغيير النسبي". في إحداثيات deltaRelative، يمثّل (0,0) موضع بدء العرض، ويمثّل (1,1) موضع الانتهاء. تتم محاذاة المحورين "س" و"ص" مع الشاشة.

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

deltaRelative هو نظام إحداثيات رائع للتحكّم في الحركة الأفقية أو العمودية بشكل منفصل. على سبيل المثال، يمكنك إنشاء صورة متحركة تكمل حركتها العمودية (Y) فقط بنسبة %50، وتستمر في الحركة أفقيًا (X).

pathRelative coordinates

f3aaadaac8b4a93f.png

آخر نظام إحداثيات في MotionLayout هو pathRelative. يختلف هذا النوع عن النوعين الآخرين لأنّ المحور X يتّبع مسار الحركة من البداية إلى النهاية. إذًا، (0,0) هو موضع البداية، و(1,0) هو موضع النهاية.

لماذا قد تحتاج إلى ذلك؟ قد يبدو ذلك مفاجئًا للوهلة الأولى، خاصةً أنّ نظام الإحداثيات هذا لا يتوافق حتى مع نظام إحداثيات الشاشة.

اتّضح أنّ pathRelative مفيد جدًا في بعض الحالات.

  • تسريع أو إبطاء أو إيقاف العرض أثناء جزء من الحركة بما أنّ البُعد X سيتطابق دائمًا مع المسار الذي تتّخذه طريقة العرض تمامًا، يمكنك استخدام pathRelative KeyPosition لتغيير framePosition الذي يتم الوصول إليه في نقطة معيّنة في هذا المسار. لذا، فإنّ KeyPosition عند framePosition="50" مع percentX="0.1" سيؤدي إلى استغراق الصورة المتحركة% 50 من الوقت لقطع% 10 الأولى من الحركة.
  • إضافة قوس خفيف إلى مسار بما أنّ البُعد Y يكون دائمًا عموديًا على الحركة، سيؤدي تغيير Y إلى تغيير المسار ليصبح منحنيًا بالنسبة إلى الحركة الإجمالية.
  • لن تنجح إضافة سمة ثانية عندما تكون deltaRelative غير متاحة. بالنسبة إلى الحركة الأفقية والعمودية تمامًا، لن تنشئ deltaRelative سوى بُعد واحد مفيد. ومع ذلك، ستنشئ pathRelative دائمًا إحداثيات X وY قابلة للاستخدام.

في الخطوة التالية، ستتعرّف على كيفية إنشاء مسارات أكثر تعقيدًا باستخدام أكثر من KeyPosition.

7. إنشاء مسارات معقّدة

بالنظر إلى الصورة المتحركة التي أنشأتها في الخطوة الأخيرة، نلاحظ أنّها تنشئ منحنى سلسًا، ولكن يمكن أن يكون الشكل "أكثر شبهًا بالقمر".

تعديل مسار يتضمّن عناصر KeyPosition متعددة

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

  1. فتح "xml/step4.xml" ستلاحظ أنّ الفيديو يتضمّن عدد المشاهدات نفسه وKeyFrame الذي أضفته في الخطوة الأخيرة.
  2. لإكمال الجزء العلوي من المنحنى، أضِف KeyPositions آخرَين إلى مسار @id/moon، أحدهما قبل أن يصل إلى الأعلى مباشرةً، والآخر بعد ذلك.

500b5ac2db48ef87.png

step4.xml

<!-- TODO: Add two more KeyPositions to the KeyFrameSet here -->
<KeyPosition
       motion:framePosition="25"
       motion:motionTarget="@id/moon"
       motion:keyPositionType="parentRelative"
       motion:percentY="0.6"
/>
<KeyPosition
       motion:framePosition="75"
       motion:motionTarget="@id/moon"
       motion:keyPositionType="parentRelative"
       motion:percentY="0.6"
/>

سيتم تطبيق KeyPositions بنسبة% 25 و% 75 من خلال الصورة المتحركة، وسيؤدي ذلك إلى تحرّك @id/moon خلال مسار يبعد% 60 عن أعلى الشاشة. وعند دمجها مع KeyPosition الحالي بنسبة %50، يتم إنشاء قوس سلس يتبعه القمر.

في MotionLayout، يمكنك إضافة العدد الذي تريده من KeyPositions للحصول على مسار الحركة الذي تريده. سيطبّق MotionLayout كل KeyPosition في framePosition المحدّد، وسيحدّد كيفية إنشاء حركة سلسة تمرّ بكل KeyPositions.

جرّب بنفسك

  1. شغِّل التطبيق مرة أخرى. انتقِل إلى الخطوة 4 لمشاهدة الصورة المتحركة. عند النقر على القمر، سيتّبع المسار من البداية إلى النهاية، مرورًا بكل KeyPosition تم تحديده في KeyFrameSet.

استكشاف المحتوى بنفسك

قبل الانتقال إلى أنواع أخرى من KeyFrame، جرِّب إضافة المزيد من KeyPositions إلى KeyFrameSet لمعرفة أنواع التأثيرات التي يمكنك إنشاؤها باستخدام KeyPosition فقط.

في ما يلي مثال يوضّح كيفية إنشاء مسار معقّد يتحرّك للأمام والخلف أثناء الصورة المتحرّكة.

cd9faaffde3dfef.png

step4.xml

<!-- Complex paths example: Dancing moon -->
<KeyFrameSet>
   <KeyPosition
           motion:framePosition="25"
           motion:motionTarget="@id/moon"
           motion:keyPositionType="parentRelative"
           motion:percentY="0.6"
           motion:percentX="0.1"
   />
   <KeyPosition
           motion:framePosition="50"
           motion:motionTarget="@id/moon"
           motion:keyPositionType="parentRelative"
           motion:percentY="0.5"
           motion:percentX="0.3"
   />
   <KeyPosition
           motion:framePosition="75"
           motion:motionTarget="@id/moon"
           motion:keyPositionType="parentRelative"
           motion:percentY="0.6"
           motion:percentX="0.1"
   />
</KeyFrameSet>

بعد الانتهاء من استكشاف KeyPosition، ستنتقل في الخطوة التالية إلى أنواع أخرى من KeyFrames.

8. تغيير السمات أثناء الحركة

غالبًا ما يعني إنشاء صور متحركة ديناميكية تغيير size أو rotation أو alpha للعناصر أثناء تقدّم الصورة المتحركة. تتيح السمة MotionLayout إضافة مؤثرات حركية إلى العديد من السمات في أي طريقة عرض باستخدام KeyAttribute.

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

الخطوة 1: تغيير الحجم والتدوير باستخدام KeyAttribute

  1. افتح xml/step5.xml الذي يحتوي على الصورة المتحركة نفسها التي أنشأتها في الخطوة الأخيرة. للتنويع، تستخدم هذه الشاشة صورة مختلفة للفضاء كخلفية.
  2. لجعل القمر يتوسّع في الحجم ويدور، أضِف علامتَي KeyAttribute في KeyFrameSet عند keyFrame="50" وkeyFrame="100".

bbae524a2898569.png

step5.xml

<!-- TODO: Add KeyAttributes to rotate and resize @id/moon -->

<KeyAttribute
       motion:framePosition="50"
       motion:motionTarget="@id/moon"
       android:scaleY="2.0"
       android:scaleX="2.0"
       android:rotation="-360"
/>
<KeyAttribute
       motion:framePosition="100"
       motion:motionTarget="@id/moon"
       android:rotation="-720"
/>

يتم تطبيق KeyAttributes هذه بنسبة% 50 و% 100 من الحركة. سيحدث أول KeyAttribute عند% 50 في أعلى القوس، وسيؤدي إلى مضاعفة حجم العرض بالإضافة إلى تدويره بمقدار 360 درجة (أو دائرة كاملة واحدة). ستنهي عملية التدوير الثانية في KeyAttribute الدورة الثانية إلى -720 درجة (دائرتان كاملتان) وسيتم تصغير الحجم إلى الحجم العادي لأنّ القيمتين scaleX وscaleY تكونان 1.0 تلقائيًا.

تمامًا مثل KeyPosition، يستخدم KeyAttribute framePosition وmotionTarget لتحديد وقت تطبيق KeyFrame، والملف الشخصي الذي سيتم تعديله. ستجري MotionLayout عملية استيفاء بين KeyPositions لإنشاء رسوم متحركة سلسة.

تتيح KeyAttributes سمات يمكن تطبيقها على جميع طرق العرض. تتيح هذه الأدوات تغيير السمات الأساسية، مثل visibility أو alpha أو elevation. يمكنك أيضًا تغيير التدوير كما تفعل هنا، أو التدوير في ثلاثة أبعاد باستخدام rotateX وrotateY، أو تغيير الحجم باستخدام scaleX وscaleY، أو تغيير موضع العرض في X أو Y أو Z.

الخطوة 2: تأخير ظهور بطاقة الائتمان

أحد أهداف هذه الخطوة هو تعديل الصورة المتحركة حتى لا يظهر نص الاعتمادات إلا بعد اكتمال معظم الصورة المتحركة.

  1. لتأخير ظهور الأرصدة، حدِّد قيمة KeyAttribute إضافية تضمن بقاء قيمة alpha هي 0 إلى أن تصل إلى keyPosition="85". سيظلّ MotionLayout ينتقل بسلاسة من 0 إلى 100 ألفا، ولكن سيحدث ذلك خلال آخر% 15 من الحركة.

step5.xml

<!-- TODO: Add KeyAttribute to delay the appearance of @id/credits -->

<KeyAttribute
       motion:framePosition="85"
       motion:motionTarget="@id/credits"
       android:alpha="0.0"
/>

يُبقي KeyAttribute هذا alpha الخاص بـ @id/credits عند 0.0 في أول% 85 من الصورة المتحركة. بما أنّها تبدأ بقيمة ألفا 0، يعني ذلك أنّها ستكون غير مرئية خلال% 85 الأولى من الصورة المتحركة.

تتمثّل النتيجة النهائية لهذا KeyAttribute في ظهور الاعتمادات في نهاية الصورة المتحركة. ويعطي ذلك انطباعًا بأنّها منسّقة مع القمر الذي يستقر في الزاوية اليسرى من الشاشة.

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

جرّب بنفسك

  1. شغِّل التطبيق مرة أخرى وانتقِل إلى الخطوة 5 لمشاهدة الصورة المتحركة أثناء عملها. عند النقر على القمر، سيتّبع المسار من البداية إلى النهاية، مرورًا بكل KeyAttribute تم تحديده في KeyFrameSet.

2f4bfdd681c1fa98.gif

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

استكشاف الموقع بنفسك

قبل الانتقال إلى النوع الأخير من KeyFrame، جرِّب تعديل السمات العادية الأخرى في KeyAttributes. على سبيل المثال، جرِّب تغيير rotation إلى rotationX لمعرفة الصورة المتحركة التي سيتم إنتاجها.

في ما يلي قائمة بالسمات العادية التي يمكنك تجربتها:

  • android:visibility
  • android:alpha
  • android:elevation
  • android:rotation
  • android:rotationX
  • android:rotationY
  • android:scaleX
  • android:scaleY
  • android:translationX
  • android:translationY
  • android:translationZ

9- تغيير السمات المخصّصة

تتضمّن الرسوم المتحركة الغنية تغيير لون العرض أو سماته الأخرى. في حين يمكن استخدام MotionLayout لـ KeyAttribute من أجل تغيير أي من السمات العادية المدرَجة في المهمة السابقة، يمكنك استخدام CustomAttribute لتحديد أي سمة أخرى.

يمكن استخدام CustomAttribute لضبط أي قيمة تتضمّن دالة تحديد القيمة. على سبيل المثال، يمكنك ضبط backgroundColor على View باستخدام CustomAttribute. سيستخدم MotionLayout الانعكاس للعثور على طريقة تحديد القيمة، ثم سيتم استدعاؤها بشكل متكرر لتحريك العرض.

في هذه الخطوة، ستستخدم CustomAttribute لضبط السمة colorFilter على القمر لإنشاء الحركة الموضّحة أدناه.

5fb6792126a09fda.gif

تحديد السمات المخصّصة

  1. للبدء، افتح xml/step6.xml الذي يحتوي على الحركة نفسها التي أنشأتها في الخطوة الأخيرة.
  2. لجعل القمر يغيّر ألوانه، أضِف KeyAttribute مع CustomAttribute في KeyFrameSet عند keyFrame="0" وkeyFrame="50" وkeyFrame="100".

214699d5fdd956da.png

step6.xml

<!-- TODO: Add Custom attributes here -->
<KeyAttribute
       motion:framePosition="0"
       motion:motionTarget="@id/moon">
   <CustomAttribute
           motion:attributeName="colorFilter"
           motion:customColorValue="#FFFFFF"
   />
</KeyAttribute>
<KeyAttribute
       motion:framePosition="50"
       motion:motionTarget="@id/moon">
   <CustomAttribute
           motion:attributeName="colorFilter"
           motion:customColorValue="#FFB612"
   />
</KeyAttribute>
<KeyAttribute
       motion:framePosition="100"
       motion:motionTarget="@id/moon">
   <CustomAttribute
           motion:attributeName="colorFilter"
           motion:customColorValue="#FFFFFF"
   />
</KeyAttribute>

تضيف CustomAttribute داخل KeyAttribute. سيتم تطبيق CustomAttribute في framePosition المحدّد من قِبل KeyAttribute.

داخل CustomAttribute، يجب تحديد attributeName وقيمة واحدة لضبطها.

  • motion:attributeName هو اسم أداة الضبط التي سيتم استدعاؤها بواسطة هذه السمة المخصّصة. في هذا المثال، سيتم استدعاء setColorFilter في Drawable.
  • motion:custom*Value هي قيمة مخصّصة للنوع المذكور في الاسم، وفي هذا المثال، القيمة المخصّصة هي لون محدّد.

يمكن أن تتضمّن القيم المخصّصة أيًّا من الأنواع التالية:

  • اللون
  • عدد صحيح
  • عدد عائم
  • سلسلة
  • السمة
  • منطقي

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

جرّب بنفسك

  1. شغِّل التطبيق مرة أخرى وانتقِل إلى الخطوة 6 لمشاهدة الصورة المتحركة أثناء عملها. عند النقر على القمر، سيتّبع المسار من البداية إلى النهاية، مرورًا بكل KeyAttribute تم تحديده في KeyFrameSet.

5fb6792126a09fda.gif

عند إضافة المزيد من KeyFrames، يغيّر MotionLayout مسار القمر من خط مستقيم إلى منحنى معقّد، ويضيف شقلبة خلفية مزدوجة وتغييرًا في الحجم وتغييرًا في اللون في منتصف الصورة المتحركة.

في الرسوم المتحركة الحقيقية، غالبًا ما يتم تحريك عدة طرق عرض في الوقت نفسه، مع التحكّم في حركتها على طول مسارات وسرعات مختلفة. من خلال تحديد KeyFrame مختلف لكل طريقة عرض، يمكن تصميم حركات غنية تحرّك طرق عرض متعددة باستخدام MotionLayout.

10. سحب الأحداث والمسارات المعقّدة

في هذه الخطوة، سنتعرّف على كيفية استخدام OnSwipe مع المسارات المعقّدة. حتى الآن، يتم تشغيل الرسوم المتحركة للقمر من خلال أداة معالجة OnClick وتستمر لمدة ثابتة.

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

الخطوة 1: استكشاف سلوك OnSwipe

  1. افتح xml/step7.xml وابحث عن بيان OnSwipe الحالي.

step7.xml

<!-- Fix OnSwipe by changing touchAnchorSide →

<OnSwipe
       motion:touchAnchorId="@id/moon"
       motion:touchAnchorSide="bottom"
/>
  1. شغِّل التطبيق على جهازك وانتقِل إلى الخطوة 7. معرفة ما إذا كان بإمكانك إنشاء صورة متحركة سلسة عن طريق سحب القمر على طول مسار القوس

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

ed96e3674854a548.gif

لفهم الخطأ، فكِّر في ما يحدث عندما يلمس المستخدم أسفل الجزء العلوي من القوس مباشرةً. بما أنّ العلامة OnSwipe تتضمّن motion:touchAnchorSide="bottom"، ستحاول MotionLayout الحفاظ على المسافة بين الإصبع وأسفل العرض ثابتة طوال مدة الحركة.

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

56cd575c5c77eddd.png

الخطوة 2: استخدام الجانب الأيمن

لتجنُّب أخطاء مثل هذه، من المهم دائمًا اختيار touchAnchorId وtouchAnchorSide يتطوّران دائمًا في اتجاه واحد طوال مدة الصورة المتحركة بأكملها.

في هذه الصورة المتحركة، سيتحرّك كل من الجانب right والجانب left من القمر على الشاشة في اتجاه واحد.

ومع ذلك، سيعكس كل من bottom وtop اتجاههما. عندما يحاول OnSwipe تتبُّعها، سيحدث تشويش عند تغيير اتجاهها.

  1. لجعل هذه الصورة المتحركة تتبع حدث لمس، غيِّر touchAnchorSide إلى right.

step7.xml

<!-- Fix OnSwipe by changing touchAnchorSide →

<OnSwipe
       motion:touchAnchorId="@id/moon"
       motion:touchAnchorSide="right"
/>

الخطوة 3: استخدام dragDirection

يمكنك أيضًا دمج dragDirection مع touchAnchorSide لجعل المقطع الصوتي يسير في اتجاه مختلف عن الاتجاه الذي يسير فيه عادةً. يبقى من المهم أن يتقدّم touchAnchorSide في اتجاه واحد فقط، ولكن يمكنك تحديد الاتجاه الذي تريد تتبّعه في MotionLayout. على سبيل المثال، يمكنك الاحتفاظ بـ touchAnchorSide="bottom"، ولكن إضافة dragDirection="dragRight". سيؤدي ذلك إلى تتبُّع MotionLayout لموضع أسفل طريقة العرض، ولكن سيتم أخذ موضعها في الاعتبار فقط عند الانتقال إلى اليسار (سيتم تجاهل الحركة العمودية). وبالتالي، حتى إذا كان الجزء السفلي يتحرك للأعلى والأسفل، سيظلّ يتحرك بشكل صحيح باستخدام OnSwipe.

  1. حدِّث OnSwipe لتتبُّع حركة القمر بشكل صحيح.

step7.xml

<!-- Using dragDirection to control the direction of drag tracking →

<OnSwipe
       motion:touchAnchorId="@id/moon"
       motion:touchAnchorSide="bottom"
       motion:dragDirection="dragRight"
/>

جرّب بنفسك

  1. شغِّل التطبيق مرة أخرى وحاوِل سحب القمر على طول المسار بأكمله. على الرغم من أنّها تتّبع مسارًا معقّدًا، سيتمكّن MotionLayout من تحريك الصورة المتحركة استجابةً لأحداث التمرير السريع.

5458dff382261427.gif

11. تشغيل الحركة باستخدام الرمز

يمكن استخدام MotionLayout لإنشاء رسوم متحركة غنية عند استخدامها مع CoordinatorLayout. في هذه الخطوة، ستنشئ عنوانًا قابلاً للتصغير باستخدام MotionLayout.

الخطوة 1: استكشاف الرمز الحالي

  1. للبدء، افتح layout/activity_step8.xml.
  2. في layout/activity_step8.xml، يمكنك ملاحظة أنّه تم إنشاء CoordinatorLayout وAppBarLayout عاملَين.

activity_step8.xml

<androidx.coordinatorlayout.widget.CoordinatorLayout
       ...>
   <com.google.android.material.appbar.AppBarLayout
           android:id="@+id/appbar_layout"
           android:layout_width="match_parent"
           android:layout_height="180dp">
       <androidx.constraintlayout.motion.widget.MotionLayout
               android:id="@+id/motion_layout"
               ... >
           ...
       </androidx.constraintlayout.motion.widget.MotionLayout>
   </com.google.android.material.appbar.AppBarLayout>
  
   <androidx.core.widget.NestedScrollView
           ...
           motion:layout_behavior="@string/appbar_scrolling_view_behavior" >
           ...
   </androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

يستخدم هذا التصميم CoordinatorLayout لمشاركة معلومات التمرير بين NestedScrollView وAppBarLayout. لذلك، عندما يتم تمرير NestedScrollView للأعلى، سيتم إعلام AppBarLayout بالتغيير. هذه هي طريقة تنفيذ شريط أدوات قابل للتصغير مثل هذا على Android، وسيتم "تنسيق" تمرير النص مع العنوان القابل للتصغير.

مشهد الحركة الذي يشير إليه @id/motion_layout مشابه لمشهد الحركة في الخطوة الأخيرة. ومع ذلك، تمت إزالة تعريف OnSwipe لكي يعمل مع CoordinatorLayout.

  1. شغِّل التطبيق وانتقِل إلى الخطوة 8. يمكنك ملاحظة أنّ القمر لا يتحرّك عند تمرير النص.

الخطوة 2: جعل MotionLayout قابلاً للتمرير

  1. لجعل طريقة عرض MotionLayout تتنقّل فور تنقّل NestedScrollView، أضِف motion:minHeight وmotion:layout_scrollFlags إلى MotionLayout.

activity_step8.xml

<!-- Add minHeight and layout_scrollFlags to the MotionLayout -->

<androidx.constraintlayout.motion.widget.MotionLayout
       android:id="@+id/motion_layout"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       motion:layoutDescription="@xml/step8"
       motion:motionDebug="SHOW_PATH"
       android:minHeight="80dp"
       motion:layout_scrollFlags="scroll|enterAlways|snap|exitUntilCollapsed"  >
  1. شغِّل التطبيق مرة أخرى وانتقِل إلى الخطوة 8. يظهر لك أنّ MotionLayout يختفي عند التمرير سريعًا للأعلى. ومع ذلك، لا تتقدّم الصورة المتحركة استنادًا إلى سلوك التمرير حتى الآن.

الخطوة 3: تحريك الصورة المتحركة باستخدام الرمز

  1. افتح Step8Activity.kt . عدِّل الدالة coordinateMotion() لإبلاغ MotionLayout بالتغييرات في موضع التمرير.

Step8Activity.kt

// TODO: set progress of MotionLayout based on an AppBarLayout.OnOffsetChangedListener

private fun coordinateMotion() {
    val appBarLayout: AppBarLayout = findViewById(R.id.appbar_layout)
    val motionLayout: MotionLayout = findViewById(R.id.motion_layout)

    val listener = AppBarLayout.OnOffsetChangedListener { unused, verticalOffset ->
        val seekPosition = -verticalOffset / appBarLayout.totalScrollRange.toFloat()
        motionLayout.progress = seekPosition
    }

    appBarLayout.addOnOffsetChangedListener(listener)
}

سيسجّل هذا الرمز OnOffsetChangedListener سيتم استدعاؤه في كل مرة يتنقّل فيها المستخدم للأعلى أو للأسفل مع إزاحة التمرير الحالية.

تتيح السمة MotionLayout طلب الانتقال من خلال ضبط السمة progress. لتحويل القيمة بين verticalOffset ومستوى التقدّم كنسبة مئوية، قسِّم على إجمالي نطاق التمرير.

جرّب بنفسك

  1. انشر التطبيق مرة أخرى وشغِّل الرسوم المتحركة في الخطوة 8. يمكنك ملاحظة أنّ MotionLayout سيؤدي إلى تقدّم الرسم المتحرّك استنادًا إلى موضع التمرير.

ee5ce4d9e33a59ca.gif

يمكن إنشاء رسوم متحركة مخصّصة لشريط الأدوات القابل للتصغير باستخدام MotionLayout. باستخدام سلسلة من KeyFrames، يمكنك تحقيق تأثيرات جريئة جدًا.

12. تهانينا

تناول هذا الدرس التطبيقي حول الترميز واجهة برمجة التطبيقات الأساسية في MotionLayout.

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

مزيد من المعلومات

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

للحصول على روابط تؤدي إلى دروس تطبيقية حول الترميز الأخرى في هذه الدورة التدريبية، يمكنك الاطّلاع على الصفحة المقصودة للدروس التطبيقية حول الترميز الخاصة بدورة "تطبيقات متقدّمة متوافقة مع نظام Android باستخدام لغة Kotlin".