1. לפני שמתחילים
ה-Codelab הזה הוא חלק מהקורס Advanced Android in Kotlin (פיתוח מתקדם ל-Android ב-Kotlin). כדי להפיק את המרב מהקורס הזה, מומלץ לעבוד על ה-codelabs לפי הסדר, אבל זה לא חובה. כל ה-codelab של הקורס מופיעים בדף הנחיתה של Advanced Android in Kotlin codelabs.
MotionLayout היא ספרייה שמאפשרת להוסיף תנועה עשירה לאפליקציית Android. היא מבוססת על ConstraintLayout, ומאפשרת להנפיש כל דבר שאפשר ליצור באמצעות ConstraintLayout.
אפשר להשתמש ב-MotionLayout כדי להנפיש את המיקום, הגודל, הנראות, האלפא, הצבע, הגובה, הסיבוב ומאפיינים אחרים של כמה תצוגות בו-זמנית. באמצעות XML הצהרתי אפשר ליצור אנימציות מתואמות שכוללות כמה תצוגות, וקשה ליצור אותן באמצעות קוד.
אנימציות הן דרך מצוינת לשפר את חוויית השימוש באפליקציה. אתם יכולים להשתמש באנימציות כדי:
- הצגת שינויים – אנימציה בין מצבים מאפשרת למשתמש לעקוב באופן טבעי אחרי שינויים בממשק המשתמש.
- למשוך תשומת לב – אפשר להשתמש באנימציות כדי למשוך תשומת לב לרכיבים חשובים בממשק המשתמש.
- יצירת עיצובים יפים – תנועה אפקטיבית בעיצוב גורמת לאפליקציות להיראות מלוטשות.
דרישות מוקדמות
ה-Codelab הזה מיועד למפתחים עם ניסיון מסוים בפיתוח ל-Android. לפני שמנסים להשלים את ה-codelab הזה, כדאי:
- לדעת איך ליצור אפליקציה עם פעילות, פריסה בסיסית ולהפעיל אותה במכשיר או באמולטור באמצעות Android Studio. כדאי להכיר את
ConstraintLayout. מידע נוסף עלConstraintLayoutזמין בCodelab בנושא Constraint Layout.
מה תעשו
- הגדרת אנימציה באמצעות
ConstraintSetsו-MotionLayout - הנפשה על סמך אירועי גרירה
- שינוי האנימציה באמצעות
KeyPosition - שינוי מאפיינים באמצעות
KeyAttribute - הפעלת אנימציות באמצעות קוד
- יצירת אנימציה של כותרות שניתנות לכיווץ באמצעות
MotionLayout
מה תצטרכו
- Android Studio 4.0 (העורך
MotionLayoutפועל רק עם הגרסה הזו של Android Studio).
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 Studio על היעדר אילוצים.
שלב 2: המרה ל-Motion Layout
כדי ליצור אנימציה באמצעות MotionLayout,, צריך להמיר את ConstraintLayout ל-MotionLayout.
כדי שהפריסה תשתמש בסצנת תנועה, היא צריכה להצביע עליה.
- כדי לעשות את זה, פותחים את משטח העיצוב. ב-Android Studio 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, Motion Editor יוצג באזור העיצוב.

