Auf dieser Seite wird beschrieben, wie Sie in Jetpack Compose wertbasierte Animationen erstellen. Der Schwerpunkt liegt dabei auf APIs, die Werte basierend auf ihrem aktuellen und Zielstatus animieren.
Einzelnen Wert mit animate*AsState animieren
Die animate*AsState-Funktionen sind einfache Animations-APIs in Compose, mit denen ein einzelner Wert animiert werden kann. Sie geben nur den Zielwert (oder Endwert) an und die API startet die Animation vom aktuellen Wert zum angegebenen Wert.
Im folgenden Beispiel wird der Alphawert mithilfe dieser API animiert. Wenn Sie den Zielwert in animateFloatAsState einschließen, ist der Alphawert jetzt ein Animationswert zwischen den angegebenen Werten (in diesem Fall 1f oder 0.5f).
var enabled by remember { mutableStateOf(true) } val animatedAlpha: Float by animateFloatAsState(if (enabled) 1f else 0.5f, label = "alpha") Box( Modifier .fillMaxSize() .graphicsLayer { alpha = animatedAlpha } .background(Color.Red) )
Sie müssen keine Instanz einer Animationsklasse erstellen oder Unterbrechungen verarbeiten. Im Hintergrund wird ein Animationsobjekt (eine Animatable-Instanz) erstellt und an der Aufrufstelle gespeichert. Der erste Zielwert wird als Anfangswert verwendet. Wenn Sie dieser zusammensetzbaren Funktion einen anderen Zielwert übergeben, wird automatisch eine Animation zu diesem Wert gestartet. Wenn bereits eine Animation ausgeführt wird, beginnt die Animation mit dem aktuellen Wert (und der aktuellen Geschwindigkeit) und wird in Richtung des Zielwerts animiert. Während der Animation wird diese zusammensetzbare Funktion neu zusammengesetzt und gibt für jeden Frame einen aktualisierten Animationswert zurück.
Standardmäßig bietet Compose animate*AsState-Funktionen für Float, Color, Dp, Size, Offset, Rect, Int, IntOffset und IntSize. Sie können Unterstützung für andere Datentypen hinzufügen, indem Sie einen TwoWayConverter für animateValueAsState bereitstellen, der einen generischen Typ akzeptiert.
Sie können die Animationsspezifikationen anpassen, indem Sie eine AnimationSpec angeben. Weitere Informationen finden Sie unter AnimationSpec.
Mehrere Eigenschaften gleichzeitig mit einem Übergang animieren
Transition verwaltet eine oder mehrere Animationen als untergeordnete Elemente und führt sie gleichzeitig zwischen mehreren Zuständen aus.
Die Zustände können einen beliebigen Datentyp haben. In vielen Fällen können Sie einen benutzerdefinierten enum-Typ verwenden, um die Typsicherheit zu überprüfen, wie in diesem Beispiel:
enum class BoxState { Collapsed, Expanded }
updateTransition erstellt eine Instanz von Transition, merkt sie sich und aktualisiert ihren Status.
var currentState by remember { mutableStateOf(BoxState.Collapsed) } val transition = updateTransition(currentState, label = "box state")
Anschließend können Sie eine der animate*-Erweiterungsfunktionen verwenden, um eine untergeordnete Animation in diesem Übergang zu definieren. Geben Sie die Zielwerte für jeden Status an.
Diese animate*-Funktionen geben einen Animationswert zurück, der während der Animation bei jedem Frame aktualisiert wird, wenn der Übergangsstatus mit updateTransition aktualisiert wird.
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 } }
Optional können Sie einen transitionSpec-Parameter übergeben, um für jede Kombination von Übergangsstatusänderungen einen anderen AnimationSpec anzugeben. Weitere Informationen finden Sie unter 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 } }
Sobald eine Umstellung den Zielstatus erreicht hat, ist Transition.currentState mit Transition.targetState identisch. Sie können dies als Signal dafür verwenden, ob der Übergang abgeschlossen ist.
Manchmal möchten Sie einen Anfangszustand, der sich vom ersten Zielzustand unterscheidet. Sie können dazu updateTransition mit MutableTransitionState verwenden. Sie können damit beispielsweise eine Animation starten, sobald der Code in die Komposition eintritt.
// Start in collapsed state and immediately animate to expanded var currentState = remember { MutableTransitionState(BoxState.Collapsed) } currentState.targetState = BoxState.Expanded val transition = rememberTransition(currentState, label = "box state") // ……
Für einen komplexeren Übergang mit mehreren zusammensetzbaren Funktionen können Sie mit createChildTransition einen untergeordneten Übergang erstellen. Diese Technik ist nützlich, um die Zuständigkeiten zwischen mehreren Unterkomponenten in einer komplexen zusammensetzbaren Funktion zu trennen. Der übergeordnete Übergang kennt alle Animationswerte in den untergeordneten Übergängen.
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 } ) } }
Übergang mit AnimatedVisibility und AnimatedContent verwenden
AnimatedVisibility und AnimatedContent sind als Erweiterungsfunktionen von Transition verfügbar. Der targetState für Transition.AnimatedVisibility und Transition.AnimatedContent wird vom Transition abgeleitet. Wenn sich der targetState des Transition ändert, werden nach Bedarf Animationen für den Einstieg, den Ausstieg und sizeTransform ausgelöst. Mit diesen Erweiterungsfunktionen können Sie alle Einblend-, Ausblend- und sizeTransform-Animationen, die sonst intern für AnimatedVisibility/AnimatedContent wären, in die Transition verschieben. Mit diesen Erweiterungsfunktionen können Sie die Statusänderung von AnimatedVisibility/AnimatedContent von außen beobachten. Anstelle eines booleschen visible-Parameters verwendet diese Version von AnimatedVisibility einen Lambda-Ausdruck, der den Zielstatus des übergeordneten Übergangs in einen booleschen Wert konvertiert.
Weitere Informationen finden Sie unter AnimatedVisibility und 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), shadowElevation = 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") } } } }
Übergang kapseln und wiederverwendbar machen
Bei einfachen Anwendungsfällen ist es sinnvoll, Übergangsanimationen in derselben zusammensetzbaren Funktion wie die Benutzeroberfläche zu definieren. Wenn Sie jedoch an einer komplexen Komponente mit einer Reihe von animierten Werten arbeiten, sollten Sie die Animationsimplementierung von der zusammensetzbaren UI trennen.
Dazu kannst du eine Klasse erstellen, die alle Animationswerte enthält, und eine update-Funktion, die eine Instanz dieser Klasse zurückgibt. Sie können die Implementierung des Übergangs in die neue separate Funktion extrahieren. Dieses Muster ist nützlich, wenn Sie die Animationslogik zentralisieren oder komplexe Animationen wiederverwendbar machen möchten.
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) } }
Erstelle eine sich unendlich wiederholende Animation mit rememberInfiniteTransition
InfiniteTransition enthält eine oder mehrere untergeordnete Animationen wie Transition. Die Animationen werden jedoch gestartet, sobald sie in die Komposition aufgenommen werden, und erst beendet, wenn sie entfernt werden. Sie können eine Instanz von InfiniteTransition mit rememberInfiniteTransition erstellen und untergeordnete Animationen mit animateColor, animateFloat oder animateValue hinzufügen. Sie müssen auch eine infiniteRepeatable angeben, um die Animationsspezifikationen festzulegen.
val infiniteTransition = rememberInfiniteTransition(label = "infinite") val color by infiniteTransition.animateColor( initialValue = Color.Red, targetValue = Color.Green, animationSpec = infiniteRepeatable( animation = tween(1000, easing = LinearEasing), repeatMode = RepeatMode.Reverse ), label = "color" ) Box( Modifier .fillMaxSize() .background(color) )
Low-Level-Animations-APIs
Alle im vorherigen Abschnitt erwähnten High-Level-Animation-APIs basieren auf den Low-Level-Animation-APIs.
Die animate*AsState-Funktionen sind einfache APIs, die eine sofortige Wertänderung als Animationswert rendern. Diese Funktion wird von Animatable unterstützt, einer coroutinebasierten API zum Animieren eines einzelnen Werts.
updateTransition erstellt ein Übergangsobjekt, das mehrere animierte Werte verwalten und bei einer Statusänderung ausführen kann. rememberInfiniteTransition ist ähnlich, erstellt aber einen unendlichen Übergang, der mehrere Animationen verwalten kann, die unbegrenzt fortgesetzt werden. Alle diese APIs sind Composables, mit Ausnahme von Animatable. Das bedeutet, dass Sie diese Animationen außerhalb der Komposition erstellen können.
Alle diese APIs basieren auf der grundlegenderen Animation API. Die meisten Apps interagieren zwar nicht direkt mit Animation, aber Sie können über APIs auf höherer Ebene auf einige der Anpassungsfunktionen zugreifen. Weitere Informationen zu AnimationVector und AnimationSpec finden Sie unter Animationen anpassen.
Animatable: Coroutine-basierte Animation mit einem einzelnen Wert
Animatable ist ein Wert-Holder, der den Wert animieren kann, wenn er mit animateTo geändert wird. Dies ist die API, die die Implementierung von animate*AsState unterstützt. Sie sorgt für eine konsistente Fortsetzung und gegenseitige Exklusivität. Das bedeutet, dass die Wertänderung immer kontinuierlich ist und Compose alle laufenden Animationen abbricht.
Viele Funktionen von Animatable, einschließlich animateTo, sind Sperrfunktionen.
Das bedeutet, dass Sie sie in einen geeigneten Koroutinenbereich einbinden müssen. Mit der komponierbaren Funktion LaunchedEffect können Sie beispielsweise einen Bereich nur für die Dauer des angegebenen Schlüsselwerts erstellen.
// 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) )
Im vorherigen Beispiel erstellen Sie eine Instanz von Animatable mit dem Anfangswert Color.Gray und speichern sie. Je nach Wert des booleschen Flags ok wird die Farbe zu Color.Green oder Color.Red animiert. Jede nachfolgende Änderung des booleschen Werts startet eine Animation zur anderen Farbe.
Wenn eine Animation läuft, während sich der Wert ändert, bricht Compose die Animation ab und die neue Animation beginnt mit dem aktuellen Snapshot-Wert und der aktuellen Geschwindigkeit.
Diese Animatable API ist die zugrunde liegende Implementierung für animate*AsState, die im vorherigen Abschnitt erwähnt wurde. Die direkte Verwendung von Animatable bietet in mehrfacher Hinsicht eine genauere Kontrolle:
- Erstens kann
Animatableeinen Anfangswert haben, der sich vom ersten Zielwert unterscheidet. Im vorherigen Codebeispiel wird beispielsweise zuerst ein graues Feld angezeigt, das sofort zu Grün oder Rot animiert wird. - Zweitens bietet
Animatablemehr Vorgänge für den Inhalt, insbesonderesnapToundanimateDecay.snapTosetzt den aktuellen Wert sofort auf den Zielwert. Das ist nützlich, wenn die Animation nicht die einzige Informationsquelle ist und mit anderen Status wie Touch-Events synchronisiert werden muss.animateDecaystartet eine Animation, die von der angegebenen Geschwindigkeit aus langsamer wird. Das ist nützlich, um ein Schleuderverhalten zu implementieren.
Weitere Informationen finden Sie unter Gesten und Animationen.
Standardmäßig unterstützt Animatable Float und Color. Sie können jedoch jeden Datentyp verwenden, indem Sie einen TwoWayConverter angeben. Weitere Informationen findest du unter AnimationVector.
Sie können die Animationsspezifikationen anpassen, indem Sie ein AnimationSpec angeben.
Weitere Informationen finden Sie unter AnimationSpec.
Animation: Manuell gesteuerte Animation
Animation ist die Animation API auf der niedrigsten Ebene. Viele der Animationen, die wir bisher gesehen haben, basieren auf Animation. Es gibt zwei Animation-Untertypen: TargetBasedAnimation und DecayAnimation.
Verwenden Sie Animation nur, um die Zeit der Animation manuell zu steuern. Animation ist zustandslos und hat kein Konzept für den Lebenszyklus. Sie dient als Animationsberechnungs-Engine für APIs auf höherer Ebene.
TargetBasedAnimation
Andere APIs decken die meisten Anwendungsfälle ab, aber wenn Sie TargetBasedAnimation direkt verwenden, können Sie die Wiedergabezeit der Animation steuern. Im folgenden Beispiel steuern Sie die Wiedergabezeit des TargetAnimation manuell anhand der von withFrameNanos bereitgestellten Frame-Zeit.
val anim = remember { TargetBasedAnimation( animationSpec = tween(200), typeConverter = Float.VectorConverter, initialValue = 200f, targetValue = 1000f ) } var playTime by remember { mutableLongStateOf(0L) } LaunchedEffect(anim) { val startTime = withFrameNanos { it } do { playTime = withFrameNanos { it } - startTime val animationValue = anim.getValueFromNanos(playTime) } while (someCustomCondition()) }
DecayAnimation
Im Gegensatz zu TargetBasedAnimation ist für DecayAnimation kein targetValue erforderlich. Stattdessen wird der targetValue anhand der Startbedingungen berechnet, die durch initialVelocity und initialValue und die bereitgestellte DecayAnimationSpec festgelegt werden.
Zerfallsanimationen werden häufig nach einer Wischgeste verwendet, um Elemente zu verlangsamen und anzuhalten. Die Animationsgeschwindigkeit beginnt bei dem von initialVelocityVector festgelegten Wert und verlangsamt sich im Laufe der Zeit.
Empfehlungen für Sie
- Hinweis: Der Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- Animationen anpassen
- Animationen in Compose
- Animationsmodifikatoren und Compose-Funktionen