1. قبل البدء
هذا الدرس العملي حول الترميز هو جزء من دورة "تطوير تطبيقات Android المتقدّمة باستخدام لغة Kotlin". ستستفيد إلى أقصى حدّ من هذه الدورة التدريبية إذا عملت على إكمال الدروس التطبيقية بالتسلسل، ولكن هذا ليس إلزاميًا. يمكنك الاطّلاع على جميع دروس الترميز في الدورة التدريبية على الصفحة المقصودة لدروس الترميز حول تطوير تطبيقات Android المتقدّمة باستخدام لغة Kotlin.
MotionLayout هي مكتبة تتيح لك إضافة رسومات متحركة غنية إلى تطبيق Android. وهي تستند إلى ConstraintLayout, وتتيح لك تحريك أي عنصر يمكنك إنشاؤه باستخدام ConstraintLayout.
يمكنك استخدام MotionLayout لتحريك الموقع الجغرافي والحجم ومستوى العرض والشفافية واللون والارتفاع والتدوير وسمات أخرى لعدة طرق عرض في الوقت نفسه. باستخدام XML التعريفية، يمكنك إنشاء رسوم متحركة منسَّقة تتضمّن طرق عرض متعددة ويصعب تنفيذها في الرمز.
تُعدّ الرسوم المتحركة طريقة رائعة لتحسين تجربة استخدام التطبيق. يمكنك استخدام الصور المتحركة من أجل:
- عرض التغييرات: يتيح التنقّل بين الحالات للمستخدم تتبُّع التغييرات في واجهة المستخدم بشكل طبيعي.
- جذب الانتباه: استخدِم الصور المتحركة لجذب الانتباه إلى عناصر واجهة المستخدم المهمة.
- إنشاء تصاميم رائعة: تساعد الحركة الفعّالة في التصميم على إظهار التطبيقات بشكل أنيق.
المتطلبات الأساسية
تم تصميم هذا الدرس التطبيقي حول الترميز للمطوّرين الذين لديهم بعض الخبرة في تطوير تطبيقات Android. قبل محاولة إكمال هذا الدرس التطبيقي حول الترميز، يجب أن:
- معرفة كيفية إنشاء تطبيق يتضمّن نشاطًا وتصميمًا أساسيًا وتشغيله على جهاز أو محاكي باستخدام "استوديو Android" التعرّف على
ConstraintLayoutيمكنك الاطّلاع على درس تطبيقي حول الترميز حول Constraint Layout لمعرفة المزيد عنConstraintLayout.
المهام التي ستنفذها
- تحديد صورة متحركة باستخدام
ConstraintSetsوMotionLayout - تحريك العناصر استنادًا إلى أحداث السحب
- تغيير الصورة المتحركة باستخدام
KeyPosition - تغيير السمات باستخدام
KeyAttribute - تشغيل الصور المتحركة باستخدام الرموز البرمجية
- إضافة تأثيرات متحركة إلى العناوين القابلة للتصغير باستخدام
MotionLayout
المتطلبات
- الإصدار 4.0 من "استوديو Android" (لا يعمل محرّر
MotionLayoutإلا مع هذا الإصدار من "استوديو Android").
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..
- في
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: التحويل إلى Motion Layout
لإنشاء رسوم متحركة باستخدام MotionLayout,، عليك تحويل ConstraintLayout إلى MotionLayout.
لكي يستخدم التصميم مشهدًا متحركًا، يجب أن يشير إليه.
- لإجراء ذلك، افتح مساحة التصميم. في "استوديو Android" 4.0، يمكنك فتح سطح التصميم باستخدام رمز التقسيم أو التصميم في أعلى يسار الشاشة عند عرض ملف XML للتصميم.

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

يؤدي ذلك إلى استبدال العلامة 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، ستعرض مساحة التصميم "أداة تعديل الصور المتحركة".

