Einen einzelnen Wert mit animate*AsState
animieren
Die Funktionen animate*AsState
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 zum angegebenen Wert.
Unten sehen Sie ein Beispiel für die Alpha-Animation mit dieser API. Durch das Einschließen 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, label = "alpha") Box( Modifier .fillMaxSize() .graphicsLayer(alpha = alpha) .background(Color.Red) )
Sie müssen keine Instanz einer Animationsklasse erstellen oder Unterbrechungen behandeln. Im Hintergrund wird ein Animationsobjekt (nämlich eine Animatable
-Instanz) an der Aufrufstelle erstellt und mit dem ersten Zielwert als Anfangswert gespeichert. Ab diesem Zeitpunkt wird jedes Mal, wenn Sie diesem Composeable einen anderen Zielwert zuweisen, automatisch eine Animation gestartet, die auf diesen Wert hinführt. Wenn bereits eine Animation läuft, beginnt die Animation bei ihrem aktuellen Wert (und ihrer aktuellen Geschwindigkeit) und bewegt sich auf den Zielwert zu. Während der Animation wird dieses Composeable neu zusammengesetzt und gibt in jedem 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 die Unterstützung für andere Datentypen ganz einfach hinzufügen, indem Sie TwoWayConverter
bis animateValueAsState
mit einem generischen Typ angeben.
Sie können die Animationsangaben anpassen, indem Sie ein AnimationSpec
angeben.
Weitere Informationen finden Sie unter AnimationSpec.
Mehrere Properties gleichzeitig mit einem Übergang animieren
Transition
verwaltet eine oder mehrere Animationen als untergeordnete Elemente und führt sie gleichzeitig in mehreren Status aus.
Die Status können beliebigen Datentypen haben. In vielen Fällen können Sie einen benutzerdefinierten enum
-Typ verwenden, um für Typsicherheit zu sorgen, wie in diesem Beispiel:
enum class BoxState { Collapsed, Expanded }
updateTransition
erstellt und speichert eine Instanz von Transition
und aktualisiert ihren Status.
var currentState by remember { mutableStateOf(BoxState.Collapsed) } val transition = updateTransition(currentState, label = "box state")
Anschließend können Sie mit einer der animate*
-Erweiterungsfunktionen eine untergeordnete Animation in diesem Übergang definieren. Geben Sie die Zielwerte für jeden der 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 kannst du einen transitionSpec
-Parameter übergeben, um für jede Kombination von Änderungen des Übergangsstatus 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
mit Transition.targetState
identisch. Dies kann als Signal dafür verwendet werden, ob die Umstellung abgeschlossen ist.
Manchmal möchten wir einen Anfangsstatus, der sich vom ersten Zielstatus unterscheidet. 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 createChildTransition
verwenden, um einen untergeordneten Übergang zu erstellen. Diese Technik ist nützlich, um Probleme in mehreren Unterkomponenten in einer komplexen Composeable-Anwendung zu trennen. Der übergeordnete Übergang kennt alle Animationswerte der untergeordneten Übergänge.
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 } ) } }
Übergänge 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 löst bei Bedarf Ein-/Ausstiegsübergänge aus, wenn sich die targetState
der Transition
geändert hat. Mit diesen Erweiterungsfunktionen können alle „enter/exit/sizeTransform“-Animationen, die sonst intern in AnimatedVisibility
/AnimatedContent
wären, in die Transition
verschoben werden.
Mit diesen Erweiterungsfunktionen kann der Status von AnimatedVisibility
/AnimatedContent
von außen beobachtet werden. Anstelle eines booleschen visible
-Parameters nimmt diese Version von AnimatedVisibility
ein Lambda an, das den Zielstatus der übergeordneten Transition in einen Booleschen Wert umwandelt.
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") } } } }
Übergänge kapseln und wiederverwendbar machen
Bei einfachen Anwendungsfällen ist es durchaus sinnvoll, Übergangsanimationen im selben Composeable wie Ihre 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 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 Implementierung der Umstellung kann in die neue separate Funktion extrahiert werden. Dieses Muster ist nützlich, wenn die Animationslogik zentralisiert oder komplexe Animationen wiederverwendbar gemacht werden sollen.
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 endlos wiederholende Animation erstellen
InfiniteTransition
enthält eine oder mehrere untergeordnete Animationen wie Transition
. Die Animationen werden jedoch ausgeführt, sobald sie in die Komposition eintreten, und enden erst, 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 anzugeben.
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-Animation-APIs
Alle im vorherigen Abschnitt genannten High-Level-Animations-APIs bauen auf den Low-Level-Animations-APIs auf.
Die animate*AsState
-Funktionen sind die einfachsten APIs, die eine sofortige Wertänderung als Animationswert rendern. Sie wird von Animatable
unterstützt, einer coroutinebasierten API zum Animieren eines einzelnen Werts. updateTransition
erstellt ein Übergangsobjekt, mit dem mehrere animierte Werte verwaltet und basierend auf einem Statuswechsel ausgeführt werden können. rememberInfiniteTransition
ist ähnlich, erstellt aber einen unendlichen Übergang, mit dem mehrere Animationen verwaltet werden können, die unbegrenzt laufen. Mit Ausnahme von Animatable
sind alle diese APIs composables. Das bedeutet, dass diese Animationen außerhalb der Komposition erstellt werden können.
Alle diese APIs basieren auf der grundlegenden Animation
API. Die meisten Apps interagieren nicht direkt mit Animation
. Einige der Anpassungsfunktionen für Animation
sind jedoch über APIs höherer Ebene verfügbar. Weitere Informationen zu AnimationVector
und AnimationSpec
finden Sie unter Animationen anpassen.
Animatable
: Coroutinenbasierte Animation mit einem einzelnen Wert
Animatable
ist ein Werthalter, mit dem der Wert animiert werden kann, wenn er über 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 Ausschließlichkeit, d. h., die Wertänderung ist immer kontinuierlich und alle laufenden Animationen werden abgebrochen.
Viele Funktionen von Animatable
, einschließlich animateTo
, werden als Pausierfunktionen bereitgestellt. Das bedeutet, dass sie in einen geeigneten coroutine-Umfang eingeschlossen werden müssen. Mit dem LaunchedEffect
-Komposit können Sie beispielsweise einen Gültigkeitsbereich 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 wird eine Instanz von Animatable
mit dem Anfangswert Color.Gray
erstellt und gespeichert. Je nach Wert des booleschen Flags ok
wird die Farbe entweder zu Color.Green
oder Color.Red
animiert. Jede nachfolgende Änderung des booleschen Werts startet die Animation zur anderen Farbe. 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 Animationsumsetzung, die die im vorherigen Abschnitt erwähnte animate*AsState
API unterstützt. Im Vergleich zu animate*AsState
bietet die direkte Verwendung von Animatable
in mehreren Aspekten eine genauere Steuerung. Erstens: Animatable
kann einen Anfangswert haben, der sich vom ersten Zielwert unterscheidet.
Im Codebeispiel oben wird beispielsweise zuerst ein graues Feld angezeigt, das sofort zu grün oder rot animiert wird. Zweitens bietet Animatable
mehr Vorgänge für den Inhaltswert, nämlich snapTo
und animateDecay
. snapTo
setzt den aktuellen Wert sofort auf den Zielwert. 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. animateDecay
startet eine Animation, die von der angegebenen Geschwindigkeit aus verlangsamt wird. Dies ist nützlich, um das Wischverhalten zu implementieren. Weitere Informationen finden Sie unter Gesten und Animationen.
Standardmäßig werden Animatable
Float
und Color
unterstützt. Es kann jedoch jeder Datentyp verwendet werden, indem ein TwoWayConverter
angegeben wird. Weitere Informationen finden Sie unter AnimationVector.
Sie können die Animationsangaben anpassen, indem Sie eine AnimationSpec
angeben.
Weitere Informationen finden Sie unter AnimationSpec.
Animation
: Manuell gesteuerte Animation
Animation
ist die Animation API der untersten Ebene. Viele der bisher gesehenen Animationen basieren auf der Funktion „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 Lebenszykluskonzept. Es dient als Animationsberechnungsmodul, das von den APIs der höheren Ebene verwendet wird.
TargetBasedAnimation
Andere APIs decken die meisten Anwendungsfälle ab. Wenn Sie TargetBasedAnimation
direkt verwenden, können Sie die Wiedergabezeit der Animation jedoch selbst steuern. Im folgenden Beispiel wird die Wiedergabezeit der TargetAnimation
manuell anhand der von withFrameNanos
bereitgestellten Framezeit 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
muss für DecayAnimation
keine targetValue
angegeben werden. Stattdessen wird die targetValue
anhand der Startbedingungen berechnet, die durch initialVelocity
und initialValue
und die angegebene DecayAnimationSpec
festgelegt werden.
Diese Art von Animation wird oft nach einer Wischgeste verwendet, um Elemente langsam zum Stillstand zu bringen. Die Geschwindigkeit der Animation beginnt bei dem mit 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
- Animationen und Composeables