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

משתמשים בAnimatedVisibility
כדי להסתיר או להציג רכיב. ילדים בתוך AnimatedVisibility
יכולים להשתמש ב-Modifier.animateEnterExit()
למעבר משלהם לכניסה או ליציאה.
var visible by remember { mutableStateOf(true) } // Animated visibility will eventually remove the item from the composition once the animation has finished. AnimatedVisibility(visible) { // your composable here // ... }
הפרמטרים enter ו-exit של AnimatedVisibility
מאפשרים להגדיר את ההתנהגות של קומפוזבל כשהוא מופיע ונעלם. מידע נוסף זמין במסמכי התיעוד המלאים.
אפשרות נוספת להנפשת הנראות של רכיב שאפשר להרכיב היא להנפיש את השקיפות לאורך זמן באמצעות animateFloatAsState
:
var visible by remember { mutableStateOf(true) } val animatedAlpha by animateFloatAsState( targetValue = if (visible) 1.0f else 0f, label = "alpha" ) Box( modifier = Modifier .size(200.dp) .graphicsLayer { alpha = animatedAlpha } .clip(RoundedCornerShape(8.dp)) .background(colorGreen) .align(Alignment.TopCenter) ) { }
עם זאת, שינוי ערך האלפא מגיע עם אזהרה: הקומפוזיציה נשארת וממשיכה לתפוס את המקום שהוקצה לה. יכול להיות שקוראי מסך ומנגנוני נגישות אחרים עדיין יתייחסו לפריט שמוצג במסך. מצד שני, AnimatedVisibility
בסופו של דבר מסיר את הפריט מההרכבה.

הנפשת צבע הרקע