تتضمّن "أداة تعديل الصور المتحركة" ثلاثة عناصر جديدة في واجهة المستخدِم:
- نظرة عامة: هذا هو خيار النافذة المنبثقة الذي يتيح لك اختيار أجزاء مختلفة من الصورة المتحركة. في هذه الصورة، تم اختيار
startConstraintSet. يمكنك أيضًا اختيار الانتقال بينstartوendمن خلال النقر على السهم بينهما. - القسم: أسفل النظرة العامة، تظهر نافذة قسم تتغيّر استنادًا إلى عنصر النظرة العامة المحدّد حاليًا. في هذه الصورة، يتم عرض معلومات
startConstraintSetفي نافذة الاختيار. - السمة: تعرض لوحة السمات سمات العنصر المحدّد حاليًا وتتيح لك تعديلها من نافذة النظرة العامة أو نافذة التحديد. في هذه الصورة، يتم عرض سمات
startConstraintSet.
الخطوة 3: تحديد قيود البدء والانتهاء
يمكن تحديد جميع الحركات من حيث البداية والنهاية. يصف البدء شكل الشاشة قبل الحركة، ويصف النهاية شكل الشاشة بعد اكتمال الحركة. MotionLayout مسؤولة عن تحديد كيفية إنشاء صورة متحركة بين حالتَي البداية والنهاية (بمرور الوقت).
تستخدم MotionScene علامة ConstraintSet لتحديد حالتي البدء والانتهاء. ConstraintSet هو ما يبدو عليه، أي مجموعة من القيود التي يمكن تطبيقها على طرق العرض. ويشمل ذلك قيود العرض والارتفاع وConstraintLayout. يتضمّن أيضًا بعض السمات، مثل alpha. ولا يحتوي على طرق العرض نفسها، بل على القيود المفروضة على طرق العرض هذه فقط.
ستتجاوز أي قيود محدّدة في ConstraintSet القيود المحدّدة في ملف التصميم. إذا حدّدت قيودًا في كلّ من التصميم وMotionScene، سيتم تطبيق القيود في MotionScene فقط.
في هذه الخطوة، ستفرض قيودًا على طريقة عرض النجمة بحيث تبدأ من أعلى الشاشة وتنتهي في أسفلها.
يمكنك إكمال هذه الخطوة باستخدام "أداة تعديل الصور المتحركة" أو من خلال تعديل نص activity_step1_scene.xml مباشرةً.
- اختَر
startConstraintSet في لوحة النظرة العامة

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

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

- افتح
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 ببداية الجزء العلوي من العنصر الأصل.
- اختَر
endConstraintSet في لوحة "نظرة عامة".

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

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

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

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

الخطوة 6: إضافة معالج عند النقر
يجب أن يكون لديك طريقة لبدء الصورة المتحركة. إحدى طرق إجراء ذلك هي جعل MotionLayout يستجيب لأحداث النقر على @id/red_star.
- افتح "أداة تعديل الحركة" (Motion editor) واختَر الانتقال من خلال النقر على السهم بين البداية والنهاية في لوحة النظرة العامة.

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

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

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

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

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

يحدّد ملف مشهد الحركة المكتمل 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: فحص الرمز الأوّلي
- للبدء، افتح ملف التصميم
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 للبدء
- افتح مشهد الحركة
xml/step2.xmlلتحديد الصورة المتحركة. - أضِف شروطًا لشرط البدء
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 النهائي
- حدِّد قيد النهاية لاستخدام سلسلة لتحديد موضع النجوم الثلاثة معًا أسفل
@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.
- استبدِل 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 على هذه المسافة طوال مدة الحركة.
للتجربة:
- شغِّل التطبيق مرة أخرى، وافتح شاشة "الخطوة 2". ستظهر لك الصورة المتحركة.
- جرِّب "التحريك السريع" أو رفع إصبعك في منتصف الصورة المتحركة لاستكشاف طريقة عرض
MotionLayoutللصور المتحركة المستندة إلى فيزياء السوائل.

يمكن MotionLayout إنشاء رسوم متحركة بين تصاميم مختلفة جدًا باستخدام الميزات من ConstraintLayout لإنشاء تأثيرات غنية.
في هذه الصورة المتحركة، يتم وضع جميع طرق العرض الثلاثة بالنسبة إلى العنصر الرئيسي في أسفل الشاشة في البداية. في النهاية، يتم وضع طرق العرض الثلاثة بالنسبة إلى @id/credits في سلسلة.
على الرغم من اختلاف التصاميم بشكل كبير، ستنشئ MotionLayout حركة سلسة بين البداية والنهاية.
5- تعديل مسار
في هذه الخطوة، ستنشئ صورة متحركة تتّبع مسارًا معقّدًا أثناء الحركة، كما ستنشئ صورة متحركة لأسماء المشاركين أثناء الحركة. يمكن أن تعدّل MotionLayout المسار الذي سيتّخذه العرض بين البداية والنهاية باستخدام KeyPosition.
الخطوة 1: استكشاف الرمز الحالي
- افتح
layout/activity_step3.xmlوxml/step3.xmlللاطّلاع على التصميم الحالي ومشهد الحركة. تعرضImageViewوTextViewالقمر ونص الاعتمادات. - افتح ملف مشهد الحركة (
xml/step3.xml). ستلاحظ أنّه تم تحديدTransitionمن@id/startإلى@id/end. تحرّك الصورة المتحركة صورة القمر من أسفل يسار الشاشة إلى أسفل يمين الشاشة باستخدامConstraintSets. يظهر نص الاعتمادات تدريجيًا منalpha="0.0"إلىalpha="1.0"أثناء تحرّك القمر. - شغِّل التطبيق الآن وانقر على الخطوة 3. ستلاحظ أنّ القمر يسير في مسار خطي (أو خط مستقيم) من البداية إلى النهاية عند النقر على القمر.
الخطوة 2: تفعيل تصحيح أخطاء المسار
قبل إضافة قوس إلى حركة القمر، من المفيد تفعيل تصحيح أخطاء المسار في MotionLayout.
للمساعدة في تطوير صور متحركة معقّدة باستخدام MotionLayout، يمكنك رسم مسار الحركة لكل طريقة عرض. يكون ذلك مفيدًا عندما تريد عرض الرسوم المتحركة، ولضبط التفاصيل الصغيرة للحركة بدقة.
- لتفعيل مسارات تصحيح الأخطاء، افتح
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" >
بعد تفعيل تصحيح أخطاء المسار، عند تشغيل التطبيق مرة أخرى، ستظهر لك مسارات جميع طرق العرض على شكل خط متقطّع.

- تمثّل الدوائر موضع البدء أو الانتهاء لأحد العروض.
- تمثّل الخطوط مسار عرض واحد.
- تمثّل الماس
KeyPositionيعدّل المسار.
على سبيل المثال، في هذه الصورة المتحركة، تمثّل الدائرة الوسطى موضع نص الاعتمادات.
الخطوة 3: تعديل مسار
يتم تحديد جميع الحركات في MotionLayout من خلال ConstraintSet بداية ونهاية تحدّدان شكل الشاشة قبل بدء الحركة وبعد انتهائها. تنشئ الدالة MotionLayout تلقائيًا مسارًا خطيًا (خطًا مستقيمًا) بين موضعَي البداية والنهاية لكل طريقة عرض يتغيّر موضعها.
لإنشاء مسارات معقّدة مثل قوس القمر في هذا المثال، تستخدم MotionLayout KeyPosition لتعديل المسار الذي يتّخذه العرض بين البداية والنهاية.
- افتح
xml/step3.xmlوأضِفKeyPositionإلى المشهد. يتم وضع العلامةKeyPositionداخل العلامةTransition.

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 لتخصيصها.
جرّب بنفسك
إذا شغّلت التطبيق مرة أخرى، ستظهر لك الصورة المتحركة لهذه الخطوة.

يتبع القمر قوسًا لأنه يمر بـ 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 أثناء التنقّل بين نقطتَي البداية والنهاية.
إذا نظرت عن كثب، ستلاحظ أنّ نص الاعتمادات مقيّد بموضع القمر. لماذا لا يتحرّك عموديًا أيضًا؟

<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

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

دلتا هو مصطلح رياضي يعني التغيير، لذا فإنّ deltaRelative هي طريقة للتعبير عن "التغيير النسبي". في إحداثيات deltaRelative، يمثّل (0,0) موضع بدء العرض، ويمثّل (1,1) موضع الانتهاء. تتم محاذاة المحورين "س" و"ص" مع الشاشة.
يكون المحور X أفقيًا دائمًا على الشاشة، ويكون المحور Y عموديًا دائمًا على الشاشة. مقارنةً بـ parentRelative، الاختلاف الرئيسي هو أنّ الإحداثيات تصف الجزء من الشاشة الذي سيتحرّك فيه العرض فقط.
deltaRelative هو نظام إحداثيات رائع للتحكّم في الحركة الأفقية أو العمودية بشكل منفصل. على سبيل المثال، يمكنك إنشاء صورة متحركة تكمل حركتها العمودية (Y) فقط بنسبة %50، وتستمر في الحركة أفقيًا (X).
pathRelative coordinates

آخر نظام إحداثيات في MotionLayout هو pathRelative. يختلف هذا النوع عن النوعين الآخرين لأنّ المحور X يتّبع مسار الحركة من البداية إلى النهاية. إذًا، (0,0) هو موضع البداية، و(1,0) هو موضع النهاية.
لماذا قد تحتاج إلى ذلك؟ قد يبدو ذلك مفاجئًا للوهلة الأولى، خاصةً أنّ نظام الإحداثيات هذا لا يتوافق حتى مع نظام إحداثيات الشاشة.
اتّضح أنّ pathRelative مفيد جدًا في بعض الحالات.
- تسريع أو إبطاء أو إيقاف العرض أثناء جزء من الحركة بما أنّ البُعد X سيتطابق دائمًا مع المسار الذي تتّخذه طريقة العرض تمامًا، يمكنك استخدام
pathRelativeKeyPositionلتغييرframePositionالذي يتم الوصول إليه في نقطة معيّنة في هذا المسار. لذا، فإنّKeyPositionعندframePosition="50"معpercentX="0.1"سيؤدي إلى استغراق الصورة المتحركة% 50 من الوقت لقطع% 10 الأولى من الحركة. - إضافة قوس خفيف إلى مسار بما أنّ البُعد Y يكون دائمًا عموديًا على الحركة، سيؤدي تغيير Y إلى تغيير المسار ليصبح منحنيًا بالنسبة إلى الحركة الإجمالية.
- لن تنجح إضافة سمة ثانية عندما تكون
deltaRelativeغير متاحة. بالنسبة إلى الحركة الأفقية والعمودية تمامًا، لن تنشئdeltaRelativeسوى بُعد واحد مفيد. ومع ذلك، ستنشئpathRelativeدائمًا إحداثيات X وY قابلة للاستخدام.
في الخطوة التالية، ستتعرّف على كيفية إنشاء مسارات أكثر تعقيدًا باستخدام أكثر من KeyPosition.
7. إنشاء مسارات معقّدة
بالنظر إلى الصورة المتحركة التي أنشأتها في الخطوة الأخيرة، نلاحظ أنّها تنشئ منحنى سلسًا، ولكن يمكن أن يكون الشكل "أكثر شبهًا بالقمر".
تعديل مسار يتضمّن عناصر KeyPosition متعددة
يمكن MotionLayout تعديل مسار بشكل أكبر من خلال تحديد أكبر عدد ممكن من KeyPosition حسب الحاجة للحصول على أي حركة. في هذه الصورة المتحركة، ستنشئ قوسًا، ولكن يمكنك جعل القمر يقفز للأعلى والأسفل في منتصف الشاشة إذا أردت ذلك.
- فتح "
xml/step4.xml" ستلاحظ أنّ الفيديو يتضمّن عدد المشاهدات نفسه وKeyFrameالذي أضفته في الخطوة الأخيرة. - لإكمال الجزء العلوي من المنحنى، أضِف
KeyPositionsآخرَين إلى مسار@id/moon، أحدهما قبل أن يصل إلى الأعلى مباشرةً، والآخر بعد ذلك.

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

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

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

