התאמה אישית של אנימציות

רבים מממשקי ה-API ליצירת אנימציות מקבלים פרמטרים לצורך התאמה אישית של ההתנהגות שלהם.

התאמה אישית של אנימציות באמצעות הפרמטר AnimationSpec

רוב ממשקי ה-API ליצירת אנימציות מאפשרים למפתחים להתאים אישית את מפרטי האנימציה באמצעות הפרמטר האופציונלי AnimationSpec.

val alpha: Float by animateFloatAsState(
    targetValue = if (enabled) 1f else 0.5f,
    // Configure the animation duration and easing.
    animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing),
    label = "alpha"
)

יש סוגים שונים של AnimationSpec ליצירת סוגים שונים של אנימציה.

יצירת אנימציה מבוססת-פיזיקה באמצעות spring

spring יוצר אנימציה מבוססת-פיזיקה בין ערכי ההתחלה והסיום. הוא מקבל 2 פרמטרים: dampingRatio ו-stiffness.

dampingRatio מגדיר את מידת ההתנגדות של הקפיץ. ערך ברירת המחדל הוא Spring.DampingRatioNoBouncy.

איור 1. הגדרת יחסי דעיכה שונים של קפיצים.

stiffness מגדיר את המהירות שבה האביב צריך לנוע לעבר ערך הסיום. ערך ברירת המחדל הוא Spring.StiffnessMedium.

איור 2. הגדרת קשיחות שונה של קפיץ

val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = spring(
        dampingRatio = Spring.DampingRatioHighBouncy,
        stiffness = Spring.StiffnessMedium
    ),
    label = "spring spec"
)

האפשרות spring יכולה לטפל בהפרעות בצורה חלקה יותר מאשר סוגי AnimationSpec שמבוססים על משך זמן, כי היא מבטיחה את המשכיות המהירות כשערך היעד משתנה במהלך הנפשות. spring משמש כברירת המחדל של AnimationSpec ב-API רבים של אנימציה, כמו animate*AsState ו-updateTransition.

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

איור 3. הגדרת מפרט tween לעומת spring לאנימציה והפרעה לה.

אנימציה בין ערכי ההתחלה והסיום באמצעות עקומת האטה עם tween

tween יוצר אנימציה בין ערכי ההתחלה והסיום במהלך durationMillis שצוין באמצעות עקומת האטה. tween הוא קיצור של המילה between (בין) – כי הוא בין שני ערכים.

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

val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = tween(
        durationMillis = 300,
        delayMillis = 50,
        easing = LinearOutSlowInEasing
    ),
    label = "tween delay"
)

מידע נוסף זמין במאמר החלקה.

אנימציה לערכים ספציפיים בזמנים מסוימים באמצעות keyframes

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

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

val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = keyframes {
        durationMillis = 375
        0.0f at 0 using LinearOutSlowInEasing // for 0-15 ms
        0.2f at 15 using FastOutLinearInEasing // for 15-75 ms
        0.4f at 75 // ms
        0.4f at 225 // ms
    },
    label = "keyframe"
)

איך חוזרים על אנימציה באמצעות repeatable

repeatable מפעילה אנימציה שמבוססת על משך זמן (כמו tween או keyframes) שוב ושוב עד שהיא מגיעה למספר החזרות שצוין. אפשר להעביר את הפרמטר repeatMode כדי לציין אם האנימציה צריכה לחזור על עצמה מההתחלה (RepeatMode.Restart) או מהסוף (RepeatMode.Reverse).

val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = repeatable(
        iterations = 3,
        animation = tween(durationMillis = 300),
        repeatMode = RepeatMode.Reverse
    ),
    label = "repeatable spec"
)

איך חוזרים על אנימציה ללא הגבלה באמצעות infiniteRepeatable

infiniteRepeatable דומה ל-repeatable, אבל היא חוזרת על עצמה במשך מספר אינסופי של חזרות.

val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = infiniteRepeatable(
        animation = tween(durationMillis = 300),
        repeatMode = RepeatMode.Reverse
    ),
    label = "infinite repeatable"
)

בבדיקות שמשתמשות ב-ComposeTestRule, אנימציות שמשתמשות ב-infiniteRepeatable לא מופעלות. הרכיב יומר באמצעות הערך הראשוני של כל ערך מונפש.

מעבר מיידי לערך הסיום באמצעות snap

snap הוא AnimationSpec מיוחד שמעביר את הערך באופן מיידי לערך הסופי. אפשר לציין את הערך delayMillis כדי לעכב את תחילת האנימציה.

