מערכת האנימציה של הנכס היא מסגרת חזקה שמאפשרת ליצור אנימציה של כמעט כל דבר. אפשר להגדיר אנימציה שתשנה כל מאפיין של אובייקט לאורך זמן, גם אם הוא מצויר במסך וגם אם לא. אנימציה של נכס משנה נכס (שדה באובייקט) לאורך פרק זמן שצוין. כדי להנפיש משהו, צריך לציין את מאפיין האובייקט שרוצים להנפיש, כמו המיקום של האובייקט במסך, משך ההנפשה והערכים שרוצים להנפיש ביניהם.
מערכת האנימציה של המאפיינים מאפשרת להגדיר את המאפיינים הבאים של אנימציה:
- משך זמן: אפשר להגדיר את משך האנימציה. אורך ברירת המחדל הוא 300 אלפיות השנייה.
- אינטרפולציה של זמן: אפשר לציין איך ערכי הנכס מחושבים הפונקציה של הזמן שחלף עכשיו באנימציה.
- מספר החזרות וההתנהגות: אפשר לציין אם אנימציה תחזור או לא כשהיא תגיע לסוף משך הזמן, וכמה פעמים היא תחזור. אפשר גם לציין אם רוצים שהאנימציה תופעל בסדר הפוך. אם מגדירים את האפשרות הזו להפעלה לאחור, האנימציה תופעל קדימה ואז לאחור שוב ושוב, עד שמגיעים למספר החזרות.
- קבוצות של אנימציות: אפשר לקבץ אנימציות לקבוצות לוגיות שיופעלו יחד, ברצף או לאחר עיכובים מוגדרים.
- עיכוב רענון המסגרות: אפשר לציין באיזו תדירות לרענן את המסגרות של האנימציה. כברירת מחדל, המערכת מתרעננת כל 10 אלפיות השנייה, אבל המהירות שבה האפליקציה יכולה לרענן את המסגרות תלויה בסופו של דבר בעומס הכולל על המערכת ובמהירות שבה המערכת יכולה לתת שירות למכשיר הטיימר הבסיסי.
דוגמה מלאה לאנימציה של נכס מופיעה בכיתה ChangeColor
בדוגמה CustomTransition ב-GitHub.
איך פועלת האנימציה של הנכס
קודם נראה איך אנימציה פועלת באמצעות דוגמה פשוטה. איור 1 מציג
אובייקט היפותטי שמונפש באמצעות המאפיין x
שלו, שמייצג
המיקום האופקי במסך. משך האנימציה מוגדר ל-40 אלפיות השנייה והמרחק לנסיעה הוא 40 פיקסלים. כל 10 אלפיות השנייה, שהיא קצב הרענון של המסגרות שמוגדר כברירת מחדל, האובייקט זז אופקית ב-10 פיקסלים. בסוף 40 אלפיות השנייה, האנימציה נעצרת והאובייקט מסתיים במיקום אופקי 40. זו דוגמה לאנימציה עם אינטרפולציה לינארית, כלומר האובייקט זז במהירות קבועה.
אפשר גם לציין שהאנימציות יהיו עם אינטרפולציה לא לינארית. באיור 2 מוצג אובייקט היפותטי שמאיץ בתחילת האנימציה ומאט בסופה. האובייקט עדיין זז 40 פיקסלים ב-40 אלפיות השנייה, אבל באופן לא לינארי. ב בהתחלה, האנימציה הזו מאיצה עד לנקודת חצי הדרך ולאחר מכן מורידה נקודת האמצע עד סוף האנימציה. כמו שרואים באיור 2, המרחק שעברת בהתחלה ובסוף של האנימציה פחות מאשר באמצע.
עכשיו נראה בפירוט איך הרכיבים החשובים במערכת האנימציה של המאפיינים הפונקציה תחשב אנימציות כמו אלו שמתוארות למעלה. באיור 3 מוצגת האופן שבו הכיתות הראשיות פועלות זו עם זו.
האובייקט ValueAnimator
עוקב אחרי תזמון האנימציה,
כמו משך הזמן שבו האנימציה פועלת, והערך הנוכחי של הנכס
מונפש.
ה-ValueAnimator
מכיל בתוך עצמו TimeInterpolator
שמגדיר את אינטרפולציית האנימציה ו-TypeEvaluator
שמגדיר את אופן החישוב של הערכים של המאפיין שמתבצעת לו אנימציה. לדוגמה, באיור 2, נעשה שימוש ב-TimeInterpolator
כך
AccelerateDecelerateInterpolator
וה-TypeEvaluator
יהיו IntEvaluator
.
כדי להתחיל אנימציה, צריך ליצור ValueAnimator
ולתת לה את
ערכי ההתחלה והסיום של הנכס שרוצים להוסיף לו אנימציה, וכן משך הזמן
את האנימציה. כשקוראים לפונקציה start()
, האנימציה
מתחיל. במהלך כל האנימציה, ה-ValueAnimator
מחשב חלק חולף בין 0 ל-1, על סמך משך האנימציה והזמן שחלף. החלק שחלף מייצג את אחוז הזמן שבו האנימציה הושלמה. הערך 0 מייצג 0% והערך 1 מייצג 100%. לדוגמה, באיור 1, החלק הזמן שחלף ב-t = 10 אלפיות השנייה יהיה 25.
כי משך הזמן הכולל הוא t = 40 אלפיות השנייה.
כשה-ValueAnimator
מסיים לחשב את החלק החולף, הוא קורא ל-TimeInterpolator
שמוגדר כרגע כדי לחשב חלק משוער. שבר אינטרפולציה ממפה את השבר שחלף לשבר חדש
שמביא בחשבון את אינטרפולציית הזמן שהוגדרה. לדוגמה, באיור 2, מכיוון שהאנימציה מאיצה לאט, החלק המשוער, בערך 0.15, קטן מהחלק שחלף, 0.25, בזמן t = 10 אלפיות השנייה. באיור 1, החלק המשוער תמיד זהה לחלק שחלף.
כאשר מחושב השבר המחושב, ValueAnimator
הפעלות
את TypeEvaluator
המתאים, כדי לחשב את הערך
את המאפיין שאתם יוצרים באנימציה, על סמך השבר המחושב, ערך ההתחלה
הערך הסופי של האנימציה. לדוגמה, באיור 2, החלק המחושב היה 0.15 בזמן t = 10ms, כך שהערך של המאפיין באותו זמן יהיה 0.15 x (40 - 0), או 6.
מה ההבדל בין האנימציה של נכס לבין האנימציה של התצוגה המפורטת
מערכת האנימציה של התצוגה מאפשרת להוסיף אנימציה רק ל-View
ולכן אם רוצים ליצור אנימציה לאובייקטים שאינם View
, צריך ליישם
כדי לעשות את זה, גם מערכת האנימציה של הצפיות מוגבלת, כי היא רק
חושף כמה היבטים של אובייקט View
להנפשה, כמו התאמה לעומס (scaling)
סיבוב של תצוגה, אבל לא של צבע הרקע.
חיסרון נוסף של מערכת האנימציה של התצוגה הוא שהיא שינתה רק את המיקום שבו התצוגה תוצג, ולא את התצוגה עצמה. לדוגמה, אם יצרתם אנימציה של לחצן כדי להזיז על המסך, הלחצן משורטט בצורה נכונה, אך המיקום בפועל שבו ניתן ללחוץ על לא משתנה, לכן עליכם להטמיע לוגיקה משלכם כדי לטפל בכך.
בעזרת מערכת האנימציה של המאפיינים, האילוצים האלה מוסרים לחלוטין, וניתן להוסיף אנימציה לכל מאפיין של כל אובייקט (תצוגות מפורטות ולא תצוגות מפורטות), והאובייקט עצמו משתנה בפועל. בנוסף, מערכת האנימציה של הנכס חזקה יותר בדרך שבה היא מבצעת את האנימציה. ברמה גבוהה, מקצים אנימטורים למאפיינים שרוצים להנפיש, כמו צבע, מיקום או גודל, וניתן להגדיר היבטים של האנימציה, כמו אינטרפולציה וסנכרון של כמה אנימטורים.
עם זאת, תהליך ההגדרה של מערכת האנימציה של התצוגה נמשך פחות זמן ונדרש פחות זמן בכתיבה. אם אנימציית התצוגה משיגה את כל מה שצריך לעשות, או אם הקוד הקיים כבר עובד. פועל באופן הרצוי, אין צורך להשתמש במערכת האנימציה של המאפיינים. ייתכן גם הגיוני להשתמש בשתי מערכות האנימציה במצבים שונים, אם התרחיש לדוגמה הזה נובע מכך.
סקירה כללית על ממשקי API
רוב ממשקי ה-API של מערכת האנימציה של הנכסים נמצאים ב-android.animation
. כי מערכת הצגת האנימציה כבר
שמגדיר הרבה אינטרפולטורים ב-android.view.animation
, אפשר להשתמש
האינטרפולטורים האלו גם במערכת האנימציה של הנכס. בטבלאות הבאות מתוארים הרכיבים העיקריים של מערכת האנימציה של המאפיינים.
המחלקה Animator
מספקת את המבנה הבסיסי ליצירה
אנימציות. בדרך כלל לא משתמשים בכיתה הזו ישירות, כי היא מספקת
פונקציונליות שצריך להרחיב כדי לתמוך באופן מלא בערכים מונפשים. הבאים
כיתות המשנה מתרחבות ב-Animator
:
דרגה | תיאור |
---|---|
ValueAnimator |
מנוע התזמון הראשי לאנימציה של הנכס, שמחשב גם את הערכים של
להנפשה. הוא כולל את כל הפונקציונליות הבסיסית שמחשבת את הערכים של האנימציה, ומכיל את פרטי התזמון של כל אנימציה, מידע על החזרה של אנימציה, מאזינים שמקבלים אירועי עדכון ואפשרות להגדיר סוגים מותאמים אישית לצורך הערכה. יש שני חלקים ביצירת אנימציה לנכסים: חישוב הערכים של האנימציה והגדרת הערכים האלה באובייקט ובנכס שאליהם מופעלת האנימציה. ValueAnimator לא מבצע את החלק השני, לכן צריך להאזין לעדכונים של ערכים שמחושבים על ידי ValueAnimator ולשנות את האובייקטים שרוצים להנפיש באמצעות הלוגיקה שלכם. מידע נוסף זמין בקטע אנימציה באמצעות ValueAnimator. |
ObjectAnimator |
תת-מחלקה של ValueAnimator שמאפשרת להגדיר יעד
של אובייקט ואובייקט להנפשה. הכיתה הזו מעדכנת את המאפיין בהתאם כשהיא מחשבת ערך חדש לאנימציה. אתם רוצים להשתמש
ObjectAnimator ברוב הזמן,
כי זה הופך את התהליך של הוספת אנימציה לערכים של אובייקטי יעד, הרבה יותר קל. אבל, לפעמים
לפעמים רוצים להשתמש ישירות באפליקציה ValueAnimator , כי ב-ObjectAnimator יש עוד כמה הגבלות, כמו דרישה
methods של רכיב גישה שיהיו באובייקט היעד. |
AnimatorSet |
מספקת מנגנון לקיבוץ אנימציות יחד כדי שיפעלו יחסים ביניהם. אתם יכולים להגדיר שהאנימציות יופעלו יחד, ברצף או לאחר עיכוב מסוים. מידע נוסף זמין בקטע יצירת כוריאוגרפיה של כמה אנימציות באמצעות ערכות של אנימטורים. |
מעריכים אומרים למערכת האנימציה של המאפיינים איך לחשב את הערכים של מימד נתון
לנכס. הם לוקחים את נתוני התזמון שסופקו על ידי הכיתה Animator
, את ערכי ההתחלה והסיום של האנימציה ומחשבים את הערכים האנימציה של המאפיין על סמך הנתונים האלה. מערכת האנימציה של הנכס מספקת את הגורמים הבאים להערכה:
כיתה/ממשק | תיאור |
---|---|
IntEvaluator |
כלי ההערכה שמוגדר כברירת מחדל לחישוב הערכים של נכסים של int . |
FloatEvaluator |
כלי ההערכה שמוגדר כברירת מחדל לחישוב הערכים של נכסים של float . |
ArgbEvaluator |
מעריך ברירת המחדל לחישוב ערכים למאפייני צבע שמיוצגים כערכים הקסדצימליים. |
TypeEvaluator |
ממשק שמאפשר ליצור מעריך משלכם. אם אתם יוצרים אנימציה של
מאפיין אובייקט שלא int , float או צבע,
עליכם להטמיע את הממשק TypeEvaluator כדי לציין איך
כדי לחשב את הערכים המונפשים של מאפיין האובייקט. אפשר גם לציין TypeEvaluator מותאם אישית עבור int , float וצבע
והערכים שלה, אם רוצים לעבד את הסוגים האלה באופן שונה מהתנהגות ברירת המחדל.
מידע נוסף זמין בקטע שימוש ב-TypeEvaluator.
מידע על האופן שבו ניתן לכתוב מעריך מותאם אישית. |
אינטרפולטור זמן מגדיר איך ערכים ספציפיים באנימציה מחושבים
הוא פונקציה של הזמן. לדוגמה, תוכלו לציין שהאנימציות יתרחשו באופן לינארי
אנימציה. כלומר, האנימציה זזה באופן שווה לאורך כל הזמן, או שאפשר לציין אנימציות.
להשתמש בזמן לא ליניארי, לדוגמה, להאיץ בהתחלה ולהפחית את התנועה
סוף האנימציה. בטבלה 3 מתוארים האינטרפולטורים שמכיל android.view.animation
. אם אף אחד מהאינטרפולטורים שסופקו לא מתאים לצרכים שלכם, תוכלו להטמיע את הממשק TimeInterpolator
וליצור אינטרפולטור משלכם. מידע נוסף על כתיבת אינטרפולטור מותאם אישית זמין במאמר שימוש באינטרפולטור.
סיווג/ממשק | תיאור |
---|---|
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
מתחיל לחשב את הערכים
אנימציה, בין 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) (באותיות קאמל) בצורת
set<PropertyName>()
מכיוון שהנכסObjectAnimator
מעדכן את הנכס באופן אוטומטי במהלך האנימציה, צריכה להיות לו גישה לנכס באמצעות שיטת ה-setter הזו. לדוגמה, אם שם המאפיין הואfoo
, צריך שיהיה לכם methodsetFoo()
. אם שיטת ה-setter הזו לא קיימת, יש לכם שלוש אפשרויות:- אם יש לכם זכויות לעשות זאת, עליכם להוסיף לכיתה את שיטת ההגדרה.
- משתמשים בכיתה מעטפת שיש לכם הרשאות לשנות, ומאפשרים לכיתה המעטפת לקבל את הערך באמצעות שיטת setter תקינה ולהעביר אותו לאובייקט המקורי.
- במקומה צריך להשתמש במדיניות
ValueAnimator
.
- אם מציינים רק ערך אחד לפרמטר
values...
באחת משיטות המפעלObjectAnimator
, ההנחה היא שזהו הערך הסופי של האנימציה. לכן, למאפיין האובייקט שאתם יוצרים אנימציה חייבת להיות פונקציית getter שמשמש לקבלת ערך ההתחלה של האנימציה. הפונקציה getter חייבת להיות בקטע שלget<PropertyName>()
. לדוגמה, אם שם המאפיין הואfoo
, צריך שיהיה שיטה בשםgetFoo()
. - השיטות של getter (במידת הצורך) והמגדיר 'הגדרות' של המאפיין שיוצרים אנימציה חייבים
פועלים על אותו סוג כמו ערכי ההתחלה והסיום שציינתם ל-
ObjectAnimator
. לדוגמה, צריך להיות לכםtargetObject.setPropName(float)
ו-targetObject.getPropName()
אם אתם יוצרים אתObjectAnimator
הבא:ObjectAnimator.ofFloat(targetObject, "propName", 1f)
- בהתאם למאפיין או לאובייקט שאתם מפעילים עליהם אנימציה, יכול להיות שתצטרכו להפעיל את השיטה
invalidate()
על View כדי לאלץ את המסך לצייר את עצמו מחדש עם הערכים המעודכנים של האנימציה. עושים זאת ב-callbackonAnimationUpdate()
. לדוגמה, אנימציה של מאפיין הצבע של אובייקט Drawable גורמת לעדכונים במסך רק כשהאובייקט הזה מצויר מחדש. כל הגדרות המאפיינים בתצוגה המפורטת, כמוsetAlpha()
ו-setTranslationX()
, מבטלות את התוקף של התצוגה המפורטת בצורה תקינה, כך שאין צורך לבטל את התוקף של התצוגה המפורטת כשקוראים לשיטות האלה עם ערכים חדשים. למידע נוסף על מאזינים, אפשר לעיין בקטע שעוסק מאזיני אנימציה.
כוריאוגרף של מספר אנימציות באמצעות AnimatorSet
במקרים רבים תרצו להפעיל אנימציה שתלויה בזמן שבו מתחילה אנימציה אחרת, או
מסתיים. מערכת Android מאפשרת לקבץ אנימציות יחד בתוך AnimatorSet
, כדי שתוכלו לציין אם להפעיל אנימציות.
בו-זמנית, ברצף או אחרי עיכוב מסוים. אפשר גם להטמיע אובייקטים של AnimatorSet
זה בתוך זה.
קטע הקוד הבא מפעיל את Animator
הבא
אובייקטים באופן הבא:
- מתבצעת הפעלה של
bounceAnim
. - הפעלה של
squashAnim1
,squashAnim2
,stretchAnim1
ו-stretchAnim2
בו-זמנית. - להפעלה של
bounceBackAnim
. - מתבצעת הפעלה של
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
onAnimationStart()
- מתבצעת קריאה כשהאנימציה מתחילה.onAnimationEnd()
– הפונקציה נקראת בסיום האנימציה.onAnimationRepeat()
– מתבצעת קריאה כשהאנימציה חוזרת על עצמה.onAnimationCancel()
– הקריאה מתבצעת כשהאנימציה מבוטלת. אנימציה שבוטלה קורא גם ל-onAnimationEnd()
, בלי קשר לאופן שבו הם הסתיימו.
ValueAnimator.AnimatorUpdateListener
-
onAnimationUpdate()
– התכונה הזו נקראת 'בכל פריים' באנימציה. אפשר להאזין לאירוע הזה כדי להשתמש בערכים המחושבים שנוצרו על ידיValueAnimator
במהלך אנימציה. כדי להשתמש בערך, שולחים שאילתה לאובייקטValueAnimator
שמוענק לאירוע כדי לקבל את הערך הנוכחי של האנימציה באמצעות השיטהgetAnimatedValue()
. צריך להטמיע את הבורר הזה אם משתמשים ב-ValueAnimator
.בהתאם למאפיין או לאובייקט שאתם יוצרים להם אנימציה, יכול להיות שתצטרכו להפעיל את
invalidate()
ב-View כדי לאלץ את האזור הזה במסך לצייר מחדש את עצמו עם הערכים החדשים של האנימציה. לדוגמה, הנפשה של מאפיין צבע של אובייקט ניתן להזזה גורם לעדכונים במסך רק כאשר האובייקט הזה מצייר את עצמו מחדש. כל הגדרות המאפיינים בתצוגה המפורטת, כמוsetAlpha()
ו-setTranslationX()
, מבטלות את התוקף של התצוגה המפורטת בצורה תקינה, כך שאין צורך לבטל את התוקף של התצוגה המפורטת כשקוראים לשיטות האלה עם ערכים חדשים.
-
יש לך אפשרות להאריך את הכיתה AnimatorListenerAdapter
במקום
להטמיע את הממשק Animator.AnimatorListener
,
אני רוצה ליישם את כל השיטות של 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 יכולות להופיע ולהיעלם באמצעות אנימציה כשאתם מוסיפים אותן ל-ViewGroup או מסירים אותן ממנו, או כשאתם קוראים ל-method setVisibility()
של View עם VISIBLE
, INVISIBLE
או GONE
. כשאתם מוסיפים או מסירים תצוגות, תוכלו להוסיף אנימציה למיקומים החדשים של התצוגות הנותרות ב-ViewGroup. אפשר להגדיר
האנימציות הבאות באובייקט LayoutTransition
בהתקשרות אל setAnimator()
ולהעביר אובייקט Animator
עם
הקבועים הבאים מסוג LayoutTransition
:
APPEARING
- דגל שמציין את האנימציה שפועלת בפריטים שמופיע במאגר.CHANGE_APPEARING
– דגל שמציין את האנימציה שפועלת בפריטים שמשתנים עקב הופעת פריט חדש בקונטיינר.DISAPPEARING
- דגל שמציין את האנימציה שפועלת בפריטים נעלם ממאגר התגים.CHANGE_DISAPPEARING
- דגל שמציין את האנימציה שרצה בפריטים משתנים מפני שפריט נעלם מהמאגר.
אתם יכולים להגדיר אנימציות בהתאמה אישית משלכם לארבעת סוגי האירועים האלה, כדי להתאים אישית את המראה. של מעברי הפריסה, או פשוט להנחות את מערכת האנימציה להשתמש באנימציות ברירת המחדל.
כדי להגדיר את המאפיין 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, המערכת יוצרת אנימציה של תצוגות שנוספות או הוסרו גם ViewGroup וגם את התצוגות המפורטות האחרות ב-ViewGroup.
הנפשה של שינויים במצב התצוגה באמצעות StateListAnimator
בכיתה StateListAnimator
אפשר להגדיר אנימציות שפועלות כשהסטטוס של תצוגה משתנה. האובייקט הזה פועל כ-wrapper
אובייקט Animator
, קריאה לאנימציה הזו בכל פעם שהנקודה
שינויים במצב התצוגה (למשל, שינויים 'לחוצים' או 'ממוקדים').
אפשר להגדיר את StateListAnimator
במשאב XML עם שורש
רכיב <selector>
ורכיבי צאצא מסוג <item>
שכל אחד מהם מציין
מצב תצוגה שונה שהוגדר על ידי המחלקה StateListAnimator
. כל אחד
<item>
מכיל את ההגדרה של קבוצת אנימציה של מאפיינים.
לדוגמה, הקובץ הבא יוצר אנימטור רשימת מצבים שמשנה את קנה המידה של x ו-y של התצוגה בזמן הלחיצה:
<?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
ישמשו כשהמצב של הלחצן הזה ישתנה.
לחלופין, כדי להקצות אנימטור של רשימת מצבים לתצוגה בקוד, משתמשים ב-method AnimatorInflater.loadStateListAnimator()
ומקצים את האנימטור לתצוגה באמצעות ה-method 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
, כך שאין צורך להביא בחשבון את האינטרפולטור כשמחשבים ערכים מונפשים.
שימוש באינטרפולטורים
אינטרפולטור מגדיר איך ערכים ספציפיים באנימציה מחושבים כפונקציה של זמן. לדוגמה, אפשר לציין שהאנימציה תתרחש באופן לינארי לאורך כל האנימציה, כלומר שהאנימציה תנוע באופן שווה לאורך כל הזמן. לחלופין, אפשר לציין שהאנימציה תשתמש בזמן לא לינארי, למשל, באמצעות האצה או האטה בתחילת האנימציה או בסופה.
אינטרפולטורים במערכת האנימציה מקבלים חלק מאנימטורים שמייצגים
הזמן שחלף של האנימציה. אינטרפולטורים משנים את השבר הזה בהתאם לסוג
שהיא נועדה לספק. מערכת Android מספקת קבוצה של אינטרפולטורים נפוצים
android.view.animation package
. אם אף אחד מהם לא מתאים לצרכים שלכם, תוכלו להטמיע את הממשק TimeInterpolator
וליצור ממשק משלכם.
לדוגמה, בהמשך מוצגת השוואה בין האופן שבו המפַתח (interpolator) שמוגדר כברירת מחדל, 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 | 0.8 | 9. |
1000 | 1 | 1 |
כפי שמוצג בטבלה, הפונקציה LinearInterpolator
משנה את הערכים באותה מהירות, 0.2 לכל 200 אלפיות השנייה שחולפות. AccelerateDecelerateInterpolator
משנה את הערכים מהר יותר מ-LinearInterpolator
בין 200 אלפיות השנייה ל-600 אלפיות השנייה, ולאט יותר בין 600 אלפיות השנייה לבין
1,000 אלפיות השנייה.
ציון של נקודות מפתח
אובייקט Keyframe
מורכב מצמד של זמן/ערך שמאפשר להגדיר
מצב ספציפי בזמן מסוים באנימציה. לכל תמונה מפתח יכול להיות גם מערך של משוואות כדי לשלוט בהתנהגות של האנימציה במרווח הזמן שבין הזמן של תמונה המפתח הקודמת לבין הזמן של תמונה המפתח הזו.
כדי ליצור אובייקט Keyframe
, צריך להשתמש באחד מהמפעלים
ofInt()
, ofFloat()
או ofObject()
כדי לקבל את הסוג המתאים של Keyframe
. לאחר מכן, קוראים ל-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 כדי לבטל את החיסרון הזה.
מערכת האנימציה של הנכסים יכולה להציג אנימציה של תצוגות מפורטות במסך על ידי שינוי המאפיינים בפועל באובייקטים של התצוגות המפורטות. לחשבון
בנוסף, תצוגות הנתונים מפעילות באופן אוטומטי את הפונקציה invalidate()
לרענון המסך בכל פעם שהמאפיינים שלו משתנים. המאפיינים החדשים במחלקה View
שמאפשרים אנימציות של מאפיינים הם:
translationX
ו-translationY
: הנכסים האלה קובעים את המיקום של התצוגה המפורטת כסטייה מהקואורדינטות הימנית והעליונה שלה, שמוגדרות על ידי מאגר הפריסה שלה.rotation
,rotationX
ו-rotationY
: הנכסים האלה קובעים את סיבוב התמונה ב-2D (נכסrotation
) וב-3D סביב נקודת הציר.scaleX
ו-scaleY
: המאפיינים האלה קובעים את שינוי הגודל ב-2D של תצוגה מפורטת סביב נקודת הציר שלה.pivotX
ו-pivotY
: המאפיינים האלה קובעים את המיקום של נקודת הציר, שסביבה מתרחשים שינויים של הסיבוב וההתאמה. כברירת מחדל, טבלת הציר נמצאת במרכז האובייקט.x
ו-y
: אלה מאפיינים פשוטים של כלי עזר, שמתארים את המיקום הסופי של התצוגה במאגר, כסכום של הערכים השמאליים והעליונים ו-TranslateX ו-TranslateY.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 המתאימים
בלוג
פוסט.
הצהרה על אנימציות ב-XML
מערכת האנימציות של הנכס מאפשרת להצהיר על אנימציות של נכסים באמצעות קובץ XML במקום לעשות זאת באופן פרוגרמטי. הגדרת האנימציות בפורמט XML מאפשרת להשתמש שוב באנימציות בקלות בכמה פעילויות ולערוך בקלות את רצף האנימציה.
כדי להבדיל בין קובצי אנימציה שמשתמשים ב-API החדש של אנימציות נכסים לבין קובצי אנימציה שמשתמשים במסגרת הקודמת של אנימציית תצוגה, החל מ-Android 3.1 צריך לשמור את קובצי ה-XML של אנימציות הנכסים בספרייה res/animator/
.
במחלקות האנימציה הבאות של המאפיינים יש תמיכה בהצהרת XML עם תגי ה-XML הבאים:
-
ValueAnimator
–<animator>
-
ObjectAnimator
–<objectAnimator>
-
AnimatorSet
–<set>
כדי למצוא את המאפיינים שבהם אפשר להשתמש בהצהרת ה-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.