סקירה כללית של אנימציה של נכס

אפשר לנסות את הדרך של כתיבת הודעה
‫Jetpack Compose היא ערכת הכלים המומלצת לבניית ממשק משתמש ב-Android. איך משתמשים באנימציות במצב כתיבה

מערכת האנימציה של מאפיינים היא מסגרת חזקה שמאפשרת לכם להוסיף אנימציה כמעט לכל דבר. אפשר להגדיר אנימציה שתשנה את המאפיינים של אובייקט מסוים לאורך זמן, בלי קשר לשאלה אם הוא מוצג על המסך או לא. אנימציה של מאפיין משנה את הערך של מאפיין (שדה באובייקט) לאורך פרק זמן מוגדר. כדי להנפיש משהו, מציינים את מאפיין האובייקט שרוצים להנפיש, כמו המיקום של האובייקט במסך, משך ההנפשה והערכים שרוצים להנפיש.

מערכת האנימציה של המאפיינים מאפשרת לכם להגדיר את המאפיינים הבאים של אנימציה:

  • משך: אפשר לציין את משך האנימציה. אורך ברירת המחדל הוא 300 אלפיות השנייה.
  • אינטרפולציה של זמן: אתם יכולים לציין איך הערכים של המאפיין מחושבים כפונקציה של הזמן שחלף מאז תחילת האנימציה.
  • מספר החזרות וההתנהגות: אפשר לציין אם האנימציה תחזור על עצמה כשהיא תגיע לסוף משך הזמן שלה, וכמה פעמים היא תחזור על עצמה. אפשר גם לציין אם רוצים שהאנימציה תופעל הפוך. אם מגדירים את האפשרות הזו לערך הפוך, האנימציה תופעל קדימה ואז אחורה שוב ושוב, עד שמגיעים למספר החזרות שהוגדר.
  • ערכות של אנימציות: אפשר לקבץ אנימציות לערכות לוגיות שמופעלות יחד, ברצף או אחרי השהיות מוגדרות.
  • השהיה ברענון המסגרת: אתם יכולים לציין באיזו תדירות לרענן את המסגרות של האנימציה. הערך שמוגדר כברירת מחדל הוא רענון כל 10 אלפיות השנייה, אבל המהירות שבה האפליקציה יכולה לרענן את הפריימים תלויה בסופו של דבר בעומס הכולל על המערכת ובמהירות שבה המערכת יכולה לטפל בטיימר הבסיסי.

כדי לראות דוגמה מלאה של אנימציה של מאפיין, אפשר לעיין במחלקה ChangeColor בדוגמה CustomTransition ב-GitHub.

איך פועלת אנימציה של מאפיינים

קודם נסביר איך אנימציה פועלת באמצעות דוגמה פשוטה. איור 1 מציג אובייקט היפותטי עם אנימציה שנוצרת באמצעות המאפיין x שלו, שמייצג את המיקום האופקי שלו במסך. משך האנימציה מוגדר ל-40 אלפיות השנייה, והמרחק למעבר הוא 40 פיקסלים. כל 10 אלפיות השנייה, שזה קצב רענון המסגרת שמוגדר כברירת מחדל, האובייקט זז אופקית ב-10 פיקסלים. בסוף 40 אלפיות השנייה, האנימציה נעצרת והאובייקט מגיע למיקום אופקי 40. זו דוגמה לאנימציה עם אינטרפולציה לינארית, כלומר האובייקט נע במהירות קבועה.

איור 1. דוגמה לאנימציה לינארית

אפשר גם לציין שהאנימציות יכללו אינטרפולציה לא לינארית. איור 2 מציג אובייקט היפותטי שמאיץ בתחילת האנימציה ומאט בסוף האנימציה. האובייקט עדיין נע 40 פיקסלים ב-40 אלפיות השנייה, אבל לא בצורה ליניארית. בתחילת האנימציה, המהירות שלה עולה עד אמצע הדרך, ואז היא יורדת מאמצע הדרך ועד סוף האנימציה. כפי שמוצג באיור 2, המרחק שעוברים בתחילת האנימציה ובסופה קטן יותר מהמרחק שעוברים באמצע.

איור 2. דוגמה לאנימציה לא לינארית

בואו נבחן בפירוט איך הרכיבים החשובים של מערכת האנימציה של המאפיינים מחשבים אנימציות כמו אלה שמוצגות באיור שלמעלה. איור 3 מציג את אופן הפעולה של המחלקות העיקריות זו עם זו.

איור 3. איך האנימציות מחושבות

האובייקט ValueAnimator עוקב אחרי התזמון של האנימציה, למשל כמה זמן האנימציה פועלת, והערך הנוכחי של המאפיין שהאנימציה מופעלת בו.

התג ValueAnimator כולל את התג TimeInterpolator, שמגדיר את האינטרפולציה של האנימציה, ואת התג TypeEvaluator, שמגדיר איך לחשב את הערכים של המאפיין שמונפש. לדוגמה, באיור 2, הערך של TimeInterpolator used יהיה AccelerateDecelerateInterpolator והערך של TypeEvaluator יהיה IntEvaluator.

כדי להתחיל אנימציה, יוצרים אובייקט ValueAnimator ומגדירים בו את ערכי ההתחלה והסיום של המאפיין שרוצים להנפיש, וגם את משך האנימציה. כשמתקשרים אל start(), האנימציה מתחילה. במהלך האנימציה, ValueAnimator מחשב elapsed fraction בין 0 ל-1, על סמך משך האנימציה והזמן שחלף. הערך elapsed fraction מייצג את אחוז הזמן שחלף מתחילת האנימציה. 0 מייצג 0% ו-1 מייצג 100%. לדוגמה, באיור 1, השבר שחלף בזמן t = 10 ms יהיה ‎ .25‎ כי משך הזמן הכולל הוא t = 40 ms.