val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = snap(delayMillis = 50),
    label = "snap spec"
)

הגדרת פונקציית העברה (easing) בהתאמה אישית

פעולות AnimationSpec שמבוססות על משך זמן (כמו tween או keyframes) משתמשות ב-Easing כדי לשנות את החלק של האנימציה. כך הערך שמופיע בהנפשה יכול לנוע במהירות גבוהה יותר או איטית יותר, במקום לנוע בקצב קבוע. Fraction הוא ערך בין 0 (התחלה) ל-1.0 (סוף) שמייצג את הנקודה הנוכחית באנימציה.

העברה חלקה היא למעשה פונקציה שמקבלת ערך שבר בין 0 ל-1.0 ומחזירה ערך של float. הערך המוחזר יכול להיות מחוץ לגבול כדי לייצג חריגה מעל או מתחת לערך היעד. אפשר ליצור עקומת Easing בהתאמה אישית כמו הקוד הבא.

val CustomEasing = Easing { fraction -> fraction * fraction }

@Composable
fun EasingUsage() {
    val value by animateFloatAsState(
        targetValue = 1f,
        animationSpec = tween(
            durationMillis = 300,
            easing = CustomEasing
        ),
        label = "custom easing"
    )
    // ……
}

ב-Compose יש כמה פונקציות Easing מובנות שמכסות את רוב תרחישי השימוש. למידע נוסף על בחירת פונקציית ההחלשה (easing) המתאימה לתרחיש שלכם, תוכלו לעיין במאמר מהירות – Material Design.

  • FastOutSlowInEasing
  • LinearOutSlowInEasing
  • FastOutLinearEasing
  • LinearEasing
  • CubicBezierEasing
  • מידע נוסף

אנימציה של סוגי נתונים מותאמים אישית על ידי המרה אל AnimationVector וממנו

רוב ממשקי ה-API ליצירת אנימציות ב-Compose תומכים ב-Float, ב-Color, ב-Dp ובסוגי נתונים בסיסיים אחרים כערכים של אנימציה כברירת מחדל, אבל לפעמים צריך ליצור אנימציה של סוגי נתונים אחרים, כולל סוגי נתונים מותאמים אישית. במהלך האנימציה, כל ערך שמשתנה מיוצג כ-AnimationVector. הערך מומר ל-AnimationVector ולהפך באמצעות TwoWayConverter תואם, כדי שמערכת האנימציה המרכזית תוכל לטפל בהם באופן אחיד. לדוגמה, Int מיוצג כ-AnimationVector1D שמכיל ערך נקודה צפה יחיד. TwoWayConverter עבור Int נראה כך:

val IntToVector: TwoWayConverter<Int, AnimationVector1D> =
    TwoWayConverter({ AnimationVector1D(it.toFloat()) }, { it.value.toInt() })

Color הוא למעשה קבוצה של 4 ערכים, אדום, ירוק, כחול ואלפא, ולכן Color מומר ל-AnimationVector4D שמכיל 4 ערכים מסוג float. כך, כל סוג נתונים שמשמש באנימציות מומר ל-AnimationVector1D, ל-AnimationVector2D, ל-AnimationVector3D או ל-AnimationVector4D, בהתאם למאפייני המימדים שלו. כך אפשר ליצור אנימציה של רכיבים שונים באובייקט בנפרד, לכל אחד מהם עם מעקב מהירות משלו. אפשר לגשת לממירים מובנים של סוגי נתונים בסיסיים באמצעות ממירים כמו Color.VectorConverter או Dp.VectorConverter.

אם רוצים להוסיף תמיכה בסוג נתונים חדש כערך מונפש, אפשר ליצור TwoWayConverter משלכם ולספק אותו ל-API. לדוגמה, אפשר להשתמש ב-animateValueAsState כדי ליצור אנימציה לסוג הנתונים המותאם אישית באופן הבא:

data class MySize(val width: Dp, val height: Dp)

@Composable
fun MyAnimation(targetSize: MySize) {
    val animSize: MySize by animateValueAsState(
        targetSize,
        TwoWayConverter(
            convertToVector = { size: MySize ->
                // Extract a float value from each of the `Dp` fields.
                AnimationVector2D(size.width.value, size.height.value)
            },
            convertFromVector = { vector: AnimationVector2D ->
                MySize(vector.v1.dp, vector.v2.dp)
            }
        ),
        label = "size"
    )
}

הרשימה הבאה כוללת כמה VectorConverter מובנים: