Compose ma wiele wbudowanych mechanizmów animacji i może być trudno zdecydować, który z nich wybrać. Poniżej znajdziesz listę typowych przypadków użycia animacji. Więcej informacji o pełnym zestawie różnych dostępnych opcji interfejsu API znajdziesz w dokumentacji Compose Animation.
Animowanie typowych właściwości funkcji kompozycyjnych
Compose udostępnia wygodne interfejsy API, które pozwalają rozwiązać wiele typowych przypadków użycia animacji. W tej sekcji dowiesz się, jak animować typowe właściwości funkcji kompozycyjnej.
Animuj pojawianie się i znikanie
Użyj AnimatedVisibility, aby ukryć lub wyświetlić element kompozycyjny. Elementy podrzędne w ramach AnimatedVisibility mogą używać Modifier.animateEnterExit() do własnego przejścia wejścia lub wyjścia.
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 // ... }
Parametry wejścia i wyjścia funkcji AnimatedVisibility pozwalają skonfigurować zachowanie elementu kompozycyjnego, gdy się pojawia i znika. Więcej informacji znajdziesz w pełnej dokumentacji.
Inną opcją animowania widoczności elementu kompozycyjnego jest animowanie kanału alfa w czasie za pomocą 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) ) { }
Jednak zmiana kanału alfa wiąże się z tym, że funkcja kompozycyjna pozostaje w kompozycji i nadal zajmuje miejsce, w którym jest rozmieszczona. Może to spowodować, że czytniki ekranu i inne mechanizmy ułatwień dostępu będą nadal uważać, że element jest na ekranie. Z kolei AnimatedVisibility ostatecznie usuwa element z kompozycji.
Animuj kolor tła
val animatedColor by animateColorAsState( if (animateBackgroundColor) colorGreen else colorBlue, label = "color" ) Column( modifier = Modifier.drawBehind { drawRect(animatedColor) } ) { // your composable here }
Ta opcja jest bardziej wydajna niż użycie Modifier.background().
Modifier.background() jest akceptowalny w przypadku jednorazowego ustawienia koloru, ale podczas animowania koloru w czasie może to spowodować więcej rekompozycji niż jest to konieczne.
Aby animować kolor tła w nieskończoność, zapoznaj się z sekcją dotyczącą powtarzania animacji.
Animowanie rozmiaru elementu kompozycyjnego
Compose umożliwia animowanie rozmiaru komponentów na kilka różnych sposobów. Używaj animateContentSize() do animacji między zmianami rozmiaru funkcji kompozycyjnej.
Jeśli na przykład masz pole zawierające tekst, który może się rozwinąć z jednego do kilku wierszy, możesz użyć Modifier.animateContentSize(), aby uzyskać płynniejsze przejście:
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 } ) { }
Możesz też użyć AnimatedContent z SizeTransform, aby opisać, jak powinny się zmieniać rozmiary.
Animowanie pozycji elementu kompozycyjnego
Aby animować pozycję funkcji kompozycyjnej, użyj Modifier.offset{ } w połączeniu z 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 } )
Jeśli chcesz mieć pewność, że elementy kompozycyjne nie będą rysowane nad innymi elementami kompozycyjnymi ani pod nimi podczas animowania pozycji lub rozmiaru, użyj Modifier.layout{ }. Ten modyfikator przekazuje zmiany rozmiaru i pozycji do elementu nadrzędnego, co wpływa na inne elementy podrzędne.
Jeśli na przykład przenosisz element Box wewnątrz elementu Column, a inne elementy podrzędne muszą zostać przeniesione razem z elementem Box, dołącz informacje o przesunięciu za pomocą elementu Modifier.layout{ } w następujący sposób:
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{ }Animowanie dopełnienia elementu kompozycyjnego
Aby animować dopełnienie elementu kompozycyjnego, użyj animateDpAsState w połączeniu z 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 } )
Animowanie wysokości funkcji kompozycyjnej
Aby animować podniesienie funkcji kompozycyjnej, użyj animateDpAsState w połączeniu z Modifier.graphicsLayer{ }. W przypadku jednorazowych zmian wysokości użyj Modifier.shadow(). Jeśli animujesz cień, użycie modyfikatora Modifier.graphicsLayer{ } jest bardziej wydajną opcją.
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) ) { }
Możesz też użyć funkcji kompozycyjnej Card i ustawić właściwość wysokości na różne wartości w zależności od stanu.
Animowanie skali, przesunięcia lub obrotu tekstu
Podczas animowania skali, przesunięcia lub obrotu tekstu ustaw parametr textMotion w TextStyle na TextMotion.Animated. Dzięki temu przejścia między animacjami tekstu będą płynniejsze. Użyj Modifier.graphicsLayer{ }, aby przesunąć, obrócić lub przeskalować tekst.
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) ) }
Animowanie koloru tekstu
Aby animować kolor tekstu, użyj funkcji lambda color w komponencie 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 }, // ... )
Przełączanie się między różnymi typami treści
Użyj AnimatedContent, aby animować przejścia między różnymi elementami kompozycyjnymi. Jeśli chcesz użyć standardowego zanikania między elementami kompozycyjnymi, użyj 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 można dostosować, aby wyświetlać wiele różnych rodzajów przejść wejścia i wyjścia. Więcej informacji znajdziesz w dokumentacji dotyczącej AnimatedContent lub w tym poście na blogu na temat
AnimatedContent.
Animacja podczas przechodzenia do różnych miejsc docelowych
Aby animować przejścia między elementami kompozycyjnymi podczas korzystania z artefaktu navigation-compose, określ enterTransition i exitTransition w elemencie kompozycyjnym. Możesz też ustawić domyślną animację, która będzie używana we wszystkich miejscach docelowych na najwyższym poziomie 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( // ... ) } }
Istnieje wiele różnych rodzajów przejść wejściowych i wyjściowych, które stosują różne efekty do treści przychodzących i wychodzących. Więcej informacji znajdziesz w dokumentacji.
Powtarzanie animacji
Użyj rememberInfiniteTransition z infiniteRepeatable
animationSpec, aby animacja powtarzała się w nieskończoność. Zmień RepeatModes, aby określić, jak ma się poruszać.
Użyj repeatable, aby powtórzyć określoną liczbę razy.
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 }
Uruchamianie animacji po uruchomieniu funkcji kompozycyjnej
LaunchedEffect jest uruchamiany, gdy element kompozycyjny wchodzi do kompozycji. Uruchamia animację po uruchomieniu funkcji kompozycyjnej. Możesz użyć tego do zmiany stanu animacji. Użycie Animatable z metodą animateTo, aby uruchomić animację przy uruchamianiu:
val alphaAnimation = remember { Animatable(0f) } LaunchedEffect(Unit) { alphaAnimation.animateTo(1f) } Box( modifier = Modifier.graphicsLayer { alpha = alphaAnimation.value } )
Tworzenie animacji sekwencyjnych
Używaj interfejsów API współprogramów Animatable do wykonywania animacji sekwencyjnych lub równoczesnych. Wywołanie metody animateTo na obiekcie Animatable jedna po drugiej powoduje, że każda animacja czeka na zakończenie poprzednich, zanim zostanie uruchomiona.
Dzieje się tak, ponieważ jest to funkcja zawieszenia.
val alphaAnimation = remember { Animatable(0f) } val yAnimation = remember { Animatable(0f) } LaunchedEffect("animationKey") { alphaAnimation.animateTo(1f) yAnimation.animateTo(100f) yAnimation.animateTo(500f, animationSpec = tween(100)) }
Tworzenie jednoczesnych animacji
Aby uzyskać jednoczesne animacje, użyj interfejsów API współprogramów (Animatable#animateTo() lub animate) albo interfejsu API Transition. Jeśli użyjesz wielu funkcji uruchamiania w kontekście współprogramu, animacje zostaną uruchomione jednocześnie:
val alphaAnimation = remember { Animatable(0f) } val yAnimation = remember { Animatable(0f) } LaunchedEffect("animationKey") { launch { alphaAnimation.animateTo(1f) } launch { yAnimation.animateTo(100f) } }
Możesz użyć interfejsu API updateTransition, aby używać tego samego stanu do jednoczesnego sterowania wieloma różnymi animacjami właściwości. W tym przykładzie animowane są 2 właściwości kontrolowane przez zmianę stanu, rect i 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 } }
Optymalizowanie wydajności animacji
Animacje w Compose mogą powodować problemy z wydajnością. Wynika to z charakteru animacji, która polega na szybkim przesuwaniu lub zmienianiu pikseli na ekranie klatka po klatce, aby stworzyć iluzję ruchu.
Weź pod uwagę różne fazy Compose: kompozycję, układ i rysowanie. Jeśli animacja zmienia fazę układu, wymaga ponownego ułożenia i przerysowania wszystkich elementów kompozycyjnych, których dotyczy. Jeśli animacja występuje w fazie rysowania, jest domyślnie bardziej wydajna niż w przypadku uruchomienia jej w fazie układu, ponieważ wymaga mniej pracy.
Aby aplikacja wykonywała jak najmniej działań podczas animacji, w miarę możliwości wybierz wersję lambda funkcji Modifier. Pozwala to pominąć rekompozycję i wykonać animację poza fazą kompozycji. W przeciwnym razie użyj Modifier.graphicsLayer{ }, ponieważ ten modyfikator zawsze działa w fazie rysowania. Więcej informacji na ten temat znajdziesz w sekcji dotyczącej odraczania odczytów w dokumentacji na temat wydajności.
Zmienianie czasu animacji
W Compose domyślnie używane są animacje sprężynowe. Sprężyny, czyli animacje oparte na fizyce, wyglądają bardziej naturalnie. Można je też przerwać, ponieważ uwzględniają bieżącą prędkość obiektu, a nie stały czas.
Jeśli chcesz zastąpić ustawienia domyślne, wszystkie interfejsy Animation API, które pokazaliśmy wcześniej, umożliwiają ustawienie animationSpec, aby dostosować sposób działania animacji, np. czas jej trwania lub efekt odbicia.
Poniżej znajdziesz podsumowanie różnych opcji animationSpec:
spring: animacja oparta na fizyce, domyślna dla wszystkich animacji. Możesz zmienić sztywność lub współczynnik tłumienia, aby uzyskać inny wygląd i działanie animacji.tween(skrót od between): animacja oparta na czasie trwania, animuje między 2 wartościami za pomocą funkcjiEasing.keyframes: specyfikacja do określania wartości w określonych kluczowych punktach animacji.repeatable: specyfikacja oparta na czasie trwania, która jest uruchamiana określoną liczbę razy, określoną przezRepeatMode.infiniteRepeatable: specyfikacja oparta na czasie trwania, która działa bez końca.snap: natychmiast przechodzi do wartości końcowej bez animacji.
Więcej informacji o animationSpecs znajdziesz w pełnej dokumentacji.
Dodatkowe materiały
Więcej przykładów ciekawych animacji w Compose znajdziesz tutaj:
- 5 szybkich animacji w Compose
- Poruszanie meduzą w Compose
- Dostosowywanie elementu
AnimatedContentw Compose - Wprowadzenie do funkcji wygładzania w Compose