הוספת אנימציה של ערך יחיד באמצעות animate*AsState
הפונקציות animate*AsState
הן ממשקי ה-API הפשוטים ביותר לאנימציה ב-Composer עבור
ליצירת אנימציה של ערך יחיד. אתם מציינים רק את ערך היעד (או ערך הסיום), וגם
ה-API מתחיל אנימציה מהערך הנוכחי לערך שצוין.
בהמשך מוצגת דוגמה לאנימציה של גרסת אלפא באמצעות ה-API הזה. פשוט על ידי האריזה של
ערך היעד ב-animateFloatAsState
, ערך האלפא הוא עכשיו ערך אנימציה
בין הערכים שצוינו (1f
או 0.5f
במקרה הזה).
var enabled by remember { mutableStateOf(true) } val alpha: Float by animateFloatAsState(if (enabled) 1f else 0.5f) Box( Modifier.fillMaxSize() .graphicsLayer(alpha = alpha) .background(Color.Red) )
שימו לב שאתם לא צריכים ליצור מופע של כל מחלקה של אנימציה או כינוי
נוסף. מתחת למכסה הכובע נמצא אובייקט אנימציה (כלומר, Animatable
'חלון ראווה') ייווצר ויישמר באתר ההתקשרות, כשהיעד הראשון הוא
כערך הראשוני. מכאן ואילך, בכל פעם שתספקו את התוכן הקומפוזבילי הזה
ערך יעד שונה, האנימציה תתחיל באופן אוטומטי בערך
עם ערך מסוים. אם כבר קיימת אנימציה בטיסה, האנימציה תתחיל
הערך הנוכחי (ומהירות) ואנימציה כלפי ערך היעד. במהלך
אנימציה, התוכן הקומפוזבילי הזה עובר קומפוזביליות מחדש ומחזיר אנימציה מעודכנת
ערך כל מסגרת.
התכונה 'כתיבה' מספקת פונקציות animate*AsState
עבור Float
,
Color
, Dp
, Size
, Offset
, Rect
, Int
, IntOffset
וגם
IntSize
. כדי להוסיף בקלות תמיכה בסוגי נתונים אחרים, מומלץ לספק
TwoWayConverter
עד animateValueAsState
מסוג גנרי.
אם רוצים להתאים אישית את מפרטי האנימציה, אפשר לספק AnimationSpec
.
מידע נוסף זמין בקטע AnimationSpec.
יצירת אנימציה של מספר נכסים בו-זמנית באמצעות מעבר
Transition
מנהל/ת אנימציה אחת או יותר בתור הצאצאים שלו, ומפעיל אותן
בו-זמנית בין מספר מדינות.
המצבים יכולים להיות מכל סוג של נתונים. במקרים רבים אפשר להשתמש ב-enum
בהתאמה אישית
כדי לוודא שהסוג בטוח, כמו בדוגמה הבאה:
enum class BoxState { Collapsed, Expanded }
updateTransition
יוצר וזוכר מופע של Transition
ועדכונים
את מצבה.
var currentState by remember { mutableStateOf(BoxState.Collapsed) } val transition = updateTransition(currentState, label = "box state")
לאחר מכן אפשר להשתמש באחת מ-animate*
פונקציות של תוסף כדי להגדיר חשבון צאצא
אנימציה במעבר הזה. מציינים את ערכי היעד לכל אחת מהמדינות.
פונקציות animate*
האלה מחזירות ערך אנימציה שמתעדכן בכל פריים
במהלך האנימציה, כשמצב המעבר מתעדכן
updateTransition
.
val rect by transition.animateRect(label = "rectangle") { state -> when (state) { BoxState.Collapsed -> Rect(0f, 0f, 100f, 100f) BoxState.Expanded -> Rect(100f, 100f, 300f, 300f) } } val borderWidth by transition.animateDp(label = "border width") { state -> when (state) { BoxState.Collapsed -> 1.dp BoxState.Expanded -> 0.dp } }
אפשר גם להעביר את הפרמטר transitionSpec
כדי לציין
AnimationSpec
לכל אחד מהשילובים של השינויים במצב המעבר. צפייה
מידע נוסף זמין במאמר AnimationSpec.
val color by transition.animateColor( transitionSpec = { when { BoxState.Expanded isTransitioningTo BoxState.Collapsed -> spring(stiffness = 50f) else -> tween(durationMillis = 500) } }, label = "color" ) { state -> when (state) { BoxState.Collapsed -> MaterialTheme.colorScheme.primary BoxState.Expanded -> MaterialTheme.colorScheme.background } }
לאחר שהמעבר יגיע למדינת היעד, Transition.currentState
תהיה זהה ל-Transition.targetState
. זה יכול לשמש כאות עבור
האם המעבר הסתיים.
לפעמים אנחנו רוצים שהמצב הראשוני יהיה שונה מהיעד הראשון
. אנחנו יכולים להשתמש ב-updateTransition
עם MutableTransitionState
כדי להשיג
הזה. לדוגמה, הוא מאפשר לנו להתחיל באנימציה מיד אחרי שהקוד נכנס
של משפטים יחידים,
// Start in collapsed state and immediately animate to expanded var currentState = remember { MutableTransitionState(BoxState.Collapsed) } currentState.targetState = BoxState.Expanded val transition = updateTransition(currentState, label = "box state") // ……
כדי לבצע מעבר מורכב יותר שכולל כמה פונקציות קומפוזביליות, אפשר
להשתמש ב-createChildTransition
כדי ליצור מעבר בין צאצאים. השיטה הזו שימושית להפרדת בעיות
בין כמה רכיבי משנה בתוכן קומפוזבילי מורכב. המעבר להורים
להיות מודעים לכל ערכי האנימציה במעברי הצאצאים.
enum class DialerState { DialerMinimized, NumberPad } @Composable fun DialerButton(isVisibleTransition: Transition<Boolean>) { // `isVisibleTransition` spares the need for the content to know // about other DialerStates. Instead, the content can focus on // animating the state change between visible and not visible. } @Composable fun NumberPad(isVisibleTransition: Transition<Boolean>) { // `isVisibleTransition` spares the need for the content to know // about other DialerStates. Instead, the content can focus on // animating the state change between visible and not visible. } @Composable fun Dialer(dialerState: DialerState) { val transition = updateTransition(dialerState, label = "dialer state") Box { // Creates separate child transitions of Boolean type for NumberPad // and DialerButton for any content animation between visible and // not visible NumberPad( transition.createChildTransition { it == DialerState.NumberPad } ) DialerButton( transition.createChildTransition { it == DialerState.DialerMinimized } ) } }
שימוש במעבר עם AnimatedVisibility
ועם AnimatedContent
AnimatedVisibility
ו-AnimatedContent
זמינות כפונקציות של תוספים של Transition
. targetState
עבור
נגזר של Transition.AnimatedVisibility
ו-Transition.AnimatedContent
מ-Transition
, והפעלת מעברים בין כניסה/יציאה לפי הצורך כאשר
בוצע שינוי בנכס targetState
של Transition
. פונקציות התוספים האלה מאפשרות
לאנימציות כניסה/יציאה/גודל טרנספורמציה, שאחרת היו פנימיות
AnimatedVisibility
/AnimatedContent
יועברו אל Transition
.
עם פונקציות התוספים האלה, המצב של AnimatedVisibility
/AnimatedContent
של שינוי מבחוץ. במקום פרמטר visible
בוליאני,
הגרסה הזו של AnimatedVisibility
לוקחת את lambda שממירה את ההורה
מעבר למצב היעד של ערך בוליאני.
פרטים נוספים זמינים במאמרים AnimatedVisibility ו-AnimatedContent.
var selected by remember { mutableStateOf(false) } // Animates changes when `selected` is changed. val transition = updateTransition(selected, label = "selected state") val borderColor by transition.animateColor(label = "border color") { isSelected -> if (isSelected) Color.Magenta else Color.White } val elevation by transition.animateDp(label = "elevation") { isSelected -> if (isSelected) 10.dp else 2.dp } Surface( onClick = { selected = !selected }, shape = RoundedCornerShape(8.dp), border = BorderStroke(2.dp, borderColor), elevation = elevation ) { Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) { Text(text = "Hello, world!") // AnimatedVisibility as a part of the transition. transition.AnimatedVisibility( visible = { targetSelected -> targetSelected }, enter = expandVertically(), exit = shrinkVertically() ) { Text(text = "It is fine today.") } // AnimatedContent as a part of the transition. transition.AnimatedContent { targetState -> if (targetState) { Text(text = "Selected") } else { Icon(imageVector = Icons.Default.Phone, contentDescription = "Phone") } } } }
כיסוי מעבר שמאפשר שימוש חוזר
בתרחישים לדוגמה פשוטים, הגדרת אנימציות מעבר באותו תוכן קומפוזבילי כמו ממשק המשתמש שלכם הוא אפשרות לגיטימית לגמרי. כשעובדים על רכיב מורכב במספר ערכים מונפשים, אבל ייתכן שתרצו להפריד של אנימציה מממשק המשתמש הקומפוזבילי.
כדי לעשות את זה, יוצרים מחלקה שמכילה את כל ערכי האנימציה הפונקציה update שמחזירה מופע של המחלקה הזו. המעבר לחלץ אותו לפונקציה הנפרדת החדשה. הדפוס הזה שימושי כאשר צריך לרכז את לוגיקת האנימציה לריכוז או להפוך אנימציות שניתן לעשות בהן שימוש חוזר.
enum class BoxState { Collapsed, Expanded } @Composable fun AnimatingBox(boxState: BoxState) { val transitionData = updateTransitionData(boxState) // UI tree Box( modifier = Modifier .background(transitionData.color) .size(transitionData.size) ) } // Holds the animation values. private class TransitionData( color: State<Color>, size: State<Dp> ) { val color by color val size by size } // Create a Transition and return its animation values. @Composable private fun updateTransitionData(boxState: BoxState): TransitionData { val transition = updateTransition(boxState, label = "box state") val color = transition.animateColor(label = "color") { state -> when (state) { BoxState.Collapsed -> Color.Gray BoxState.Expanded -> Color.Red } } val size = transition.animateDp(label = "size") { state -> when (state) { BoxState.Collapsed -> 64.dp BoxState.Expanded -> 128.dp } } return remember(transition) { TransitionData(color, size) } }
יצירת אנימציה שחוזרת על עצמה ללא הגבלה באמצעות rememberInfiniteTransition
InfiniteTransition
מכילה לפחות אנימציה אחת של ילדים כמו Transition
, אבל
האנימציות מתחילות לפעול ברגע שהן נכנסות ליצירה
עוצרים, אלא אם מסירים אותם. אפשר ליצור מופע של InfiniteTransition
עם rememberInfiniteTransition
. אפשר להוסיף אנימציות צאצא עם
animateColor
, animatedFloat
או animatedValue
. צריך גם לציין
infiniteRepeatable כדי לציין את האנימציה
מפרט.
val infiniteTransition = rememberInfiniteTransition() val color by infiniteTransition.animateColor( initialValue = Color.Red, targetValue = Color.Green, animationSpec = infiniteRepeatable( animation = tween(1000, easing = LinearEasing), repeatMode = RepeatMode.Reverse ) ) Box(Modifier.fillMaxSize().background(color))
ממשקי API של אנימציה ברמה נמוכה
כל ממשקי ה-API ברמה הכללית של אנימציה שהוזכרו בקטע הקודם מבוססים על הבסיס של ממשקי ה-API של אנימציה ברמה נמוכה.
הפונקציות animate*AsState
הן ממשקי ה-API הפשוטים ביותר, כלומר
כערך אנימציה. הוא מגובה על ידי Animatable
,
API מבוסס-coroutine להנפשה של ערך יחיד. updateTransition
יוצר
אובייקט מעבר שיכול לנהל ערכים מרובים של אנימציה ולהריץ אותם על סמך
על שינוי במדינה. rememberInfiniteTransition
דומה, אבל הוא יוצר
מעבר אינסופי שיכול לנהל מספר אנימציות שממשיכות לפעול
ללא הגבלת זמן. כל ממשקי ה-API האלה הם תכנים קומפוזביליים, מלבד Animatable
,
המשמעות היא שניתן ליצור את האנימציות האלה בלי הרכבה.
כל ממשקי ה-API האלה מבוססים על ה-API הבסיסי יותר של Animation
. למרות שברוב
אפליקציות לא יקיימו אינטראקציה ישירות עם Animation
, חלק מההתאמה האישית
היכולות של Animation
זמינות דרך ממשקי API ברמה גבוהה יותר. צפייה
למידע נוסף על התאמה אישית של אנימציות
AnimationVector
וגם AnimationSpec
.
Animatable
: אנימציה של ערך יחיד שמבוסס על קורוטין
Animatable
הוא שומר ערך שיכול להוסיף אנימציה לערך כשהוא משתנה דרך
animateTo
זהו ה-API שמגבה את ההטמעה של animate*AsState
.
הוא מבטיח המשך עקבי ובלעדיות הדדית, כלומר
שינוי הערך הוא תמיד מתמשך וכל אנימציה מתמשכת תבוטל.
הרבה תכונות של Animatable
, כולל animateTo
, זמינות כהשעיה
למשימות ספציפיות. המשמעות היא שצריך לעטוף אותם בקורוטין מתאים
היקף. לדוגמה, אתם יכולים להשתמש בתוכן הקומפוזבילי LaunchedEffect
כדי ליצור
היקף רק למשך ערך המפתח שצוין.
// Start out gray and animate to green/red based on `ok` val color = remember { Animatable(Color.Gray) } LaunchedEffect(ok) { color.animateTo(if (ok) Color.Green else Color.Red) } Box(Modifier.fillMaxSize().background(color.value))
בדוגמה שלמעלה, אנחנו יוצרים וזוכרים מופע של Animatable
עם
בערך הראשוני של Color.Gray
. בהתאם לערך של הדגל הבוליאני
ok
, הצבע מכיל אנימציה של Color.Green
או Color.Red
. כל אירוע המשך
הערך הבוליאני מפעיל את האנימציה לצבע האחר. אם יש
אנימציה מתמשכת כשהערך משתנה, האנימציה מבוטלת,
אנימציה חדשה מתחילה מערך תמונת המצב הנוכחי במהירות הנוכחית.
זוהי הטמעת האנימציה שמגבה את ה-API של animate*AsState
שהוזכרו בקטע הקודם. בהשוואה ל-animate*AsState
, נעשה שימוש
השירות Animatable
נותן לנו שליטה פרטנית יותר מכמה היבטים. קודם כל,
אפשר להגדיר ל-Animatable
ערך ראשוני שונה מערך היעד הראשון.
לדוגמה, הקוד לדוגמה שלמעלה מציג תחילה תיבה אפורה, שמיד לאחר מכן
מתחילה אנימציה לירוק או לאדום. שנית, Animatable
מספק יותר
פעולות על ערך התוכן, snapTo
ו-animateDecay
. snapTo
מגדירה את הערך הנוכחי לערך היעד באופן מיידי. זה שימושי כאשר
האנימציה עצמה היא לא מקור האמת היחיד וצריך לסנכרן אותה עם
מצבי מגע, כמו אירועי מגע. animateDecay
מפעיל אנימציה שנמצאת בהילוך איטי
מהמהירות הנתונה. האפשרות הזו שימושית להטמעת התנהגות של החלקה. צפייה
תנועה ואנימציה לקבלת מידע נוסף.
מחוץ למסגרת, Animatable
תומך ב-Float
וב-Color
, אבל כל סוג נתונים יכול
משמש לציון TwoWayConverter
. צפייה
למידע נוסף, AnimationVector.
כדי להתאים אישית את מפרטי האנימציה, אפשר לספק AnimationSpec
.
מידע נוסף זמין בקטע AnimationSpec.
Animation
: אנימציה בשליטה ידנית
Animation
הוא ה-API של אנימציה ברמה הנמוכה ביותר. הרבה מהאנימציות
שראינו עד עכשיו פיתוחים מעל אנימציה. יש שני סוגי משנה של Animation
:
TargetBasedAnimation
ו-DecayAnimation
.
אפשר להשתמש בAnimation
רק כדי לשלוט באופן ידני בזמן האנימציה.
Animation
הוא ללא שמירת מצב, ואין בו מושג של מחזור חיים. הוא
משמש כמנוע לחישוב אנימציה, שבו משתמשים ממשקי API ברמה גבוהה יותר.
TargetBasedAnimation
ממשקי API אחרים מכסים את רוב תרחישי השימוש, אבל משתמשים ישירות ב-TargetBasedAnimation
מאפשר לכם לשלוט בעצמכם בזמן הפעלת האנימציה. בדוגמה הבאה,
זמן ההפעלה של TargetAnimation
נשלט באופן ידני בהתאם למסגרת
השעה סופקה על ידי withFrameNanos
.
val anim = remember { TargetBasedAnimation( animationSpec = tween(200), typeConverter = Float.VectorConverter, initialValue = 200f, targetValue = 1000f ) } var playTime by remember { mutableStateOf(0L) } LaunchedEffect(anim) { val startTime = withFrameNanos { it } do { playTime = withFrameNanos { it } - startTime val animationValue = anim.getValueFromNanos(playTime) } while (someCustomCondition()) }
DecayAnimation
בניגוד ל-TargetBasedAnimation
,
DecayAnimation
לא דורש targetValue
. במקום זאת, היא מחשבת
targetValue
על סמך תנאי ההתחלה, שהוגדרו על ידי initialVelocity
וגם
initialValue
והערך שצוין, DecayAnimationSpec
.
לרוב נעשה שימוש באנימציות דעיכה לאחר תנועת החלקה כדי להאט אלמנטים
עצירת ביניים. מהירות האנימציה מתחילה בערך שהוגדר על ידי initialVelocityVector
והוא מואט עם הזמן.
מומלץ עבורך
- הערה: טקסט הקישור מוצג כאשר JavaScript מושבת
- התאמה אישית של אנימציות {:#customize-animations}
- אנימציות בכתיבה
- מגבילי אנימציה ותכנים קומפוזביליים