יש שלושה רכיבי ממשק משתמש חדשים ב-Motion Editor:
- סקירה כללית – זוהי תיבת דו-שיח לבחירה שמאפשרת לכם לבחור חלקים שונים של האנימציה. בתמונה הזו, נבחר
startConstraintSet. אפשר גם לבחור את המעבר ביןstartל-endבלחיצה על החץ שביניהם. - קטע – מתחת לסקירה הכללית יש חלון קטע שמשתנה בהתאם לפריט הסקירה הכללית שנבחר כרגע. בתמונה הזו, המידע על
startConstraintSetמוצג בחלון הבחירה. - מאפיין – בחלונית המאפיינים מוצגים המאפיינים של הפריט שנבחר כרגע, ואפשר לערוך אותם. אפשר לגשת לחלונית הזו מתצוגת הסקירה הכללית או מחלון הבחירה. בתמונה הזו מוצגים המאפיינים של
startConstraintSet.
שלב 3: הגדרת אילוצים של התחלה וסיום
אפשר להגדיר את כל האנימציות לפי נקודת התחלה ונקודת סיום. ההתחלה מתארת איך המסך נראה לפני האנימציה, והסוף מתאר איך המסך נראה אחרי שהאנימציה מסתיימת. MotionLayout אחראי על חישוב האופן שבו האנימציה מתבצעת בין מצב ההתחלה לבין מצב הסיום (לאורך זמן).
MotionScene משתמש בתג ConstraintSet כדי להגדיר את מצבי ההתחלה והסיום. ConstraintSet הוא בדיוק מה שמשתמע מהשם שלו – קבוצה של אילוצים שאפשר להחיל על תצוגות. זה כולל אילוצים של רוחב, גובה וConstraintLayout. הוא כולל גם מאפיינים כמו alpha. הוא לא מכיל את התצוגות עצמן, אלא רק את ההגבלות על התצוגות האלה.
כל אילוץ שמצוין ב-ConstraintSet יבטל את האילוצים שמצוינים בקובץ הפריסה. אם מגדירים אילוצים גם בפריסה וגם ב-MotionScene, רק האילוצים ב-MotionScene יחולו.
בשלב הזה, תגבילו את תצוגת הכוכבים כך שהיא תתחיל בחלק העליון של המסך ותסתיים בחלק התחתון של המסך.
אפשר להשלים את השלב הזה באמצעות Motion Editor או על ידי עריכת הטקסט של activity_step1_scene.xml ישירות.
- בוחרים את
startConstraintSet בחלונית הסקירה הכללית.

- בחלונית בחירה, בוחרים באפשרות
red_star. בשלב הזה מוצג המקור שלlayout– כלומר, אין הגבלה עלConstraintSetהזה. לוחצים על סמל העיפרון בפינה השמאלית העליונה כדי ליצור אילוץ.

- מוודאים שבעמודה
red_starמוצג מקור שלstartכשבוחרים אתstartConstraintSetבחלונית הסקירה הכללית. - בחלונית 'מאפיינים', כשהאפשרות
red_starמסומנת ב-startConstraintSet, מוסיפים אילוץ בחלק העליון ומתחילים בלחיצה על הלחצנים הכחולים +.

- פותחים את
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 לחלק העליון של רכיב האב.
- בוחרים באפשרות
endConstraintSet בחלונית הסקירה הכללית.

- כדי להוסיף
Constraintל-red_starב-endConstraintSet, חוזרים על אותם השלבים שביצעתם קודם. - כדי להשתמש ב-Motion Editor כדי להשלים את השלב הזה, מוסיפים אילוץ ל-
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

אנימציה: סרטון שבו מוצגת תצוגה מקדימה של מעבר בכלי Motion Editor
- פותחים את Motion Editor ובוחרים את המעבר על ידי לחיצה על החץ שבין
startל-endבחלונית הסקירה הכללית.

- בחלונית בחירה מוצגים רכיבי UI להפעלה וסרגל לבחירת מיקום כשבוחרים מעבר בין סצנות. כדי לראות תצוגה מקדימה של האנימציה, לוחצים על סמל ההפעלה או גוררים את המיקום הנוכחי.

שלב 6: מוסיפים handler של קליק
צריך דרך להתחיל את האנימציה. אחת הדרכים לעשות זאת היא לגרום ל-MotionLayout להגיב לאירועי לחיצה על @id/red_star.
- פותחים את עורך התנועה ובוחרים את המעבר על ידי לחיצה על החץ שבין ההתחלה לסיום בחלונית הסקירה הכללית.

- בסרגל הכלים של חלונית הסקירה הכללית, לוחצים על
יצירת handler של לחיצה או החלקה . הפעולה הזו מוסיפה handler שיתחיל מעבר. - בוחרים באפשרות Click Handler (הפונקציה לטיפול בקליקים) בחלון הקופץ.

- משנים את View To Click (תצוגה ללחיצה) ל-
red_star.

