Einzelnen Wert mit animate*AsState
animieren
Die animate*AsState
-Funktionen sind die einfachsten Animations-APIs in Compose zum Animieren eines einzelnen Werts. Sie geben nur den Zielwert (oder Endwert) an und die API startet die Animation vom aktuellen Wert bis zum angegebenen Wert.
Im Folgenden finden Sie ein Beispiel für eine Animation der Alphaversion mit dieser API. Durch einfaches Einfügen des Zielwerts in animateFloatAsState
ist der Alphawert jetzt ein Animationswert zwischen den angegebenen Werten (in diesem Fall 1f
oder 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) )
Sie müssen keine Instanz einer Animationsklasse erstellen und keine Unterbrechungen beheben. Intern wird ein Animationsobjekt (eine Animatable
-Instanz) erstellt und auf der Aufrufwebsite gespeichert, wobei der erste Zielwert als Anfangswert verwendet wird. Jedes Mal, wenn Sie dieser zusammensetzbaren Funktion einen anderen Zielwert zuweisen, wird automatisch eine Animation in Richtung dieses Werts gestartet. Wenn bereits eine Animation läuft, beginnt die Animation bei ihrem aktuellen Wert (und der 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.
Compose bietet animate*AsState
-Funktionen für Float
, Color
, Dp
, Size
, Offset
, Rect
, Int
, IntOffset
und IntSize
. Sie können auf einfache Weise Unterstützung für andere Datentypen hinzufügen, indem Sie ein TwoWayConverter
für animateValueAsState
angeben, das einen generischen Typ verwendet.
Sie können die Animationsspezifikationen anpassen, indem Sie ein 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 Status können einen beliebigen Datentyp haben. In vielen Fällen können Sie einen benutzerdefinierten enum
-Typ verwenden, um die Typsicherheit zu gewährleisten, wie in diesem Beispiel:
enum class BoxState { Collapsed, Expanded }
updateTransition
erstellt und merkt sich eine Instanz von Transition
und aktualisiert ihren Status.
var currentState by remember { mutableStateOf(BoxState.Collapsed) } val transition = updateTransition(currentState, label = "box state")
Sie können dann eine der animate*
-Erweiterungsfunktionen verwenden, um bei diesem Übergang eine untergeordnete Animation zu definieren. Geben Sie die Zielwerte für jeden Bundesstaat an.
Diese animate*
-Funktionen geben einen Animationswert zurück, der während der Animation jeden 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 der Kombinationen 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 ein Übergang im Zielzustand erreicht ist, ist Transition.currentState
mit Transition.targetState
identisch. Daran erkennen Sie, ob der Übergang abgeschlossen ist.
Manchmal ist es sinnvoll, dass sich der Anfangszustand vom ersten Zielstatus unterscheidet. Dazu können wir updateTransition
mit MutableTransitionState
verwenden. So können wir z. B. die Animation starten, sobald der Code die Zusammensetzung eingegeben hat.
// 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") // ……
Für einen komplexeren Übergang mit mehreren zusammensetzbaren Funktionen können Sie mit createChildTransition
einen untergeordneten Übergang erstellen. Diese Technik ist nützlich, um Bedenken auf mehrere Unterkomponenten in einer komplexen zusammensetzbaren Funktion zu trennen. Der übergeordnete Übergang erkennt 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. Die targetState
für Transition.AnimatedVisibility
und Transition.AnimatedContent
wird aus Transition
abgeleitet und löst bei Bedarf Ein-/Ausgangsübergänge aus, wenn sich der targetState
der Transition
geändert hat. Mit diesen Erweiterungsfunktionen können alle Eingabe-/Exit-/sizeTransform-Animationen, die andernfalls für AnimatedVisibility
/AnimatedContent
intern wären, in die Transition
gezogen werden.
Mit diesen Erweiterungsfunktionen kann die Statusänderung von AnimatedVisibility
/AnimatedContent
von außen beobachtet werden. Anstelle eines booleschen visible
-Parameters verwendet diese Version von AnimatedVisibility
eine Lambda-Funktion, die den Zielstatus des übergeordneten Übergangs in einen booleschen Wert umwandelt.
Weitere Informationen finden Sie unter animateVisibility und animateContent.
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") } } } }
Übergang kapseln und wiederverwendbar machen
Für einfache Anwendungsfälle ist die Definition von Übergangsanimationen in derselben zusammensetzbaren Funktion wie Ihre UI eine durchaus sinnvolle Option. Wenn Sie an einer komplexen Komponente mit einer Reihe von animierten Werten arbeiten, sollten Sie die Animationsimplementierung jedoch von der zusammensetzbaren Benutzeroberfläche trennen.
Dazu erstellen Sie eine Klasse, die alle Animationswerte enthält, und eine Aktualisierungsfunktion, die eine Instanz dieser Klasse zurückgibt. Die Übergangsimplementierung kann in die neue separate Funktion extrahiert werden. Dieses Muster ist nützlich, wenn die Animationslogik zentralisiert werden muss oder komplexe Animationen wiederverwendbar werden müssen.
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) } }
Mit rememberInfiniteTransition
eine sich endlos wiederholende Animation erstellen
InfiniteTransition
enthält eine oder mehrere untergeordnete Animationen wie Transition
. Die Animationen werden jedoch gestartet, sobald sie die Komposition beginnen, und werden erst beendet, wenn sie entfernt werden. Sie können eine Instanz von InfiniteTransition
mit rememberInfiniteTransition
erstellen. Untergeordnete Animationen können mit animateColor
, animatedFloat
oder animatedValue
hinzugefügt werden. Außerdem müssen Sie infiniteRepeatable angeben, um die Animationsspezifikationen festzulegen.
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))
Low-Level-Animations-APIs
Alle im vorherigen Abschnitt erwähnten übergeordneten Animations-APIs basieren auf den Low-Level-Animations-APIs.
Die animate*AsState
-Funktionen sind die einfachsten APIs, die eine sofortige Wertänderung als Animationswert rendern. Unterstützt wird dies durch Animatable
, eine koroutinebasierte API zur Animation eines einzelnen Werts. Mit updateTransition
wird ein Übergangsobjekt erstellt, mit dem mehrere Animationswerte verwaltet und basierend auf einer Statusänderung ausgeführt werden können. rememberInfiniteTransition
ist ähnlich, erzeugt aber einen unendlichen Übergang, mit dem mehrere Animationen verwaltet werden können, die unbegrenzt laufen. Alle diese APIs sind zusammensetzbar, mit Ausnahme von Animatable
. Das bedeutet, dass diese Animationen außerhalb der Komposition erstellt werden können.
Alle diese APIs basieren auf der grundlegenden Animation
API. Obwohl die meisten Apps nicht direkt mit Animation
interagieren, sind einige der Anpassungsmöglichkeiten für Animation
über übergeordnete APIs verfügbar. Weitere Informationen zu AnimationVector
und AnimationSpec
finden Sie unter Animationen anpassen.
Animatable
: Koroutinebasierte Einzelwertanimation
Animatable
ist ein Werthalter, mit dem der Wert animiert werden kann, sobald er über animateTo
geändert wird. Dies ist die API, mit der die Implementierung von animate*AsState
gesichert wird.
Sie sorgt für eine konsistente Fortsetzung und eine gegenseitige Exklusivität, d. h., die Wertänderung erfolgt immer kontinuierlich und alle laufenden Animationen werden abgebrochen.
Viele Features von Animatable
, einschließlich animateTo
, werden als Sperrfunktionen bereitgestellt. Dies bedeutet, dass sie in einen geeigneten Koroutinebereich eingeschlossen werden müssen. Sie können beispielsweise die zusammensetzbare Funktion LaunchedEffect
verwenden, um einen Bereich nur für die Dauer des angegebenen Schlüsselwerts zu 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 obigen Beispiel wird eine Instanz von Animatable
mit dem Anfangswert Color.Gray
erstellt und gespeichert. Abhängig vom Wert des booleschen Flags ok
wird die Farbe zu Color.Green
oder Color.Red
animiert. Jede nachfolgende Änderung des booleschen Werts startet die Animation in die andere Farbe. Wenn sich der Wert ändert und eine Animation läuft, wird sie abgebrochen und die neue Animation beginnt beim aktuellen Snapshot-Wert mit der aktuellen Geschwindigkeit.
Dies ist die Animationsimplementierung, mit der die im vorherigen Abschnitt erwähnte animate*AsState
API gesichert wird. Im Vergleich zu animate*AsState
können wir durch die direkte Verwendung von Animatable
mehrere Aspekte genauer steuern. Erstens kann Animatable
einen Anfangswert haben, der sich von seinem ersten Zielwert unterscheidet.
Im obigen Codebeispiel ist anfangs ein graues Feld zu sehen, das sofort zu Grün oder Rot wird. Zweitens bietet Animatable
weitere Vorgänge für den Inhaltswert, nämlich snapTo
und animateDecay
. snapTo
legt den aktuellen Wert sofort auf den Zielwert fest. Dies ist nützlich, wenn die Animation selbst nicht die einzige Informationsquelle ist und mit anderen Zuständen wie Touch-Ereignissen synchronisiert werden muss. animateDecay
startet eine Animation, die ab der angegebenen Geschwindigkeit langsamer wird. Dies ist nützlich, um das Fling-Verhalten zu implementieren. Weitere Informationen finden Sie unter Gesten und Animationen.
Animatable
unterstützt standardmäßig Float
und Color
. Es kann aber jeder Datentyp verwendet werden, indem ein TwoWayConverter
angegeben wird. Weitere Informationen finden Sie unter AnimationVector.
Sie können die Animationsspezifikationen durch Angabe eines AnimationSpec
anpassen.
Weitere Informationen finden Sie unter AnimationSpec.
Animation
: Manuell gesteuerte Animation
Animation
ist die Animations-API auf der niedrigsten Ebene. Viele der Animationen, die wir bisher gesehen
haben, bauen auf Animation auf. Es gibt zwei Animation
-Untertypen: TargetBasedAnimation
und DecayAnimation
.
Animation
sollte nur verwendet werden, um die Zeit der Animation manuell zu steuern.
Animation
ist zustandslos und hat kein Lebenszykluskonzept. Es dient als Berechnungs-Engine für die Animation, die von den übergeordneten APIs verwendet wird.
TargetBasedAnimation
Die meisten Anwendungsfälle decken andere APIs ab. Wenn Sie TargetBasedAnimation
jedoch direkt verwenden, können Sie die Wiedergabedauer der Animation selbst steuern. Im folgenden Beispiel wird die Wiedergabezeit von TargetAnimation
anhand der von withFrameNanos
bereitgestellten Frame Time manuell gesteuert.
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
Im Gegensatz zu TargetBasedAnimation
muss für DecayAnimation
keine targetValue
angegeben werden. Stattdessen wird targetValue
anhand der Startbedingungen berechnet, die von initialVelocity
und initialValue
sowie den bereitgestellten DecayAnimationSpec
festgelegt wurden.
Decay-Animationen werden häufig nach einem Flachgeste verwendet, um Elemente bis zum Anschlag zu verlangsamen. Die Animationsgeschwindigkeit beginnt beim von initialVelocityVector
festgelegten Wert und wird mit der Zeit langsamer.
Empfehlungen für dich
- Hinweis: Der Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- Animationen anpassen {:#customize-animations}
- Animationen in „Compose“
- Modifikatoren für Animationen und zusammensetzbare Funktionen