بما أنّك أدرت القمر دورتين كاملتين، سيقوم الآن بحركة بهلوانية مزدوجة، وسيتم تأخير ظهور الاعتمادات إلى أن تنتهي الصورة المتحركة تقريبًا.
استكشاف الموقع بنفسك
قبل الانتقال إلى النوع الأخير من KeyFrame، جرِّب تعديل السمات العادية الأخرى في KeyAttributes. على سبيل المثال، جرِّب تغيير rotation إلى rotationX لمعرفة الصورة المتحركة التي سيتم إنتاجها.
في ما يلي قائمة بالسمات العادية التي يمكنك تجربتها:
android:visibilityandroid:alphaandroid:elevationandroid:rotationandroid:rotationXandroid:rotationYandroid:scaleXandroid:scaleYandroid:translationXandroid:translationYandroid:translationZ
9- تغيير السمات المخصّصة
تتضمّن الرسوم المتحركة الغنية تغيير لون العرض أو سماته الأخرى. في حين يمكن استخدام MotionLayout لـ KeyAttribute من أجل تغيير أي من السمات العادية المدرَجة في المهمة السابقة، يمكنك استخدام CustomAttribute لتحديد أي سمة أخرى.
يمكن استخدام CustomAttribute لضبط أي قيمة تتضمّن دالة تحديد القيمة. على سبيل المثال، يمكنك ضبط backgroundColor على View باستخدام CustomAttribute. سيستخدم MotionLayout الانعكاس للعثور على طريقة تحديد القيمة، ثم سيتم استدعاؤها بشكل متكرر لتحريك العرض.
في هذه الخطوة، ستستخدم CustomAttribute لضبط السمة colorFilter على القمر لإنشاء الحركة الموضّحة أدناه.

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

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

عند إضافة المزيد من KeyFrames، يغيّر MotionLayout مسار القمر من خط مستقيم إلى منحنى معقّد، ويضيف شقلبة خلفية مزدوجة وتغييرًا في الحجم وتغييرًا في اللون في منتصف الصورة المتحركة.
في الرسوم المتحركة الحقيقية، غالبًا ما يتم تحريك عدة طرق عرض في الوقت نفسه، مع التحكّم في حركتها على طول مسارات وسرعات مختلفة. من خلال تحديد KeyFrame مختلف لكل طريقة عرض، يمكن تصميم حركات غنية تحرّك طرق عرض متعددة باستخدام MotionLayout.
10. سحب الأحداث والمسارات المعقّدة
في هذه الخطوة، سنتعرّف على كيفية استخدام OnSwipe مع المسارات المعقّدة. حتى الآن، يتم تشغيل الرسوم المتحركة للقمر من خلال أداة معالجة OnClick وتستمر لمدة ثابتة.
يتطلّب التحكّم في الصور المتحركة التي تتضمّن مسارات معقّدة باستخدام OnSwipe، مثل صورة القمر المتحركة التي أنشأتها في الخطوات القليلة السابقة، فهم طريقة عمل OnSwipe.
الخطوة 1: استكشاف سلوك OnSwipe
- افتح
xml/step7.xmlوابحث عن بيانOnSwipeالحالي.
step7.xml
<!-- Fix OnSwipe by changing touchAnchorSide →
<OnSwipe
motion:touchAnchorId="@id/moon"
motion:touchAnchorSide="bottom"
/>
- شغِّل التطبيق على جهازك وانتقِل إلى الخطوة 7. معرفة ما إذا كان بإمكانك إنشاء صورة متحركة سلسة عن طريق سحب القمر على طول مسار القوس
عند تشغيل هذه الصورة المتحركة، لن تبدو جيدة جدًا. بعد أن يصل القمر إلى أعلى القوس، يبدأ في القفز.

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

