1. לפני שמתחילים
ה-Codelab הזה הוא חלק מהקורס Advanced Android ב-Kotlin. כדי שתפיקו את המרב מהקורס הזה אם תעבדו על ה-codelabs ברצף, אבל זו לא חובה. כל שיעורי ה-codelabs מפורטים בדף הנחיתה של Advanced Android ב-Kotlin Codelabs.
MotionLayout
היא ספרייה שמאפשרת להוסיף תנועה עשירה לאפליקציה ל-Android. היא מבוססת על ConstraintLayout,
ומאפשרת לכם להוסיף אנימציה לכל דבר שאפשר ליצור באמצעות ConstraintLayout
.
אפשר להשתמש ב-MotionLayout
כדי להנפיש את המיקום, הגודל, החשיפה, אלפא, הצבע, הגובה, הסיבוב ומאפיינים אחרים של כמה תצוגות בו-זמנית. באמצעות XML הצהרתי אפשר ליצור אנימציות מתואמות שכוללות מספר תצוגות, שקשה להשיג בקוד.
אנימציות הן דרך נהדרת לשפר חוויה באפליקציה. אפשר להשתמש באנימציות כדי:
- הצגת שינויים – אנימציה בין מצבים מאפשרת למשתמש לעקוב באופן טבעי אחרי שינויים בממשק המשתמש.
- משיכת תשומת לב — אפשר להשתמש באנימציות כדי למשוך תשומת לב לרכיבים חשובים בממשק המשתמש.
- בניית עיצובים מדהימים — תנועה יעילה בעיצוב משפרת את מראה האפליקציות.
דרישות מוקדמות
ה-Codelab הזה תוכנן למפתחים עם ניסיון בפיתוח מסוים ב-Android. לפני שתנסו להשלים את ה-Codelab הזה, אתם צריכים:
- חשוב לדעת איך ליצור אפליקציה עם פעילות ופריסה בסיסית, ולהריץ אותה במכשיר או באמולטור באמצעות Android Studio. חשוב להכיר את
ConstraintLayout
. כדי לקבל מידע נוסף עלConstraintLayout
, אפשר לעיין בקוד Lab לפריסת אילוצים.
מה תעשו
- הגדרת אנימציה עם
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: המרה לפריסת תנועה
כדי להוסיף אנימציה באמצעות MotionLayout,
, צריך להמיר את ConstraintLayout
לMotionLayout
.
כדי שהפריסה תתבסס על סצנת תנועה, היא צריכה להצביע אליה.
- כדי לעשות את זה, פותחים את פלטפורמת העיצוב. ב-Android Studio 4.0, פותחים את שטח העיצוב באמצעות סמל המפוצל או העיצוב שבפינה השמאלית העליונה, כשמעיינים בקובץ XML לפריסה.
- אחרי שפותחים את משטח העיצוב, לוחצים לחיצה ימנית על התצוגה המקדימה ובוחרים באפשרות המרה לפריסה עם תנועה.
הפעולה הזו מחליפה את התג 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
, במסך העיצוב יוצג ה'עורך השינויים'
בעורך התנועה יש שלושה רכיבים חדשים בממשק המשתמש:
- סקירה כללית – זוהי אפשרות בחירה מודאלית שמאפשרת לבחור חלקים שונים של האנימציה. בתמונה הזו נבחר המאפיין
start
ConstraintSet
. אפשר גם לבחור את המעבר ביןstart
לביןend
על-ידי לחיצה על החץ ביניהם. - קטע – מתחת לסקירה הכללית מופיע חלון של קטע שמשתנה בהתאם לפריט הסקירה הכללית הנוכחי שנבחר. בתמונה הזו, המידע
start
ConstraintSet
מוצג בחלון הבחירה. - מאפיין – חלונית המאפיינים מוצגת ומאפשרת לערוך את המאפיינים של הפריט הנוכחי שנבחר דרך חלון הסקירה הכללית או חלון הבחירה. בתמונה הזו מוצגים המאפיינים של
start
ConstraintSet
.
שלב 3: הגדרת מגבלות התחלה וסיום
כל האנימציות יכולות להיות מוגדרות בתור התחלה וסיום. ההתחלה מתארת את המראה של המסך לפני האנימציה, ובסופו מתואר איך המסך נראה בסיום האנימציה. MotionLayout
אחראי להבין איך ליצור אנימציה בין מצב ההתחלה למצב הסיום (לאורך זמן).
הפרמטר MotionScene
משתמש בתג ConstraintSet
כדי להגדיר את מצבי ההתחלה והסיום. ConstraintSet
הוא האופן שבו הוא נשמע, קבוצת אילוצים שאפשר להחיל על צפיות. כולל מגבלות של רוחב, גובה ו-ConstraintLayout
. הוא כולל גם כמה מאפיינים כמו alpha
. הוא לא מכיל את התצוגות עצמן, אלא רק את המגבלות על הצפיות האלה.
כל אילוצים שצוינו ב-ConstraintSet
יבטלו את המגבלות שצוינו בקובץ הפריסה. אם מגדירים אילוצים גם בפריסה וגם ב-MotionScene
, יחולו רק האילוצים ב-MotionScene
.
בשלב הזה, תיגבלו את תצוגת הכוכב כך שתתחיל בהתחלה העליונה של המסך ותסתיים בקצה התחתון של המסך.
אפשר להשלים את השלב הזה באמצעות הכלי לעריכת תמונות, או על ידי עריכת הטקסט של activity_step1_scene.xml
ישירות.
- צריך לבחור את ConstraintSet
start
בחלונית הסקירה הכללית
- בחלונית הבחירה בוחרים באפשרות
red_star
. כרגע מוצג בו המקור שלlayout
, כלומר הוא לא מוגבל בConstraintSet
. לוחצים על סמל העיפרון בפינה השמאלית העליונה כדי ליצור הגבלה.
- מוודאים ש-
red_star
מציג מקור שלstart
כאשרstart
ConstraintSet
נבחר בחלונית הסקירה הכללית. - בחלונית 'מאפיינים', שבה האפשרות
red_star
נבחרה בstart
ConstraintSet
, מוסיפים הגבלה בחלק העליון ומתחילים ללחוץ על לחצני + הכחולים.
- פותחים את
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
להתחלה העליונה של רכיב ההורה.
- צריך לבחור באפשרות
end
ConstraintSet בחלונית הסקירה הכללית.
- עליך לפעול לפי אותם השלבים שביצעת לפני כן כדי להוסיף
Constraint
לred_star
בend
ConstraintSet
. - כדי להשתמש ב'עורך התנועה' להשלמת השלב הזה, מוסיפים אילוץ ל-
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
לביצוע המעבר. מעבר יכול גם לציין כיצד לשנות את האנימציה בדרכים אחרות, כמו משך הזמן להריץ את האנימציה או איך להוסיף אנימציה על ידי גרירת התצוגות.
- 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: תצוגה מקדימה של האנימציה ב-Motion Editor
אנימציה: סרטון של הפעלת תצוגה מקדימה של מעבר בעורך התנועה
- פותחים את 'עורך התנועה' ובוחרים את המעבר באמצעות לחיצה על החץ בין
start
לביןend
בחלונית הסקירה הכללית.
- בחלונית בחירה מוצגים פקדי הפעלה וסרגל ניווט כאשר נבחר מעבר. לוחצים על 'הפעלה' או גוררים את המיקום הנוכחי כדי להציג תצוגה מקדימה של האנימציה.
שלב 6: הוספה של handler של קליקים
צריכה להיות לכם דרך להתחיל את האנימציה. דרך אחת לעשות זאת היא לגרום ל-MotionLayout
להגיב לאירועי קליקים ב-@id/red_star
.
- פותחים את עורך התנועה ובוחרים במעבר על ידי לחיצה על החץ בין ההתחלה לסיום בחלונית הסקירה הכללית.
- לוחצים על יצירת handler של לחיצה או החלקה בסרגל הכלים של חלונית הסקירה הכללית . הפעולה הזו מוסיפה handler שיתחיל מעבר.
- בוחרים באפשרות מטפל קליקים בחלון הקופץ
- משנים את הערך View to click ל-
red_star
.
- לוחצים על Add (הוספה). מטפל הקליקים מיוצג על ידי נקודה קטנה בכלי המעבר בעורך התנועה.
- כשהמעבר נבחר בחלונית הסקירה הכללית, מוסיפים מאפיין
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,
וסימן 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
.
- מחליפים את הפעולה לביצוע להוספת תג
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>
יש 3 סוגים של 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.
אם זה נשמע קצת יותר מדי כמו שיעור אלגברה, אתם מוזמנים לראות את התמונות שלמטה.
קואורדינטות יחסיות של הורה
ב-keyPositionType
של parentRelative
נעשה שימוש באותה מערכת קואורדינטות כמו במסך. הוא מגדיר את (0, 0)
מצד שמאל למעלה של MotionLayout
כולו, ואת (1, 1)
מצד ימין למטה.
אפשר להשתמש בפונקציה parentRelative
בכל פעם שרוצים ליצור אנימציה שזזה לאורך כל MotionLayout
– כמו קשת הירח בדוגמה הזו.
עם זאת, אם רוצים לשנות נתיב ביחס לתנועה, לדוגמה להפוך אותו לעקומה קצת, מומלץ להשתמש בשתי מערכות הקואורדינטות האחרות.
קואורדינטות יחסיות
דלתא היא מונח מתמטי לשינוי, כך שdeltaRelative
הוא דרך לומר 'שינוי יחסי'. בקואורדינטות deltaRelative
(0,0)
הוא המיקום ההתחלתי של התצוגה, ו-(1,1)
הוא המיקום הסופי. הצירים X ו-Y מיושרים למסך.
ציר ה-X הוא תמיד אופקי במסך, וציר ה-Y תמיד אנכי על המסך. בהשוואה ל-parentRelative
, ההבדל העיקרי הוא שהקואורדינטות מתארות רק את החלק במסך שבו התצוגה תזוז.
deltaRelative
הוא מערכת קואורדינטות מעולה לשליטה בתנועה האופקית או האנכית באופן מבודד. לדוגמה, אפשר ליצור אנימציה שמשלימה רק את התנועה האנכית (Y) שלה ב-50%, וממשיכה להוסיף אנימציה לרוחב (X).
קואורדינטות יחסיות
מערכת הקואורדינטות האחרונה ב-MotionLayout
היא pathRelative
. הוא שונה מהשניים האחרים, מכיוון שציר ה-X עוקב אחר נתיב התנועה מתחילתו ועד סופו. לכן, (0,0)
הוא מיקום ההתחלה, ו-(1,0)
הוא מיקום הסיום.
למה היית רוצה לעשות את זה? במבט ראשון זה מפתיע, במיוחד מפני שמערכת הקואורדינטות הזו אפילו לא מותאמת למערכת הקואורדינטות של המסך.
מתברר ש-pathRelative
ממש שימושי לכמה דברים.
- האצה, האטה או עצירה של צפייה במהלך חלק מהאנימציה. המימד X תמיד יתאים בדיוק לנתיב בתצוגה המפורטת, ולכן אפשר להשתמש ב-
pathRelative
KeyPosition
כדי לשנות אתframePosition
הנקודה המסוימת בנתיב הזה. לכן,KeyPosition
ב-framePosition="50"
עםpercentX="0.1"
יגרום לאנימציה להימשך 50% מהזמן ב-10% הראשונים של התנועה. - הוספת קשת מעודנת לנתיב. מכיוון שהממד Y תמיד מאונך לתנועה, שינוי של Y ישנה את הנתיב לעקומה ביחס לתנועה הכוללת.
- הוספת מאפיין שני כאשר
deltaRelative
לא תפעל. לתנועה אופקית ואנכית לחלוטין,deltaRelative
ייצור רק מימד שימושי אחד. עם זאת,pathRelative
תמיד ייצור קואורדינטות X ו-Y שניתן להשתמש בהן.
בשלב הבא תלמדו איך ליצור נתיבים מורכבים יותר באמצעות יותר משדה KeyPosition
אחד.
7. מסלולים מורכבים של מבנים
בחינת האנימציה שבנית בשלב האחרון יוצרת עקומה חלקה, אבל הצורה יכולה להיות יותר כמו על ירח.
שינוי נתיב שיש בו כמה רכיבי KeyPosition
MotionLayout
יכול לשנות נתיב עוד יותר על ידי הגדרה של כמה KeyPosition
שצריך כדי לקבל כל תנועה. באנימציה הזו תצרו קשת, אבל תוכלו לגרום לירח לקפוץ למעלה ולמטה באמצע המסך, אם תרצו.
- פתיחת
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:visibility
android:alpha
android:elevation
android:rotation
android:rotationX
android:rotationY
android:scaleX
android:scaleY
android:translationX
android:translationY
android:translationZ
9. שינוי מאפיינים מותאמים אישית
אנימציות עשירות כוללות שינוי הצבע או מאפיינים אחרים של תצוגה. ל-MotionLayout
יש אפשרות להשתמש ב-KeyAttribute
כדי לשנות כל אחד מהמאפיינים הרגילים שמפורטים במשימה הקודמת, אבל המאפיין CustomAttribute
משמש לציון כל מאפיין אחר.
אפשר להשתמש ב-CustomAttribute
כדי להגדיר כל ערך שיש לו מגדיר. לדוגמה, אפשר להגדיר את backgroundColor בתצוגה באמצעות 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
הוא ערך מותאם אישית מהסוג שצוין בשם. בדוגמה הזו הערך המותאם אישית הוא צבע שצוין.
ערכים מותאמים אישית יכולים להיות מהסוגים הבאים:
- צבע
- מספר שלם
- Float
- מחרוזת
- מאפיין
- בוליאני
באמצעות ה-API הזה, MotionLayout
יכול להוסיף אנימציה לכל דבר שמספק רכיב הגדרה בכל תצוגה.
רוצים לנסות?
- מפעילים שוב את האפליקציה ועוברים לשלב 6 כדי לראות את האנימציה בפעולה. כשלוחצים על הירח, הוא יעקוב אחרי הנתיב מתחילתו ועד סופו, דרך כל
KeyAttribute
שצוין בKeyFrameSet
.
כשמוסיפים עוד KeyFrames
, MotionLayout
משנה את נתיב הירח מקו ישר לעקומה מורכבת, ומוסיפה היפוך כפול, שינוי גודל ושינוי צבע באמצע האנימציה.
באנימציות אמיתיות, לרוב תשתמשו בהנפשה של מספר צפיות בו-זמנית כדי לשלוט בתנועה שלהן לאורך נתיבים שונים ומהירויות שונות. ציון KeyFrame
שונה לכל תצוגה מאפשר לבצע כוריאוגרפיה של אנימציות עשירות עם אנימציה של מספר צפיות באמצעות MotionLayout
.
10. אפשר לגרור אירועים ונתיבים מורכבים
בשלב הזה תלמדו להשתמש ב-OnSwipe
עם נתיבים מורכבים. עד עכשיו, האנימציה של הירח הופעלה על ידי מאזין OnClick
, והיא פועלת למשך זמן קבוע.
כדי לשלוט באנימציות עם נתיבים מורכבים באמצעות OnSwipe
, כמו אנימציית הירח שבנית בשלבים האחרונים, נדרשת הבנה של אופן הפעולה של OnSwipe
.
שלב 1: בדיקת ההתנהגות בזמן החלקה
- צריך לפתוח את
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,
שמאפשר ליצור אנימציה לפי זמן השעון. עיינו בדוגמאות כדי לראות דוגמאות של כל אחד מהם.
לפרטים על קישורים לשיעורי Lab אחרים בקורס הזה, ראו דף הנחיתה של שיעורי Lab מתקדמים ל-Android ב-Kotlin.