val animatedColor by animateColorAsState( if (animateBackgroundColor) colorGreen else colorBlue, label = "color" ) Column( modifier = Modifier.drawBehind { drawRect(animatedColor) } ) { // your composable here }
האפשרות הזו יעילה יותר מאשר שימוש ב-Modifier.background()
.
הערך Modifier.background()
מתאים להגדרת צבע חד-פעמית, אבל אם משתמשים בו כדי להנפיש צבע לאורך זמן, יכול להיות שיהיו יותר קומפוזיציות מחדש ממה שצריך.
כדי ליצור אנימציה אינסופית של צבע הרקע, אפשר לעיין בקטע בנושא חזרה על אנימציה.
הנפשה של הגודל של רכיב Composable

ב-Compose אפשר להנפיש את הגודל של רכיבים שניתנים להרכבה בכמה דרכים שונות. משתמשים ב-animateContentSize()
לאנימציות בין שינויים בגודל שאפשר להגדיר.
לדוגמה, אם יש לכם תיבה שמכילה טקסט שיכול להתרחב משורה אחת לכמה שורות, אתם יכולים להשתמש ב-Modifier.animateContentSize()
כדי ליצור מעבר חלק יותר:
var expanded by remember { mutableStateOf(false) } Box( modifier = Modifier .background(colorBlue) .animateContentSize() .height(if (expanded) 400.dp else 200.dp) .fillMaxWidth() .clickable( interactionSource = remember { MutableInteractionSource() }, indication = null ) { expanded = !expanded } ) { }
אפשר גם להשתמש ב-AnimatedContent
, עם SizeTransform
כדי לתאר איך השינויים בגודל צריכים להתבצע.
הנפשת המיקום של רכיב שאפשר להרכיב

כדי להנפיש את המיקום של רכיב שאפשר להרכיב, משתמשים ב-Modifier.offset{ }
בשילוב עם animateIntOffsetAsState()
.
var moved by remember { mutableStateOf(false) } val pxToMove = with(LocalDensity.current) { 100.dp.toPx().roundToInt() } val offset by animateIntOffsetAsState( targetValue = if (moved) { IntOffset(pxToMove, pxToMove) } else { IntOffset.Zero }, label = "offset" ) Box( modifier = Modifier .offset { offset } .background(colorBlue) .size(100.dp) .clickable( interactionSource = remember { MutableInteractionSource() }, indication = null ) { moved = !moved } )
אם רוצים לוודא שרכיבי Composable לא מוצגים מעל או מתחת לרכיבי Composable אחרים כשמנפישים את המיקום או הגודל, צריך להשתמש ב-Modifier.layout{ }
. המשנה הזה מעביר שינויים בגודל ובמיקום אל רכיב האב, ואז הם משפיעים על רכיבי צאצא אחרים.
לדוגמה, אם מעבירים Box
בתוך Column
וצריך להעביר את שאר הילדים כשמעבירים את Box
, צריך לכלול את פרטי ההיסט עם Modifier.layout{ }
באופן הבא:
var toggled by remember { mutableStateOf(false) } val interactionSource = remember { MutableInteractionSource() } Column( modifier = Modifier .padding(16.dp) .fillMaxSize() .clickable(indication = null, interactionSource = interactionSource) { toggled = !toggled } ) { val offsetTarget = if (toggled) { IntOffset(150, 150) } else { IntOffset.Zero } val offset = animateIntOffsetAsState( targetValue = offsetTarget, label = "offset" ) Box( modifier = Modifier .size(100.dp) .background(colorBlue) ) Box( modifier = Modifier .layout { measurable, constraints -> val offsetValue = if (isLookingAhead) offsetTarget else offset.value val placeable = measurable.measure(constraints) layout(placeable.width + offsetValue.x, placeable.height + offsetValue.y) { placeable.placeRelative(offsetValue) } } .size(100.dp) .background(colorGreen) ) Box( modifier = Modifier .size(100.dp) .background(colorBlue) ) }

Modifier.layout{ }
יצירת אנימציה של ריווח פנימי של קומפוזיציה

כדי להנפיש את הריווח הפנימי של רכיב שאפשר להרכיב, משתמשים ב-animateDpAsState
בשילוב עם
Modifier.padding()
:
var toggled by remember { mutableStateOf(false) } val animatedPadding by animateDpAsState( if (toggled) { 0.dp } else { 20.dp }, label = "padding" ) Box( modifier = Modifier .aspectRatio(1f) .fillMaxSize() .padding(animatedPadding) .background(Color(0xff53D9A1)) .clickable( interactionSource = remember { MutableInteractionSource() }, indication = null ) { toggled = !toggled } )
הוספת אנימציה לשינוי הגובה של רכיב שאפשר להרכיב
כדי להנפיש את הגובה של רכיב שאפשר להרכיב, משתמשים ב-animateDpAsState
בשילוב עם Modifier.graphicsLayer{ }
. לשינויים חד-פעמיים בגובה, משתמשים בתג Modifier.shadow()
. אם אתם יוצרים אנימציה לצל, השימוש במגדיר Modifier.graphicsLayer{ }
הוא האפשרות עם הביצועים הטובים יותר.
val mutableInteractionSource = remember { MutableInteractionSource() } val pressed = mutableInteractionSource.collectIsPressedAsState() val elevation = animateDpAsState( targetValue = if (pressed.value) { 32.dp } else { 8.dp }, label = "elevation" ) Box( modifier = Modifier .size(100.dp) .align(Alignment.Center) .graphicsLayer { this.shadowElevation = elevation.value.toPx() } .clickable(interactionSource = mutableInteractionSource, indication = null) { } .background(colorGreen) ) { }
אפשר גם להשתמש ב-Card
composable ולהגדיר את מאפיין הגובה לערכים שונים לכל מצב.
הוספת אנימציה לשינוי גודל, להזזה או לסיבוב של טקסט

כשמנפישים שינוי גודל, תרגום או סיבוב של טקסט, מגדירים את הפרמטר textMotion
ב-TextStyle
לערך TextMotion.Animated
. כך המעברים בין אנימציות הטקסט יהיו חלקים יותר. משתמשים ב-Modifier.graphicsLayer{ }
כדי לתרגם, לסובב או לשנות את הגודל של הטקסט.
val infiniteTransition = rememberInfiniteTransition(label = "infinite transition") val scale by infiniteTransition.animateFloat( initialValue = 1f, targetValue = 8f, animationSpec = infiniteRepeatable(tween(1000), RepeatMode.Reverse), label = "scale" ) Box(modifier = Modifier.fillMaxSize()) { Text( text = "Hello", modifier = Modifier .graphicsLayer { scaleX = scale scaleY = scale transformOrigin = TransformOrigin.Center } .align(Alignment.Center), // Text composable does not take TextMotion as a parameter. // Provide it via style argument but make sure that we are copying from current theme style = LocalTextStyle.current.copy(textMotion = TextMotion.Animated) ) }
הוספת אנימציה לצבע הטקסט

כדי להנפיש את צבע הטקסט, משתמשים בביטוי ה-lambda color
בקומפוזיציה BasicText
:
val infiniteTransition = rememberInfiniteTransition(label = "infinite transition") val animatedColor by infiniteTransition.animateColor( initialValue = Color(0xFF60DDAD), targetValue = Color(0xFF4285F4), animationSpec = infiniteRepeatable(tween(1000), RepeatMode.Reverse), label = "color" ) BasicText( text = "Hello Compose", color = { animatedColor }, // ... )
מעבר בין סוגים שונים של תוכן

כדי ליצור אנימציה בין רכיבים שונים שאפשר להרכיב, משתמשים ב-AnimatedContent
. אם רוצים רק ליצור מעבר רגיל בין רכיבים שאפשר להרכיב, משתמשים ב-Crossfade
.
var state by remember { mutableStateOf(UiState.Loading) } AnimatedContent( state, transitionSpec = { fadeIn( animationSpec = tween(3000) ) togetherWith fadeOut(animationSpec = tween(3000)) }, modifier = Modifier.clickable( interactionSource = remember { MutableInteractionSource() }, indication = null ) { state = when (state) { UiState.Loading -> UiState.Loaded UiState.Loaded -> UiState.Error UiState.Error -> UiState.Loading } }, label = "Animated Content" ) { targetState -> when (targetState) { UiState.Loading -> { LoadingScreen() } UiState.Loaded -> { LoadedScreen() } UiState.Error -> { ErrorScreen() } } }
אפשר להתאים אישית את AnimatedContent
כדי להציג סוגים שונים של מעברים בכניסה וביציאה. מידע נוסף זמין במסמכי התיעוד בנושא AnimatedContent
או בפוסט הזה בבלוג בנושא
AnimatedContent
.
אנימציה בזמן ניווט ליעדים שונים

כדי להנפיש מעברים בין קומפוזיציות כשמשתמשים בארטיפקט navigation-compose, צריך לציין את enterTransition
ואת exitTransition
בקומפוזיציה. אפשר גם להגדיר את האנימציה שתשמש כברירת מחדל לכל היעדים ברמה העליונה NavHost
:
val navController = rememberNavController() NavHost( navController = navController, startDestination = "landing", enterTransition = { EnterTransition.None }, exitTransition = { ExitTransition.None } ) { composable("landing") { ScreenLanding( // ... ) } composable( "detail/{photoUrl}", arguments = listOf(navArgument("photoUrl") { type = NavType.StringType }), enterTransition = { fadeIn( animationSpec = tween( 300, easing = LinearEasing ) ) + slideIntoContainer( animationSpec = tween(300, easing = EaseIn), towards = AnimatedContentTransitionScope.SlideDirection.Start ) }, exitTransition = { fadeOut( animationSpec = tween( 300, easing = LinearEasing ) ) + slideOutOfContainer( animationSpec = tween(300, easing = EaseOut), towards = AnimatedContentTransitionScope.SlideDirection.End ) } ) { backStackEntry -> ScreenDetails( // ... ) } }
יש הרבה סוגים שונים של מעברים לכניסה וליציאה, שמחילים אפקטים שונים על התוכן הנכנס והיוצא. למידע נוסף אפשר לעיין בתיעוד.
חזרה על אנימציה

כדי להפעיל את האנימציה בלופ, משתמשים ב-rememberInfiniteTransition
עם infiniteRepeatable
animationSpec
. משנים את RepeatModes
כדי לציין איך הוא צריך לנוע קדימה ואחורה.
משתמשים ב-repeatable
כדי לחזור על מספר מסוים של פעמים.
val infiniteTransition = rememberInfiniteTransition(label = "infinite") val color by infiniteTransition.animateColor( initialValue = Color.Green, targetValue = Color.Blue, animationSpec = infiniteRepeatable( animation = tween(1000, easing = LinearEasing), repeatMode = RepeatMode.Reverse ), label = "color" ) Column( modifier = Modifier.drawBehind { drawRect(color) } ) { // your composable here }
הפעלת אנימציה בהשקה של רכיב שאפשר להרכיב
LaunchedEffect
פועל כשקומפוזבל נכנס לקומפוזיציה. הוא מפעיל אנימציה בהשקה של קומפוזיציה, ואפשר להשתמש בו כדי להניע את שינוי מצב האנימציה. שימוש ב-Animatable
עם ה-method animateTo
כדי להתחיל את האנימציה בהפעלה:
val alphaAnimation = remember { Animatable(0f) } LaunchedEffect(Unit) { alphaAnimation.animateTo(1f) } Box( modifier = Modifier.graphicsLayer { alpha = alphaAnimation.value } )
יצירת אנימציות רציפות

משתמשים בממשקי ה-API של קורוטינות Animatable
כדי להפעיל אנימציות רציפות או מקבילות. הפעלת animateTo
ב-Animatable
אחת אחרי השנייה גורמת לכל אנימציה להמתין עד שהאנימציות הקודמות יסתיימו לפני שהיא מתחילה .
הסיבה לכך היא שזו פונקציית השעיה.
val alphaAnimation = remember { Animatable(0f) } val yAnimation = remember { Animatable(0f) } LaunchedEffect("animationKey") { alphaAnimation.animateTo(1f) yAnimation.animateTo(100f) yAnimation.animateTo(500f, animationSpec = tween(100)) }
יצירת אנימציות בו-זמניות

משתמשים בממשקי ה-API של קורוטינות (Animatable#animateTo()
או animate
) או ב-Transition
API כדי ליצור אנימציות מקבילות. אם משתמשים בכמה פונקציות launch בהקשר של קורוטינה, ההנפשות מופעלות באותו הזמן:
val alphaAnimation = remember { Animatable(0f) } val yAnimation = remember { Animatable(0f) } LaunchedEffect("animationKey") { launch { alphaAnimation.animateTo(1f) } launch { yAnimation.animateTo(100f) } }
אתם יכולים להשתמש ב-API updateTransition
כדי להשתמש באותו מצב להפעלת הרבה אנימציות שונות של נכסים בו-זמנית. בדוגמה הבאה יש אנימציה של שני מאפיינים שנשלטים על ידי שינוי מצב, rect
ו-borderWidth
:
var currentState by remember { mutableStateOf(BoxState.Collapsed) } val transition = updateTransition(currentState, label = "transition") val rect by transition.animateRect(label = "rect") { state -> when (state) { BoxState.Collapsed -> Rect(0f, 0f, 100f, 100f) BoxState.Expanded -> Rect(100f, 100f, 300f, 300f) } } val borderWidth by transition.animateDp(label = "borderWidth") { state -> when (state) { BoxState.Collapsed -> 1.dp BoxState.Expanded -> 0.dp } }
אופטימיזציה של ביצועי האנימציה
אנימציות במצב כתיבה עלולות לגרום לבעיות בביצועים. הסיבה לכך היא האופי של אנימציה: פיקסלים שזזים או משתנים במהירות על המסך, פריים אחר פריים, כדי ליצור אשליה של תנועה.
כדאי לקחת בחשבון את השלבים השונים של Compose: קומפוזיציה, פריסה וציור. אם האנימציה משנה את שלב הפריסה, צריך לפרוס מחדש ולצייר מחדש את כל הרכיבים המושפעים. אם האנימציה מתרחשת בשלב הציור, היא תהיה יעילה יותר כברירת מחדל מאשר אם תפעילו את האנימציה בשלב הפריסה, כי יהיה לה פחות עבודה לעשות באופן כללי.
כדי לוודא שהאפליקציה תבצע כמה שפחות פעולות בזמן האנימציה, כדאי לבחור בגרסת ה-lambda של Modifier
כשזה אפשרי. הפעולה הזו מדלגת על ההרכבה מחדש ומבצעת את האנימציה מחוץ לשלב ההרכבה. אחרת, צריך להשתמש ב-Modifier.graphicsLayer{ }
, כי המגדיר הזה תמיד פועל בשלב הציור. מידע נוסף בנושא הזה זמין בקטע דחיית קריאות במסמכי התיעוד בנושא ביצועים.
שינוי התזמון של האנימציה
כברירת מחדל, ב'יצירה' נעשה שימוש באנימציות קפיציות ברוב האנימציות. הקפיצות, או האנימציות שמבוססות על פיזיקה, נראות טבעיות יותר. בנוסף, אפשר להפריע להם כי הם מתחשבים במהירות הנוכחית של האובייקט, במקום בזמן קבוע.
אם רוצים לשנות את ברירת המחדל, אפשר להגדיר animationSpec
בכל ממשקי ה-API של האנימציה שמוצגים למעלה כדי להתאים אישית את האופן שבו האנימציה פועלת, למשל אם רוצים שהיא תפעל במשך פרק זמן מסוים או שתהיה קופצנית יותר.
הנה סיכום של האפשרויות השונות של animationSpec
:
-
spring
: אנימציה שמבוססת על פיזיקה, ברירת המחדל לכל האנימציות. אפשר לשנות את הערכים של stiffness או dampingRatio כדי להשיג מראה ותחושה שונים של האנימציה. -
tween
(קיצור של between): אנימציה שמבוססת על משך הזמן, אנימציה בין שני ערכים עם פונקציהEasing
. -
keyframes
: מפרט להגדרת ערכים בנקודות מפתח מסוימות באנימציה. -
repeatable
: מפרט שמבוסס על משך זמן ומופעל מספר מסוים של פעמים, שמוגדר על ידיRepeatMode
. -
infiniteRepeatable
: הגדרה מבוססת-משך שפועלת ללא הפסקה. -
snap
: מעבר מיידי לערך הסופי ללא אנימציה.

מידע נוסף על animationSpecs זמין בתיעוד המלא.
מקורות מידע נוספים
דוגמאות נוספות לאנימציות ב-Compose:
- 5 אנימציות מהירות ב-Compose
- איך גורמים למדוזה לזוז בכתיבה
- התאמה אישית של
AnimatedContent
בכלי הכתיבה - הסבר על פונקציות Easing ב-Compose