כשהפונקציה ValueAnimator מסיימת לחשב את השבר של הזמן שחלף, היא קוראת לפונקציה TimeInterpolator שהוגדרה כרגע, כדי לחשב שבר משוער. שבר שעבר אינטרפולציה ממפה את השבר שחלף לשבר חדש שמתחשב באינטרפולציה של הזמן שהוגדרה. לדוגמה, באיור 2, מכיוון שהאנימציה מאיצה לאט, החלק המחושב, בערך 0 .15, קטן מהחלק שחלף, 0.25, בזמן t = 10 ms. באיור 1, החלק המחושב תמיד זהה לחלק שחלף.

כשמחושב השבר המחושב, הפונקציה ValueAnimator קוראת לפונקציה המתאימה TypeEvaluator כדי לחשב את ערך המאפיין שמונפש, על סמך השבר המחושב, ערך ההתחלה וערך הסיום של האנימציה. לדוגמה, באיור 2, השבר המשוער היה 0 .15 ב-t = 10 ms, ולכן הערך של המאפיין באותו זמן יהיה 0 .15 × (40 - 0), או 6.

מה ההבדל בין אנימציה של מאפיינים לבין אנימציה של תצוגה

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

חיסרון נוסף של מערכת האנימציה של התצוגה הוא שהיא שינתה רק את המיקום שבו התצוגה צוירה, ולא את התצוגה עצמה. לדוגמה, אם יצרתם אנימציה של לחצן כדי להזיז אותו על המסך, הלחצן מצויר בצורה נכונה, אבל המיקום בפועל שבו אפשר ללחוץ על הלחצן לא משתנה, ולכן צריך להטמיע לוגיקה משלכם כדי לטפל בזה.

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

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

סקירה כללית על ממשקי API

רוב ממשקי ה-API של מערכת הנפשת המאפיינים נמצאים ב-android.animation. מכיוון שמערכת האנימציה של התצוגה כבר מגדירה הרבה פונקציות אינטרפולציה ב-android.view.animation, אפשר להשתמש בפונקציות האלה גם במערכת האנימציה של המאפיינים. בטבלאות הבאות מתוארים הרכיבים העיקריים של מערכת האנימציה של המאפיינים.

המחלקת Animator מספקת את המבנה הבסיסי ליצירת אנימציות. בדרך כלל לא משתמשים במחלקה הזו ישירות, כי היא מספקת רק פונקציונליות מינימלית שצריך להרחיב כדי לתמוך באופן מלא בהנפשת ערכים. מחלקות המשנה הבאות מרחיבות את Animator:

טבלה 1. אנימטורים

דרגה תיאור
ValueAnimator מנוע התזמון הראשי לאנימציה של מאפיינים, שמחשב גם את הערכים של המאפיין שרוצים להנפיש. הוא כולל את כל הפונקציונליות הבסיסית שמחשבת ערכי אנימציה, ומכיל את פרטי התזמון של כל אנימציה, מידע על חזרה של אנימציה, מאזינים שמקבלים אירועי עדכון ואפשרות להגדיר סוגים מותאמים אישית להערכה. יש שני חלקים בהנפשת מאפיינים: חישוב הערכים המונפשים והגדרת הערכים האלה באובייקט ובמאפיין שמנפישים. ‫ValueAnimator לא מבצע את החלק השני, ולכן צריך להאזין לעדכונים של ערכים שמחושבים על ידי ValueAnimator ולשנות את האובייקטים שרוצים להנפיש באמצעות לוגיקה משלכם. מידע נוסף זמין בקטע בנושא הנפשה באמצעות ValueAnimator.
ObjectAnimator מחלקת משנה של ValueAnimator שמאפשרת להגדיר אובייקט יעד ומאפיין אובייקט להנפשה. המחלקות האלה מעדכנות את המאפיין בהתאם כשמחושב ערך חדש לאנימציה. מומלץ להשתמש ב-ObjectAnimator ברוב המקרים, כי הוא מקל מאוד על תהליך האנימציה של ערכים באובייקטים של היעד. עם זאת, לפעמים כדאי להשתמש ישירות ב-ValueAnimator כי יש ל-ObjectAnimator כמה הגבלות נוספות, כמו דרישה לשימוש בשיטות גישה ספציפיות באובייקט היעד.
AnimatorSet מספק מנגנון לקיבוץ אנימציות כך שהן יפעלו ביחס זו לזו. אתם יכולים להגדיר שאנימציות יפעלו יחד, ברצף או אחרי השהיה שצוינה. מידע נוסף זמין בקטע בנושא כוריאוגרפיה של כמה אנימציות באמצעות ערכות Animator.

הפונקציות Evaluator אומרות למערכת האנימציה של המאפיינים איך לחשב ערכים של מאפיין נתון. הם מקבלים את נתוני התזמון שמסופקים על ידי מחלקה Animator, את ערך ההתחלה והסיום של האנימציה, ומחשבים את הערכים המונפשים של המאפיין על סמך הנתונים האלה. מערכת האנימציה של הנכס מספקת את המעריכים הבאים:

טבלה 2. מעריכים

Class/Interface תיאור
IntEvaluator הערך שמוגדר כברירת מחדל לחישוב ערכים של מאפייני int.
FloatEvaluator הערך שמוגדר כברירת מחדל לחישוב ערכים למאפיינים של float.
ArgbEvaluator הפונקציה שמוגדרת כברירת מחדל לחישוב ערכים של מאפייני צבע שמיוצגים כערכים הקסדצימליים.
TypeEvaluator ממשק שמאפשר לכם ליצור בודק משלכם. אם מנפישים מאפיין של אובייקט שהוא לא int,‏ float או צבע, צריך להטמיע את הממשק TypeEvaluator כדי לציין איך לחשב את הערכים המונפשים של מאפיין האובייקט. אפשר גם לציין ערך מותאם אישית של TypeEvaluator לint, לfloat ולצבע, אם רוצים לעבד את הסוגים האלה באופן שונה מההתנהגות שמוגדרת כברירת מחדל. מידע נוסף על כתיבת כלי הערכה מותאם אישית זמין בקטע שימוש ב-TypeEvaluator.

אינטרפולטור זמן מגדיר איך ערכים ספציפיים באנימציה מחושבים כפונקציה של הזמן. לדוגמה, אפשר לציין שאנימציות יפעלו באופן לינארי לאורך כל האנימציה, כלומר האנימציה תנוע באופן שווה לאורך כל הזמן, או לציין שאנימציות ישתמשו בזמן לא לינארי, למשל, האצה בהתחלה והאטה בסוף האנימציה. בטבלה 3 מתוארים האובייקטים מסוג Interpolator שנכללים ב-android.view.animation. אם אף אחד מהאינטרפולטורים שסופקו לא מתאים לצרכים שלכם, אתם יכולים להטמיע את הממשק TimeInterpolator וליצור אינטרפולטור משלכם. מידע נוסף על כתיבת אינטרפולטור מותאם אישית זמין במאמר שימוש באינטרפולטורים.

טבלה 3. אינטרפולטורים

Class/Interface תיאור
AccelerateDecelerateInterpolator אינטרפולטור ששיעור השינוי שלו מתחיל ומסתיים לאט, אבל מואץ באמצע.
AccelerateInterpolator אינטרפולטור ששיעור השינוי שלו מתחיל לאט ואז מואץ.
AnticipateInterpolator אינטרפולטור שהשינוי שלו מתחיל לאחור ואז מתקדם קדימה.
AnticipateOvershootInterpolator פונקציית אינטרפולציה שהשינוי שלה מתחיל לאחור, מתקדם קדימה ועובר את ערך היעד, ואז חוזר לערך הסופי.
BounceInterpolator אינטרפולטור שהשינוי שלו קופץ בסוף.
CycleInterpolator אמצעי אינטרפולציה שהאנימציה שלו חוזרת על עצמה מספר מסוים של מחזורים.
DecelerateInterpolator אינטרפולטור שקצב השינוי שלו מתחיל מהר ואז מואט.
LinearInterpolator אינטרפולטור שקצב השינוי שלו קבוע.
OvershootInterpolator אינטרפולטור שהשינוי שלו מתקדם קדימה וחוצה את הערך האחרון, ואז חוזר.
TimeInterpolator ממשק שמאפשר לכם להטמיע אינטרפולטור משלכם.

יצירת אנימציה באמצעות ValueAnimator

המחלקות ValueAnimator מאפשרות להוסיף אנימציה לערכים מסוג מסוים למשך האנימציה, על ידי ציון קבוצה של ערכי int, ‏ float או צבע שיוצגו באנימציה. כדי לקבל ValueAnimator, קוראים לאחת משיטות היצירה שלו: ofInt(), ‏ ofFloat() או ofObject(). לדוגמה:

Kotlin

ValueAnimator.ofFloat(0f, 100f).apply {
    duration = 1000
    start()
}

Java

ValueAnimator animation = ValueAnimator.ofFloat(0f, 100f);
animation.setDuration(1000);
animation.start();

בדוגמה הזו, הפונקציה ValueAnimator מתחילה לחשב את ערכי האנימציה start(), בין 0 ל-100, למשך 1,000 אלפיות השנייה, כשהמתודה start() מופעלת.

אפשר גם לציין סוג מותאם אישית של אנימציה באופן הבא:

Kotlin

ValueAnimator.ofObject(MyTypeEvaluator(), startPropertyValue, endPropertyValue).apply {
    duration = 1000
    start()
}

Java

ValueAnimator animation = ValueAnimator.ofObject(new MyTypeEvaluator(), startPropertyValue, endPropertyValue);
animation.setDuration(1000);
animation.start();

בקוד הזה, הפונקציה ValueAnimator מתחילה לחשב את ערכי האנימציה, בין startPropertyValue ל-endPropertyValue באמצעות הלוגיקה שסופקה על ידי MyTypeEvaluator למשך 1,000 אלפיות השנייה, כשהמתודה start() פועלת.

אפשר להשתמש בערכים של האנימציה על ידי הוספת AnimatorUpdateListener לאובייקט ValueAnimator, כמו שמוצג בקוד הבא:

Kotlin

ValueAnimator.ofObject(...).apply {
    ...
    addUpdateListener { updatedAnimation ->
        // You can use the animated value in a property that uses the
        // same type as the animation. In this case, you can use the
        // float value in the translationX property.
        textView.translationX = updatedAnimation.animatedValue as Float
    }
    ...
}

Java

animation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator updatedAnimation) {
        // You can use the animated value in a property that uses the
        // same type as the animation. In this case, you can use the
        // float value in the translationX property.
        float animatedValue = (float)updatedAnimation.getAnimatedValue();
        textView.setTranslationX(animatedValue);
    }
});

בשיטה onAnimationUpdate() אפשר לגשת לערך המעודכן של האנימציה ולהשתמש בו במאפיין של אחת מהתצוגות המפורטות. מידע נוסף על מאזינים זמין בקטע בנושא מאזינים של אנימציות.

יצירת אנימציה באמצעות ObjectAnimator

ObjectAnimator הוא מחלקת משנה של ValueAnimator (שמוסברת בקטע הקודם), והוא משלב את מנוע התזמון ואת חישוב הערך של ValueAnimator עם היכולת להנפיש מאפיין בעל שם של אובייקט יעד. כך קל יותר להנפיש אובייקטים, כי כבר לא צריך להטמיע את ValueAnimator.AnimatorUpdateListener, כי המאפיין המונפש מתעדכן באופן אוטומטי.

יצירת מופע של ObjectAnimator דומה ליצירת מופע של ValueAnimator, אבל צריך גם לציין את האובייקט ואת שם המאפיין של האובייקט (כמחרוזת) יחד עם הערכים שרוצים ליצור ביניהם אנימציה:

Kotlin

ObjectAnimator.ofFloat(textView, "translationX", 100f).apply {
    duration = 1000
    start()
}

Java

ObjectAnimator animation = ObjectAnimator.ofFloat(textView, "translationX", 100f);
animation.setDuration(1000);
animation.start();

כדי שהמערכת ObjectAnimatorתעדכן את הנכסים כמו שצריך, צריך לבצע את הפעולות הבאות:

  • למאפיין האובייקט שיוצרים לו אנימציה צריכה להיות פונקציית setter (ב-camel case) בצורה הבאה: set<PropertyName>(). השיטה הזו מאפשרת גישה לנכס כי ObjectAnimator מעדכן את הנכס באופן אוטומטי במהלך האנימציה. לדוגמה, אם שם הנכס הוא foo, צריך להגדיר את השיטה setFoo(). אם שיטת ההגדרה הזו לא קיימת, יש שלוש אפשרויות:
    • אם יש לכם הרשאה לעשות זאת, מוסיפים את שיטת ה-setter למחלקה.
    • משתמשים במחלקת wrapper שיש לכם הרשאות לשנות, ומגדירים שה-wrapper יקבל את הערך באמצעות שיטת setter תקינה ויעביר אותו לאובייקט המקורי.
    • במקומה, צריך להשתמש ב-method‏ ValueAnimator.
  • אם מציינים רק ערך אחד לפרמטר values... באחת משיטות היצירה ObjectAnimator, המערכת מניחה שזהו ערך הסיום של האנימציה. לכן, למאפיין האובייקט שמנפישים צריך להיות פונקציית getter שמשמשת להשגת ערך ההתחלה של האנימציה. פונקציית ה-getter צריכה להיות בפורמט get<PropertyName>(). לדוגמה, אם שם הנכס הוא foo, צריך להגדיר את השיטה getFoo().
  • שיטות ה-getter (אם צריך) וה-setter של המאפיין שאתם מנפישים חייבות לפעול על אותו סוג כמו ערכי ההתחלה והסיום שאתם מציינים ל-ObjectAnimator. לדוגמה, אם יוצרים את ObjectAnimator הבא, צריך להגדיר את targetObject.setPropName(float) ואת targetObject.getPropName():
    ObjectAnimator.ofFloat(targetObject, "propName", 1f)
  • בהתאם למאפיין או לאובייקט שאתם מנפישים, יכול להיות שתצטרכו לקרוא לשיטה invalidate() ב-View כדי לאלץ את המסך לצייר מחדש את עצמו עם הערכים המונפשים המעודכנים. הפעולה הזו מתבצעת ב-callback של onAnimationUpdate(). לדוגמה, אם מפעילים אנימציה של מאפיין הצבע של אובייקט Drawable, המסך מתעדכן רק כשהאובייקט מצייר את עצמו מחדש. כל הפונקציות להגדרת מאפיינים בתצוגה המפורטת, כמו setAlpha() ו-setTranslationX(), מבטלות את התוקף של התצוגה המפורטת בצורה תקינה, כך שלא צריך לבטל את התוקף של התצוגה המפורטת כשמפעילים את הפונקציות האלה עם ערכים חדשים. מידע נוסף על מאזינים זמין בקטע בנושא מאזינים של אנימציות.

כוריאוגרפיה של כמה אנימציות באמצעות AnimatorSet

במקרים רבים, רוצים להפעיל אנימציה שתלויה במועד שבו אנימציה אחרת מתחילה או מסתיימת. מערכת Android מאפשרת לכם לאגד אנימציות יחד לתוך AnimatorSet, כדי שתוכלו לציין אם האנימציות יתחילו בו-זמנית, ברצף או אחרי השהיה מוגדרת. אפשר גם להוסיף AnimatorSet אובייקטים אחד בתוך השני.

בקטע הקוד הבא מוצגים אובייקטים של Animator שמופעלים באופן הבא:

  1. הפעלת bounceAnim.
  2. הפעלת squashAnim1,‏ squashAnim2,‏ stretchAnim1 ו-stretchAnim2 בו-זמנית.
  3. הפעלת bounceBackAnim.
  4. הפעלת fadeAnim.

Kotlin

val bouncer = AnimatorSet().apply {
    play(bounceAnim).before(squashAnim1)
    play(squashAnim1).with(squashAnim2)
    play(squashAnim1).with(stretchAnim1)
    play(squashAnim1).with(stretchAnim2)
    play(bounceBackAnim).after(stretchAnim2)
}
val fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f).apply {
    duration = 250
}
AnimatorSet().apply {
    play(bouncer).before(fadeAnim)
    start()
}

Java

AnimatorSet bouncer = new AnimatorSet();
bouncer.play(bounceAnim).before(squashAnim1);
bouncer.play(squashAnim1).with(squashAnim2);
bouncer.play(squashAnim1).with(stretchAnim1);
bouncer.play(squashAnim1).with(stretchAnim2);
bouncer.play(bounceBackAnim).after(stretchAnim2);
ValueAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);
fadeAnim.setDuration(250);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(bouncer).before(fadeAnim);
animatorSet.start();

מאזינים לאנימציות

אפשר להאזין לאירועים חשובים במהלך האנימציה באמצעות מאזינים שמתוארים בהמשך.

  • Animator.AnimatorListener
  • ValueAnimator.AnimatorUpdateListener
    • onAnimationUpdate() – מופעל בכל פריים של האנימציה. כדאי להאזין לאירוע הזה כדי להשתמש בערכים המחושבים שנוצרו על ידי ValueAnimator במהלך אנימציה. כדי להשתמש בערך, שולחים שאילתה לאובייקט ValueAnimator שעבר לאירוע כדי לקבל את הערך המונפש הנוכחי באמצעות השיטה getAnimatedValue(). חובה להטמיע את מאזין האירועים הזה אם משתמשים ב-ValueAnimator.

      בהתאם לנכס או לאובייקט שמוסיפים לו אנימציה, יכול להיות שתצטרכו להפעיל את הפונקציה invalidate() ב-View כדי לאלץ את האזור הזה במסך לצייר את עצמו מחדש עם ערכי האנימציה החדשים. לדוגמה, אם מפעילים אנימציה של מאפיין הצבע של אובייקט Drawable, המסך מתעדכן רק כשהאובייקט מצייר את עצמו מחדש. כל הפונקציות להגדרת מאפיינים בתצוגה המפורטת, כמו setAlpha() ו-setTranslationX(), מבטלות את התוקף של התצוגה המפורטת בצורה תקינה, כך שלא צריך לבטל את התוקף של התצוגה המפורטת כשקוראים לפונקציות האלה עם ערכים חדשים.

אם אתם לא רוצים להטמיע את כל השיטות של הממשק Animator.AnimatorListener, אתם יכולים להרחיב את המחלקה AnimatorListenerAdapter במקום להטמיע את הממשק Animator.AnimatorListener. המחלקות AnimatorListenerAdapter מספקות הטמעות ריקות של השיטות שאפשר לבחור להחליף.

לדוגמה, קטע הקוד הבא יוצר AnimatorListenerAdapter רק עבור הקריאה החוזרת onAnimationEnd():

Kotlin

ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f).apply {
    duration = 250
    addListener(object : AnimatorListenerAdapter() {
        override fun onAnimationEnd(animation: Animator) {
            balls.remove((animation as ObjectAnimator).target)
        }
    })
}

Java

ValueAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);
fadeAnim.setDuration(250);
fadeAnim.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation) {
    balls.remove(((ObjectAnimator)animation).getTarget());
}

הנפשת שינויים בפריסה של אובייקטים מסוג ViewGroup

מערכת האנימציה של המאפיינים מאפשרת להנפיש שינויים באובייקטים של ViewGroup, וגם מספקת דרך קלה להנפיש אובייקטים של View.

אפשר להנפיש שינויים בפריסה בתוך ViewGroup באמצעות המחלקה LayoutTransition. כשמוסיפים תצוגות ל-ViewGroup או מסירים אותן ממנו, או כשקוראים לשיטה setVisibility() של View עם VISIBLE, INVISIBLE או GONE, התצוגות בתוך ה-ViewGroup עוברות אנימציה של הופעה והיעלמות. גם שאר התצוגות ב-ViewGroup יכולות להנפיש את המעבר למיקומים החדשים שלהן כשמוסיפים או מסירים תצוגות. אפשר להגדיר את האנימציות הבאות באובייקט LayoutTransition באמצעות קריאה ל-setAnimator() והעברה של אובייקט Animator עם אחד מהקבועים הבאים של LayoutTransition:

  • APPEARING – דגל שמציין את האנימציה שמופעלת על פריטים שמופיעים במאגר התגים.
  • CHANGE_APPEARING – דגל שמציין את האנימציה שמופעלת על פריטים שמשתנים בגלל פריט חדש שמופיע במאגר.
  • DISAPPEARING – דגל שמציין את האנימציה שמופעלת על פריטים שנעלמים מהקונטיינר.
  • CHANGE_DISAPPEARING – דגל שמציין את האנימציה שמופעלת על פריטים שמשתנים בגלל שפריט נעלם מהקונטיינר.

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

כדי להגדיר את מאפיין android:animateLayoutchanges לערך true עבור ViewGroup:

<LinearLayout
    android:orientation="vertical"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:id="@+id/verticalContainer"
    android:animateLayoutChanges="true" />

הגדרת הערך של המאפיין הזה כ-true תגרום להנפשה אוטומטית של רכיבי View שנוספו ל-ViewGroup או הוסרו ממנו, וגם של רכיבי View שנותרו ב-ViewGroup.

הנפשת שינויים במצב התצוגה באמצעות StateListAnimator

המחלקות StateListAnimator מאפשרות להגדיר אנימציות שמופעלות כשמצב התצוגה משתנה. האובייקט הזה מתנהג כעטיפה לאובייקט Animator, ומפעיל את האנימציה הזו בכל פעם שמשתנה מצב התצוגה שצוין (למשל, 'לחוץ' או 'במיקוד').

אפשר להגדיר את StateListAnimator במשאב XML עם רכיב בסיס <selector> ואלמנטים צאצאים <item> שכל אחד מהם מציין מצב תצוגה שונה שמוגדר על ידי המחלקה StateListAnimator. כל רכיב <item> מכיל את ההגדרה של קבוצת אנימציות של מאפיינים.

לדוגמה, הקובץ הבא יוצר אנימטור של רשימת מצבים שמשנה את קנה המידה של ציר ה-x וציר ה-y של התצוגה כשלוחצים עליה:

res/xml/animate_scale.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- the pressed state; increase x and y size to 150% -->
    <item android:state_pressed="true">
        <set>
            <objectAnimator android:propertyName="scaleX"
                android:duration="@android:integer/config_shortAnimTime"
                android:valueTo="1.5"
                android:valueType="floatType"/>
            <objectAnimator android:propertyName="scaleY"
                android:duration="@android:integer/config_shortAnimTime"
                android:valueTo="1.5"
                android:valueType="floatType"/>
        </set>
    </item>
    <!-- the default, non-pressed state; set x and y size to 100% -->
    <item android:state_pressed="false">
        <set>
            <objectAnimator android:propertyName="scaleX"
                android:duration="@android:integer/config_shortAnimTime"
                android:valueTo="1"
                android:valueType="floatType"/>
            <objectAnimator android:propertyName="scaleY"
                android:duration="@android:integer/config_shortAnimTime"
                android:valueTo="1"
                android:valueType="floatType"/>
        </set>
    </item>
</selector>

כדי לצרף את האנימטור של רשימת המצבים לתצוגה, מוסיפים את המאפיין android:stateListAnimator באופן הבא:

<Button android:stateListAnimator="@xml/animate_scale"
        ... />

עכשיו נעשה שימוש באנימציות שמוגדרות ב-animate_scale.xml כשמצב הלחצן הזה משתנה.

לחלופין, כדי להקצות אנימטור של רשימת מצבים לתצוגה בקוד, משתמשים בשיטה AnimatorInflater.loadStateListAnimator() ומקצים את האנימטור לתצוגה באמצעות השיטה View.setStateListAnimator().

לחלופין, במקום להנפיש את מאפייני התצוגה, אפשר להפעיל אנימציה של רכיב drawable בין שינויי מצב באמצעות AnimatedStateListDrawable. חלק מהווידג'טים של המערכת ב-Android 5.0 משתמשים באנימציות האלה כברירת מחדל. בדוגמה הבאה אפשר לראות איך מגדירים AnimatedStateListDrawable כמשאב XML:

<!-- res/drawable/myanimstatedrawable.xml -->
<animated-selector
    xmlns:android="http://schemas.android.com/apk/res/android">

    <!-- provide a different drawable for each state-->
    <item android:id="@+id/pressed" android:drawable="@drawable/drawableP"
        android:state_pressed="true"/>
    <item android:id="@+id/focused" android:drawable="@drawable/drawableF"
        android:state_focused="true"/>
    <item android:id="@id/default"
        android:drawable="@drawable/drawableD"/>

    <!-- specify a transition -->
    <transition android:fromId="@+id/default" android:toId="@+id/pressed">
        <animation-list>
            <item android:duration="15" android:drawable="@drawable/dt1"/>
            <item android:duration="15" android:drawable="@drawable/dt2"/>
            ...
        </animation-list>
    </transition>
    ...
</animated-selector>

שימוש ב-TypeEvaluator

אם רוצים להנפיש סוג שלא מוכר למערכת Android, אפשר ליצור מעריך משלכם על ידי הטמעה של הממשק TypeEvaluator. הסוגים שמערכת Android מכירה הם int,‏ float או צבע, שנתמכים על ידי מעריכי הסוגים IntEvaluator,‏ FloatEvaluator ו-ArgbEvaluator.

יש רק שיטה אחת להטמעה בממשק TypeEvaluator – השיטה evaluate(). כך האנימטור שבו אתם משתמשים יכול להחזיר ערך מתאים לנכס האנימציה בנקודה הנוכחית של האנימציה. בדוגמה הבאה אפשר לראות איך עושים את זה באמצעות המחלקה FloatEvaluator:

Kotlin

private class FloatEvaluator : TypeEvaluator<Any> {

    override fun evaluate(fraction: Float, startValue: Any, endValue: Any): Any {
        return (startValue as Number).toFloat().let { startFloat ->
            startFloat + fraction * ((endValue as Number).toFloat() - startFloat)
        }
    }

}

Java

public class FloatEvaluator implements TypeEvaluator {

    public Object evaluate(float fraction, Object startValue, Object endValue) {
        float startFloat = ((Number) startValue).floatValue();
        return startFloat + fraction * (((Number) endValue).floatValue() - startFloat);
    }
}

הערה: כשמפעילים את ValueAnimator (או את ObjectAnimator), המערכת מחשבת את החלק הנוכחי של האנימציה שעבר (ערך בין 0 ל-1), ואז מחשבת גרסה משוערת של החלק הזה בהתאם לאינטרפולטור שבו משתמשים. השבר שחושב באינטרפולציה הוא מה ש-TypeEvaluator מקבל דרך הפרמטר fraction, כך שלא צריך להתחשב בפונקציית האינטרפולציה כשמחשבים ערכים מונפשים.

שימוש באינטרפולטורים

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

אינטרפולטורים במערכת האנימציה מקבלים מספר בין 0 ל-1 מ-Animators שמייצג את הזמן שחלף מאז תחילת האנימציה. האינטרפולטורים משנים את השבר הזה כך שיתאים לסוג האנימציה שהם אמורים לספק. מערכת Android מספקת קבוצה של פונקציות אינטרפולציה נפוצות ב-android.view.animation package. אם אף אחד מהם לא מתאים לצרכים שלכם, אתם יכולים להטמיע את הממשק TimeInterpolator וליצור משלכם.

לדוגמה, בהמשך מוצגות השוואות בין האופן שבו מחשבים את השברים המחושבים של ברירת המחדל של פונקציית האינטרפולציה AccelerateDecelerateInterpolator לבין האופן שבו מחשבים אותם באמצעות LinearInterpolator. הערך LinearInterpolator לא משפיע על השבר שחלף. הערך AccelerateDecelerateInterpolator מאיץ לתוך האנימציה ומאט כשהוא יוצא ממנה. השיטות הבאות מגדירות את הלוגיקה של הפונקציות האלה:

AccelerateDecelerateInterpolator

Kotlin

override fun getInterpolation(input: Float): Float =
        (Math.cos((input + 1) * Math.PI) / 2.0f).toFloat() + 0.5f

Java

@Override
public float getInterpolation(float input) {
    return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}

LinearInterpolator

Kotlin

override fun getInterpolation(input: Float): Float = input

Java

@Override
public float getInterpolation(float input) {
    return input;
}

בטבלה הבאה מוצגים הערכים המשוערים שמחושבים על ידי פונקציות האינטרפולציה האלה לאנימציה שנמשכת 1,000 אלפיות השנייה:

הזמן שחלף (באלפיות השנייה) השבר שחלף/השבר המשוער (ליניארי) שבר משוער (האצה/האטה)
0 0 0
200 ‫.2 ‫.1
400 ‫4. ‫.345
600 ‫6. ‪.654
800 ‫.8 ‫9.
1000 1 1

כפי שאפשר לראות בטבלה, LinearInterpolator משנה את הערכים באותה מהירות, 0.2 לכל 200 אלפיות השנייה שעוברות. השינוי בערכים של AccelerateDecelerateInterpolator מהיר יותר מאשר בערכים של LinearInterpolator בין 200 אלפיות השנייה ל-600 אלפיות השנייה, ואיטי יותר בין 600 אלפיות השנייה ל-1,000 אלפיות השנייה.

ציון מסגרות מפתח

אובייקט Keyframe מורכב מצמד של זמן/ערך שמאפשר להגדיר מצב ספציפי בזמן ספציפי באנימציה. לכל תמונת מפתח יכול להיות גם אינטרפולטור משלה כדי לשלוט בהתנהגות של האנימציה במרווח שבין הזמן של תמונת המפתח הקודמת לבין הזמן של תמונת המפתח הנוכחית.

כדי ליצור מופע של אובייקט Keyframe, צריך להשתמש באחת משיטות הפקטורי, ofInt(), ofFloat() או ofObject(), כדי לקבל את הסוג המתאים של Keyframe. אחר כך קוראים ל-factory method‏ ofKeyframe() כדי לקבל אובייקט PropertyValuesHolder. אחרי שיש לכם את האובייקט, אתם יכולים להשיג אנימטור על ידי העברת האובייקט PropertyValuesHolder והאובייקט שרוצים להנפיש. בקטע הקוד הבא אפשר לראות איך עושים את זה:

Kotlin

val kf0 = Keyframe.ofFloat(0f, 0f)
val kf1 = Keyframe.ofFloat(.5f, 360f)
val kf2 = Keyframe.ofFloat(1f, 0f)
val pvhRotation = PropertyValuesHolder.ofKeyframe("rotation", kf0, kf1, kf2)
ObjectAnimator.ofPropertyValuesHolder(target, pvhRotation).apply {
    duration = 5000
}

Java

Keyframe kf0 = Keyframe.ofFloat(0f, 0f);
Keyframe kf1 = Keyframe.ofFloat(.5f, 360f);
Keyframe kf2 = Keyframe.ofFloat(1f, 0f);
PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofKeyframe("rotation", kf0, kf1, kf2);
ObjectAnimator rotationAnim = ObjectAnimator.ofPropertyValuesHolder(target, pvhRotation);
rotationAnim.setDuration(5000);

הנפשת תצוגות

מערכת האנימציה של הנכס מאפשרת אנימציה יעילה של אובייקטים מסוג View ומציעה כמה יתרונות על פני מערכת האנימציה של התצוגה. מערכת האנימציה של התצוגה שינתה את אובייקטי התצוגה על ידי שינוי האופן שבו הם צויירו. הטיפול הזה בוצע במאגר התגים של כל תצוגה מפורטת, כי לתצוגה המפורטת עצמה לא היו מאפיינים שאפשר לשנות. התוצאה הייתה שהתצוגה הונפשה, אבל לא חל שינוי באובייקט התצוגה עצמו. הדבר הוביל להתנהגויות כמו אובייקט שעדיין קיים במיקום המקורי שלו, למרות שהוא צויר במיקום אחר במסך. ב-Android 3.0, נוספו מאפיינים חדשים ושיטות ה-getter וה-setter התואמות כדי לפתור את הבעיה הזו.

מערכת האנימציה של המאפיינים יכולה להנפיש תצוגות במסך על ידי שינוי המאפיינים בפועל באובייקטים של התצוגה. בנוסף, Views גם קורא באופן אוטומטי לשיטה invalidate() כדי לרענן את המסך בכל פעם שהמאפיינים שלו משתנים. המאפיינים החדשים במחלקה View שמקלים על יצירת אנימציות של מאפיינים הם:

  • translationX ו-translationY: המאפיינים האלה קובעים את המיקום של התצוגה כדלתא מהקואורדינטות השמאליות והעליונות שלה, שמוגדרות על ידי מאגר הפריסה שלה.
  • rotation,‏ rotationX ו-rotationY: המאפיינים האלה שולטים בסיבוב בדו-ממד (המאפיין rotation) ובתלת-ממד סביב נקודת הציר.
  • scaleX ו-scaleY: המאפיינים האלה שולטים בשינוי הגודל הדו-ממדי של תצוגה סביב נקודת הציר שלה.
  • pivotX ו-pivotY: המאפיינים האלה קובעים את המיקום של נקודת הציר, שסביבה מתבצעים שינויי הסיבוב והקנה מידה. כברירת מחדל, נקודת הציר ממוקמת במרכז האובייקט.
  • x ו-y: אלה מאפייני כלי עזר פשוטים שמתארים את המיקום הסופי של התצוגה ברכיב המכיל שלה, כסכום של הערכים left ו-top ושל הערכים translationX ו-translationY.
  • alpha: מייצג את שקיפות האלפא בתצוגה. ערך ברירת המחדל הוא 1 (אטום), והערך 0 מייצג שקיפות מלאה (לא נראה).

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

Kotlin

ObjectAnimator.ofFloat(myView, "rotation", 0f, 360f)

Java

ObjectAnimator.ofFloat(myView, "rotation", 0f, 360f);

מידע נוסף על יצירת אנימטורים זמין בקטעים על אנימציה באמצעות ValueAnimator ו-ObjectAnimator.

יצירת אנימציה באמצעות ViewPropertyAnimator

ה-ViewPropertyAnimator מספק דרך פשוטה להנפשה של כמה מאפיינים של View במקביל, באמצעות אובייקט Animator בסיסי יחיד. היא מתנהגת בדומה ל-ObjectAnimator, כי היא משנה את הערכים בפועל של מאפייני התצוגה, אבל היא יעילה יותר כשמנפישים הרבה מאפיינים בו-זמנית. בנוסף, הקוד לשימוש ב-ViewPropertyAnimator הרבה יותר תמציתי וקל לקריאה. קטעי הקוד הבאים מראים את ההבדלים בשימוש בכמה אובייקטים של ObjectAnimator, באובייקט ObjectAnimator יחיד וב-ViewPropertyAnimator כשמנפישים בו-זמנית את המאפיינים x ו-y של תצוגה.

כמה אובייקטים של ObjectAnimator

Kotlin

val animX = ObjectAnimator.ofFloat(myView, "x", 50f)
val animY = ObjectAnimator.ofFloat(myView, "y", 100f)
AnimatorSet().apply {
    playTogether(animX, animY)
    start()
}

Java

ObjectAnimator animX = ObjectAnimator.ofFloat(myView, "x", 50f);
ObjectAnimator animY = ObjectAnimator.ofFloat(myView, "y", 100f);
AnimatorSet animSetXY = new AnimatorSet();
animSetXY.playTogether(animX, animY);
animSetXY.start();

One ObjectAnimator

Kotlin

val pvhX = PropertyValuesHolder.ofFloat("x", 50f)
val pvhY = PropertyValuesHolder.ofFloat("y", 100f)
ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvhY).start()

Java

PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("x", 50f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("y", 100f);
ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvhY).start();

ViewPropertyAnimator

Kotlin

myView.animate().x(50f).y(100f)

Java

myView.animate().x(50f).y(100f);

מידע מפורט יותר על ViewPropertyAnimator זמין בפוסט בבלוג של Android Developers.

הצהרה על אנימציות ב-XML

מערכת האנימציה של המאפיינים מאפשרת להצהיר על אנימציות של מאפיינים באמצעות XML במקום לעשות זאת באופן פרוגרמטי. הגדרת האנימציות ב-XML מאפשרת לעשות בהן שימוש חוזר בקלות בכמה פעילויות, ולערוך את רצף האנימציה בקלות רבה יותר.

כדי להבדיל בין קובצי אנימציה שמשתמשים בממשקי ה-API החדשים של אנימציית מאפיינים לבין קובצי אנימציה שמשתמשים במסגרת אנימציית התצוגה מדור קודם, החל מ-Android 3.1, צריך לשמור את קובצי ה-XML של אנימציות המאפיינים בספרייה res/animator/.

מחלקות האנימציה הבאות של מאפיינים תומכות בהצהרת XML עם תגי ה-XML הבאים:

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

<set android:ordering="sequentially">
    <set>
        <objectAnimator
            android:propertyName="x"
            android:duration="500"
            android:valueTo="400"
            android:valueType="intType"/>
        <objectAnimator
            android:propertyName="y"
            android:duration="500"
            android:valueTo="300"
            android:valueType="intType"/>
    </set>
    <objectAnimator
        android:propertyName="alpha"
        android:duration="500"
        android:valueTo="1f"/>
</set>

כדי להפעיל את האנימציה הזו, צריך להרחיב את משאבי ה-XML בקוד לאובייקט AnimatorSet, ואז להגדיר את אובייקטי היעד לכל האנימציות לפני שמפעילים את קבוצת האנימציות. הקריאה ל-setTarget() מגדירה אובייקט יעד יחיד לכל הצאצאים של AnimatorSet, כדי להקל על השימוש. בדוגמה הבאה אפשר לראות איך עושים את זה:

Kotlin

(AnimatorInflater.loadAnimator(myContext, R.animator.property_animator) as AnimatorSet).apply {
    setTarget(myObject)
    start()
}

Java

AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext,
    R.animator.property_animator);
set.setTarget(myObject);
set.start();

אפשר גם להצהיר על ValueAnimator ב-XML, כמו בדוגמה הבאה:

<animator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:valueType="floatType"
    android:valueFrom="0f"
    android:valueTo="-100f" />

כדי להשתמש ב-ValueAnimator הקודם בקוד, צריך להרחיב את האובייקט, להוסיף AnimatorUpdateListener, לקבל את ערך האנימציה המעודכן ולהשתמש בו במאפיין של אחד מהתצוגות, כמו שמוצג בקוד הבא:

Kotlin

(AnimatorInflater.loadAnimator(this, R.animator.animator) as ValueAnimator).apply {
    addUpdateListener { updatedAnimation ->
        textView.translationX = updatedAnimation.animatedValue as Float
    }

    start()
}

Java

ValueAnimator xmlAnimator = (ValueAnimator) AnimatorInflater.loadAnimator(this,
        R.animator.animator);
xmlAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator updatedAnimation) {
        float animatedValue = (float)updatedAnimation.getAnimatedValue();
        textView.setTranslationX(animatedValue);
    }
});

xmlAnimator.start();

מידע על תחביר ה-XML להגדרת אנימציות של מאפיינים זמין במאמר משאבי אנימציה .

השפעות אפשריות על ביצועי ממשק המשתמש

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

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