Einen 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. Die API startet die Animation vom aktuellen Wert zum angegebenen Wert.
Unten sehen Sie ein Beispiel für die Animation von Alpha mit dieser API. Indem Sie den Zielwert einfach 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 am Aufrufort gespeichert. Der erste Zielwert wird als Anfangswert verwendet. Wenn Sie diesem Composable-Element einen anderen Zielwert zuweisen, wird automatisch eine Animation für diesen Wert gestartet. Wenn bereits eine Animation läuft, beginnt die neue 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 in jedem Frame einen aktualisierten Animationswert zurück.
Compose bietet standardmäßig animate*AsState
-Funktionen für Float
, Color
, Dp
, Size
, Offset
, Rect
, Int
, IntOffset
und IntSize
. Sie können ganz einfach Unterstützung für andere Datentypen hinzufügen, indem Sie eine TwoWayConverter
-zu-animateValueAsState
-Funktion bereitstellen, die einen generischen Typ akzeptiert.
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 Status 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 }
Mit updateTransition
wird eine Instanz von Transition
erstellt und gespeichert und ihr Status wird aktualisiert.
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 die einzelnen Bundesstaaten an.
Diese animate*
-Funktionen geben einen Animationswert zurück, der bei jeder Aktualisierung des Übergangszustands mit updateTransition
in jedem Frame der Animation 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 Übergangszustandsä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 Transition den Zielstatus erreicht hat, ist Transition.currentState
gleich Transition.targetState
. Dies kann als Signal dafür verwendet werden, ob die Umstellung abgeschlossen ist.
Manchmal soll der Ausgangszustand nicht mit dem ersten Zielzustand übereinstimmen. Dazu können wir updateTransition
mit MutableTransitionState
verwenden. So können wir beispielsweise die 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 Verantwortlichkeiten zwischen mehreren Unterkomponenten in einer komplexen zusammensetzbaren Funktion zu trennen. Bei der übergeordneten Übergangsanimation werden alle Animationswerte in den untergeordneten Übergangsanimationen berücksichtigt.
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 der Transition
abgeleitet und bei Bedarf werden Ein- und Ausblendübergänge ausgelöst, wenn sich die targetState
der Transition
geändert hat. Mit diesen Erweiterungsfunktionen können alle Ein-/Ausgangs- und Größenänderungsanimationen, die sonst intern für AnimatedVisibility
/AnimatedContent
wären, in Transition
verschoben werden.
Mit diesen Erweiterungsfunktionen kann der Status von AnimatedVisibility
/AnimatedContent
von außen beobachtet werden. Anstelle eines booleschen visible
-Parameters wird in dieser Version von AnimatedVisibility
ein Lambda verwendet, das 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 durchaus sinnvoll, Übergangsanimationen im selben Composable wie die Benutzeroberfläche zu definieren. Wenn Sie jedoch an einer komplexen Komponente mit einer Reihe animierter Werte arbeiten, sollten Sie die Animationsimplementierung möglicherweise von der zusammensetzbaren Benutzeroberfläche trennen.
Dazu erstellen Sie eine Klasse, die alle Animationswerte enthält, und eine „update“-Funktion, 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 oder komplexe Animationen wiederverwendbar gemacht 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 Animation erstellen, die sich unendlich oft wiederholt
InfiniteTransition
enthält eine oder mehrere untergeordnete Animationen wie Transition
. Die Animationen werden jedoch sofort ausgeführt, wenn sie in die Komposition eingefügt werden, 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(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-APIs für Animationen
Alle im vorherigen Abschnitt erwähnten High-Level-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. Sie basiert auf Animatable
, einer auf Coroutinen basierenden API zum Animieren eines einzelnen Werts. Mit updateTransition
wird ein Übergangsobjekt erstellt, das mehrere animierte Werte verwalten und sie basierend auf einer Zustandsänderung ausführen kann. rememberInfiniteTransition
ist ähnlich, erstellt aber einen unendlichen Übergang, der mehrere Animationen verwalten kann, die unbegrenzt weiterlaufen. Alle diese APIs sind Composables, mit Ausnahme von Animatable
. Das bedeutet, dass diese Animationen außerhalb der Komposition erstellt werden können.
Alle diese APIs basieren auf der grundlegenderen Animation
API. Die meisten Apps interagieren nicht direkt mit Animation
. Einige der Anpassungsfunktionen für Animation
sind jedoch über APIs auf höherer Ebene verfügbar. 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 über animateTo
geändert wird. Dies ist die API, die der Implementierung von animate*AsState
zugrunde liegt.
Sie sorgt für eine konsistente Fortsetzung und gegenseitige Ausschließlichkeit. Das bedeutet, dass sich der Wert immer kontinuierlich ändert und alle laufenden Animationen abgebrochen werden.
Viele Funktionen von Animatable
, einschließlich animateTo
, werden als suspend-Funktionen bereitgestellt. Das bedeutet, dass sie in einen entsprechenden Coroutine-Scope eingeschlossen werden müssen. Mit der zusammensetzbaren 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 obigen Beispiel erstellen und speichern wir eine Instanz von Animatable
mit dem Anfangswert Color.Gray
. Je nach Wert des booleschen Flags ok
wird die Farbe entweder zu Color.Green
oder zu Color.Red
animiert. Bei jeder nachfolgenden Änderung des booleschen Werts wird die Animation zur anderen Farbe gestartet. Wenn beim Ändern des Werts eine Animation läuft, wird sie abgebrochen und die neue Animation beginnt mit dem aktuellen Snapshot-Wert und der aktuellen Geschwindigkeit.
Dies ist die Animationsimplementierung, die die im vorherigen Abschnitt erwähnte animate*AsState
API unterstützt. Im Vergleich zu animate*AsState
bietet die direkte Verwendung von Animatable
in vielerlei Hinsicht eine genauere Steuerung. Erstens kann Animatable
einen Anfangswert haben, der sich von seinem ersten Zielwert unterscheidet.
Im Codebeispiel oben wird zuerst ein graues Feld angezeigt, das sofort in Grün oder Rot animiert wird. Zweitens bietet Animatable
mehr Vorgänge für den Inhaltswert, nämlich snapTo
und animateDecay
. Mit snapTo
wird der aktuelle Wert sofort auf den Zielwert gesetzt. Das ist nützlich, wenn die Animation selbst nicht die einzige Quelle der Wahrheit ist und mit anderen Status wie Touch-Ereignissen synchronisiert werden muss. Mit animateDecay
wird eine Animation gestartet, die sich von der angegebenen Geschwindigkeit verlangsamt. Dies ist nützlich, um das Verhalten beim schnellen Wischen zu implementieren. Weitere Informationen finden Sie unter Gesten und Animationen.
Standardmäßig unterstützt Animatable
die Datentypen Float
und Color
. Durch Angabe eines TwoWayConverter
kann jedoch ein beliebiger Datentyp verwendet werden. Weitere Informationen finden Sie 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 bisherigen Animationen basieren auf Animation. 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 Konzept für den Lebenszyklus. Sie dient als Engine für die Berechnung von Animationen, die von den APIs auf höherer Ebene verwendet wird.
TargetBasedAnimation
Die meisten Anwendungsfälle werden von anderen APIs abgedeckt. Wenn Sie TargetBasedAnimation
direkt verwenden, können Sie die Wiedergabezeit der Animation selbst steuern. Im Beispiel unten wird die Wiedergabezeit von TargetAnimation
manuell anhand der von withFrameNanos
bereitgestellten Frame-Zeit gesteuert.
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
keine targetValue
erforderlich. Stattdessen wird targetValue
basierend auf den Startbedingungen berechnet, die von initialVelocity
und initialValue
festgelegt werden, sowie auf dem bereitgestellten DecayAnimationSpec
.
Decay-Animationen werden oft nach einer Fling-Geste verwendet, um Elemente zu verlangsamen, bis sie zum Stillstand kommen. Die Animationsgeschwindigkeit beginnt mit dem durch initialVelocityVector
festgelegten Wert und verlangsamt sich im Laufe der Zeit.
Empfehlungen für dich
- Hinweis: Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- Animationen anpassen {:#customize-animations}
- Animationen in Compose
- Modifikatoren und Composables für Animationen