- לוחצים על הוספה. נקודה קטנה מייצגת את ה-click handler במעבר בכלי לעריכת תנועה.

- כשאלמנט המעבר מסומן בחלונית הסקירה הכללית, מוסיפים מאפיין
clickActionעם הערךtoggleל-handler של OnClick שזה עתה הוספתם בחלונית המאפיינים.

- פותחים את
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, ואז לוחצים על הכוכב האדום כדי לראות את האנימציה.
שלב 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, וסיום 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 מאזין לאירועי גרירה, ה-listener יירשם בתצוגה MotionLayout ולא בתצוגה שצוינה על ידי touchAnchorId. כשמשתמש מתחיל לבצע תנועת מגע בכל מקום במסך, MotionLayout ישמור על המרחק בין האצבע שלו לבין touchAnchorSide של התצוגה touchAnchorId קבוע. לדוגמה, אם הם יגעו במרחק של 100dp מהצד של נקודת העיגון, 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 שנמצאת 50% למטה במסך. הפעולה הזו 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 שמוגדרים על ידי שני קווים מאונכים. ההבדל העיקרי ביניהם הוא המיקום של ציר ה-X במסך (ציר ה-Y תמיד ניצב לציר ה-X).
בכל מערכות הקואורדינטות ב-MotionLayout נעשה שימוש בערכים בין 0.0 ל-1.0 בציר X ובציר Y. אפשר להזין ערכים שליליים וערכים גדולים מ-1.0. לדוגמה, ערך percentX של -2.0 אומר: צריך ללכת פעמיים בכיוון ההפוך של ציר X.
אם כל זה נשמע לכם כמו שיעור אלגברה, כדאי לעיין בתמונות שלמטה.
parentRelative coordinates

ה-keyPositionType של parentRelative משתמש באותה מערכת קואורדינטות כמו המסך. הוא מגדיר את (0, 0) בפינה הימנית העליונה של כל MotionLayout, ואת (1, 1) בפינה השמאלית התחתונה.
אתם יכולים להשתמש ב-parentRelative בכל פעם שאתם רוצים ליצור אנימציה שמתקדמת לאורך כל MotionLayout – כמו קשת הירח בדוגמה הזו.
עם זאת, אם רוצים לשנות את הנתיב ביחס לתנועה, למשל לגרום לו להתעגל קצת, עדיף להשתמש בשתי מערכות הקואורדינטות האחרות.
קואורדינטות deltaRelative

דלתא היא מונח מתמטי לשינוי, ולכן deltaRelative היא דרך לומר "שינוי יחסי". deltaRelative coordinates(0,0) היא נקודת ההתחלה של התצוגה, ו-(1,1) היא נקודת הסיום. הצירים X ו-Y מיושרים עם המסך.
ציר ה-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", האנימציה תנוע 10% מהדרך ב-50% מהזמן. - הוספת קשת עדינה לנתיב. מכיוון שהמימד Y תמיד ניצב לתנועה, שינוי של Y ישנה את הנתיב לעקומה ביחס לתנועה הכוללת.
- הוספת מאפיין משני כש
deltaRelativeלא תפעל. אם התנועה היא אופקית או אנכית לחלוטין, הפונקציהdeltaRelativeתיצור רק מימד אחד שימושי. עם זאת,pathRelativeתמיד תיצור קואורדינטות X ו-Y שניתן להשתמש בהן.
בשלב הבא נסביר איך ליצור נתיבים מורכבים יותר באמצעות יותר מ-KeyPosition אחד.
7. יצירת נתיבים מורכבים
אם מסתכלים על האנימציה שיצרתם בשלב הקודם, רואים שהיא יוצרת עקומה חלקה, אבל הצורה יכולה להיות יותר 'כמו ירח'.
שינוי נתיב עם כמה רכיבי KeyPosition
אפשר לשנות את הנתיב עוד יותר על ידי הגדרה של כמה KeyPosition שרוצים כדי לקבל תנועה כלשהי.MotionLayout באנימציה הזו תבנו קשת, אבל אם תרצו תוכלו לגרום לירח לקפוץ למעלה ולמטה באמצע המסך.
- פתיחת
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% מהאנימציה. ההגדלה הראשונה ל-50% תתרחש בחלק העליון של הקשת, ותגרום להגדלת התצוגה פי שניים ולסיבוב של 360- מעלות (או סיבוב מלא אחד).KeyAttribute הפונקציה השנייה 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 כדי להגדיר כל ערך שיש לו setter. לדוגמה, אפשר להגדיר את backgroundColor ב-View באמצעות CustomAttribute. MotionLayout ישתמש ברפלקציה כדי למצוא את הפונקציה setter, ואז יקרא לה שוב ושוב כדי להנפיש את התצוגה.
בשלב הזה, משתמשים ב-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הוא ערך מותאם אישית מהסוג שצוין בשם. בדוגמה הזו, הערך המותאם אישית הוא צבע שצוין.
ערכים מותאמים אישית יכולים להיות מהסוגים הבאים:
- צבע
- מספר שלם
- מספר ממשי (float)
- מחרוזת
- מאפיין
- בוליאני
באמצעות ה-API הזה, MotionLayout יכול להנפיש כל דבר שמספק שיטת setter בכל תצוגה.
רוצים לנסות?
- מפעילים שוב את האפליקציה ועוברים אל שלב 6 כדי לראות את האנימציה בפעולה. כשלוחצים על הירח, הוא נע לאורך הנתיב מההתחלה ועד הסוף, ועובר דרך כל
KeyAttributeשצוין ב-KeyFrameSet.