الخطوة 2: استخدام الجانب الأيمن
لتجنُّب أخطاء مثل هذه، من المهم دائمًا اختيار touchAnchorId وtouchAnchorSide يتطوّران دائمًا في اتجاه واحد طوال مدة الصورة المتحركة بأكملها.
في هذه الصورة المتحركة، سيتحرّك كل من الجانب right والجانب left من القمر على الشاشة في اتجاه واحد.
ومع ذلك، سيعكس كل من bottom وtop اتجاههما. عندما يحاول OnSwipe تتبُّعها، سيحدث تشويش عند تغيير اتجاهها.
- لجعل هذه الصورة المتحركة تتبع حدث لمس، غيِّر
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.
- حدِّث
OnSwipeلتتبُّع حركة القمر بشكل صحيح.
step7.xml
<!-- Using dragDirection to control the direction of drag tracking →
<OnSwipe
motion:touchAnchorId="@id/moon"
motion:touchAnchorSide="bottom"
motion:dragDirection="dragRight"
/>
جرّب بنفسك
- شغِّل التطبيق مرة أخرى وحاوِل سحب القمر على طول المسار بأكمله. على الرغم من أنّها تتّبع مسارًا معقّدًا، سيتمكّن
MotionLayoutمن تحريك الصورة المتحركة استجابةً لأحداث التمرير السريع.

11. تشغيل الحركة باستخدام الرمز
يمكن استخدام MotionLayout لإنشاء رسوم متحركة غنية عند استخدامها مع CoordinatorLayout. في هذه الخطوة، ستنشئ عنوانًا قابلاً للتصغير باستخدام MotionLayout.
الخطوة 1: استكشاف الرمز الحالي
- للبدء، افتح
layout/activity_step8.xml. - في
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.
- شغِّل التطبيق وانتقِل إلى الخطوة 8. يمكنك ملاحظة أنّ القمر لا يتحرّك عند تمرير النص.
الخطوة 2: جعل MotionLayout قابلاً للتمرير
- لجعل طريقة عرض
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" >
- شغِّل التطبيق مرة أخرى وانتقِل إلى الخطوة 8. يظهر لك أنّ
MotionLayoutيختفي عند التمرير سريعًا للأعلى. ومع ذلك، لا تتقدّم الصورة المتحركة استنادًا إلى سلوك التمرير حتى الآن.
الخطوة 3: تحريك الصورة المتحركة باستخدام الرمز
- افتح
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 ومستوى التقدّم كنسبة مئوية، قسِّم على إجمالي نطاق التمرير.
جرّب بنفسك
- انشر التطبيق مرة أخرى وشغِّل الرسوم المتحركة في الخطوة 8. يمكنك ملاحظة أنّ
MotionLayoutسيؤدي إلى تقدّم الرسم المتحرّك استنادًا إلى موضع التمرير.

يمكن إنشاء رسوم متحركة مخصّصة لشريط الأدوات القابل للتصغير باستخدام MotionLayout. باستخدام سلسلة من KeyFrames، يمكنك تحقيق تأثيرات جريئة جدًا.
12. تهانينا
تناول هذا الدرس التطبيقي حول الترميز واجهة برمجة التطبيقات الأساسية في MotionLayout.
للاطّلاع على المزيد من الأمثلة حول كيفية استخدام MotionLayout، يمكنك الاطّلاع على النموذج الرسمي. ننصحك أيضًا بالاطّلاع على المستندات.
مزيد من المعلومات
تتيح MotionLayout المزيد من الميزات غير المشمولة في هذا الدرس التطبيقي حول الترميز، مثل KeyCycle, التي تتيح لك التحكّم في المسارات أو السمات باستخدام دورات متكررة، وKeyTimeCycle, التي تتيح لك إنشاء رسوم متحركة استنادًا إلى وقت الساعة. اطّلِع على العيّنات للحصول على أمثلة لكلّ منها.
للحصول على روابط تؤدي إلى دروس تطبيقية حول الترميز الأخرى في هذه الدورة التدريبية، يمكنك الاطّلاع على الصفحة المقصودة للدروس التطبيقية حول الترميز الخاصة بدورة "تطبيقات متقدّمة متوافقة مع نظام Android باستخدام لغة Kotlin".