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

1. قبل البدء

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

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

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

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

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

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

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

  • يمكنك التعرّف على كيفية إنشاء تطبيق باستخدام نشاط وتصميم أساسي وتشغيله على جهاز أو محاكي باستخدام "استوديو Android". مزيد من المعلومات عن "ConstraintLayout" يمكنك الاطّلاع على الدرس التطبيقي حول ترميز تنسيق القيود لمعرفة مزيد من المعلومات حول 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: يحدد كل من قيد start وend الانتقال.

لنلقِ نظرة على كل من هذه الخيارات بالترتيب، بدءًا من 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 هذا، لذا إذا أردت تشغيل التطبيق الآن، ستظهر لك شاشة النجمة بدون قيود، ما يعني أنّه سيتم وضعها في موقع غير معروف. سيُرسل لك "استوديو Android" تحذيرًا بشأن عدم توفُّر قيود.

الخطوة 2: التحويل إلى "تخطيط الحركة"

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

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

  1. للقيام بذلك، افتح سطح التصميم. في Android Studio 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. نظرة عامة: تحديد شكلي يتيح لك اختيار أجزاء مختلفة من الصورة المتحركة. في هذه الصورة، تم اختيار ConstraintSet start. يمكنك أيضًا اختيار الانتقال بين start وend من خلال النقر على السهم بينهما.
  2. القسم: أسفل النظرة العامة، تظهر نافذة قسم تتغيّر استنادًا إلى عنصر النظرة العامة المحدَّد حاليًا. في هذه الصورة، يتم عرض معلومات start ConstraintSet في نافذة الاختيار.
  3. السمة – تعرض لوحة السمات وتتيح لك تعديل سمات العنصر الحالي المحدّد من خلال نافذة النظرة العامة أو نافذة الاختيار. وتعرض في هذه الصورة سمات start ConstraintSet.

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

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

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

أي قيود محدّدة في ConstraintSet ستلغي القيود المحددة في ملف التنسيق. في حال تحديد قيود في كل من التنسيق وMotionScene، سيتم تطبيق القيود في MotionScene فقط.

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

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

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

6e57661ed358b860.png

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

f9564c574b86ea8.gif

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

2fce076cd7b04bd.png

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

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. اختَر ConstraintSet end في لوحة النظرة العامة.

346e1248639b6f1e.png

  1. اتّبِع الخطوات نفسها التي اتخذتها في السابق لإضافة Constraint لـ red_star في ConstraintSet end.
  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. أنشأ Motion Editor عنصر انتقالي بشكل تلقائي عند إنشاء ملف 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

صورة متحركة: فيديو لتشغيل معاينة انتقال في Motion Editor

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

1dc541ae8c43b250.png

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

a0fd2593384dfb36.png

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

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

  1. افتح محرِّر الحركة واختَر العنصر الانتقالي من خلال النقر على السهم بين البداية والنهاية في لوحة النظرة العامة.

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 لعرض الرمز الذي أنشأه تطبيق Motion Editor.

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. استبدِل قائمة المهام لإضافة علامة 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" ذلك الجانب بعيدًا عن إصبعه بنسبة 100 بكسل مستقل الكثافة مثلاً طوال مدة الحركة.

جرّبه الآن

  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 بأسفل الشاشة بنسبة 50%. لا يغيّر 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 هي نظام إحداثيات كارتزي. هذا يعني أنها تحتوي على محور س ومحور ص محدد بخطين عموديين. الفرق الرئيسي بينهما هو مكان المحور س على الشاشة (المحور ص دائمًا عمودي على المحور س).

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

إذا كان كل ذلك يبدو إلى حد كبير مثل حصة الجبر، فراجع الصور أدناه!

الإحداثيات النسبية

a7b7568d46d9dec7.png

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

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

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

إحداثيات دلتا النسبية

5680bf553627416c.png

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

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

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

الإحداثيات النسبية

f3aaadaac8b4a93f.png

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

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

اتضح أن pathRelative مفيد حقًا في بعض الأمور.

  • تسريع أو إبطاء أو إيقاف عرض أثناء جزء من الصورة المتحركة: بما أنّ السمة X ستتطابق دائمًا مع المسار الذي يتخذه العرض تمامًا، يمكنك استخدام KeyPosition pathRelative لتغيير 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 لضبط أي قيمة لها قيمة setter. على سبيل المثال، يمكنك ضبط backgroundColor على طريقة عرض باستخدام 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: استكشاف سلوك OnSالتمرير

  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 طلب عملية النقل من خلال ضبط خاصية التقدّم. للتحويل بين verticalOffset ومستوى التقدّم بالنسبة المئوية، اقسِم على إجمالي نطاق التمرير.

جرّب بنفسك

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

ee5ce4d9e33a59ca.gif

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

12. تهانينا

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

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

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

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

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