כשמוסיפים עוד KeyFrames, MotionLayout משנה את הנתיב של הירח מקו ישר לעקומה מורכבת, ומוסיף היפוך כפול לאחור, שינוי גודל ושינוי צבע באמצע האנימציה.
באנימציות אמיתיות, לרוב מנפישים כמה תצוגות בו-זמנית, ושולטים בתנועה שלהן לאורך נתיבים ומהירויות שונים. אם מציינים ערך שונה של KeyFrame לכל תצוגה, אפשר ליצור אנימציות עשירות שמופעלות בכמה תצוגות עם MotionLayout.
10. גרירת אירועים ונתיבים מורכבים
בשלב הזה נסביר איך להשתמש ב-OnSwipe עם נתיבים מורכבים. עד עכשיו, האנימציה של הירח הופעלה על ידי רכיב OnClick listener ורצה למשך זמן קבוע.
כדי לשלוט באנימציות עם נתיבים מורכבים באמצעות 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 תומך בחיפוש המעבר שלו על ידי הגדרת מאפיין ההתקדמות. כדי להמיר בין verticalOffset לבין אחוז התקדמות, מחלקים בטווח הגלילה הכולל.
רוצים לנסות?
- מפעילים מחדש את האפליקציה ומריצים את האנימציה של שלב 8. אפשר לראות שהאנימציה של
MotionLayoutתתקדם בהתאם למיקום הגלילה.

אפשר ליצור אנימציות מותאמות אישית של סרגל כלים דינמי שניתן לכווץ באמצעות MotionLayout. אפשר להשתמש ברצף של KeyFrames כדי ליצור אפקטים בולטים מאוד.
12. מזל טוב
ב-Codelab הזה למדנו על ה-API הבסיסי של MotionLayout.
כדי לראות עוד דוגמאות לשימוש ב-MotionLayout, אפשר לעיין בדוגמה הרשמית. מומלץ לעיין גם בתיעוד.
מידע נוסף
MotionLayout תומך בעוד תכונות שלא מוסברות ב-codelab הזה, כמו KeyCycle, שמאפשרת לכם לשלוט בנתיבים או במאפיינים באמצעות מחזורים חוזרים, ו-KeyTimeCycle, שמאפשרת לכם ליצור אנימציה על סמך שעת השעון. כדאי לעיין בדוגמאות לכל אחת מהאפשרויות.
קישורים ל-codelab אחרים בקורס הזה מופיעים בדף הנחיתה של ה-codelab בנושא Android מתקדם ב-Kotlin.