Wertbasierte Animationen

Einzelnen Wert mit animate*AsState animieren

Die animate*AsState-Funktionen sind die einfachsten Animations-APIs in Compose für zum Animieren eines einzelnen Werts. Sie geben nur den Zielwert (oder Endwert) an. 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 das einfache Einbinden des Zielwert 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 oder eines Unterbrechung. Intern ist ein Animationsobjekt (ein Animatable) Instanz) erstellt und gespeichert wird, wobei die erste Ziel-URL -Wert als Anfangswert festlegen. Sobald Sie diese zusammensetzbare Funktion bereitstellen, wird automatisch eine Animation gestartet, Wert. Wenn bereits eine Animation läuft, beginnt die Animation bei Aktueller Wert (und Geschwindigkeit) und führt eine Animation in Richtung des Zielwerts. Während der Animation wird diese zusammensetzbare Funktion neu zusammengesetzt und gibt eine aktualisierte Animation zurück jeden Frame einen Wert hat.

„Compose“ bietet animate*AsState-Funktionen für Float, Color, Dp, Size, Offset, Rect, Int, IntOffset und IntSize Sie können problemlos andere Datentypen unterstützen, indem Sie eine TwoWayConverter auf animateValueAsState, die einen generischen Typ verwenden.

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 aus gleichzeitig zwischen mehreren Bundesstaaten.

Die Status können einen beliebigen Datentyp haben. In vielen Fällen können Sie eine benutzerdefinierte enum um die Typsicherheit zu gewährleisten. Beispiel:

enum class BoxState {
    Collapsed,
    Expanded
}

updateTransition erstellt und merkt sich eine Instanz von Transition und aktualisiert sie und deren Zustand.

var currentState by remember { mutableStateOf(BoxState.Collapsed) }
val transition = updateTransition(currentState, label = "box state")

Sie können dann mit einer der animate*-Erweiterungsfunktionen ein untergeordnetes Element definieren an diesem Übergang zu einer Animation ein. Geben Sie die Zielwerte für jeden Bundesstaat an. Diese animate*-Funktionen geben einen Animationswert zurück, der jeden Frame aktualisiert wird. während der Animation, wenn der Übergangsstatus mit updateTransition.

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 einen anderen AnimationSpec für jede der Kombinationen von Änderungen des Übergangsstatus. 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 Zielstatus erreicht ist, Transition.currentState ist mit Transition.targetState identisch. Dies kann als Signal für ob der Wechsel abgeschlossen ist.

Manchmal möchten wir einen anderen Anfangszustand als das erste Ziel verwenden. Bundesstaat. Wir können updateTransition mit MutableTransitionState verwenden, um dies. So können wir z. B. die Animation starten, sobald der Code Zusammensetzung.

// 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 createChildTransition verwenden um einen untergeordneten Übergang zu erstellen. Diese Technik ist nützlich, in einer komplexen zusammensetzbaren Funktion auf mehrere Unterkomponenten zugreifen. Die übergeordnete Umstellung alle Animationswerte in den untergeordneten Übergängen beachten.

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. Das targetState für Transition.AnimatedVisibility und Transition.AnimatedContent werden abgeleitet aus Transition und löst je nach Bedarf einen Wechsel zum Ein-/Ausstieg aus, wenn der targetState von Transition hat sich geändert. Mit diesen Erweiterungsfunktionen die Enter-/Exit-/sizeTransform-Animationen, die andernfalls intern wären. AnimatedVisibility/AnimatedContent werden in die Transition geschoben. Mit diesen Erweiterungsfunktionen wird der Status von AnimatedVisibility/AnimatedContent Veränderung von außen beobachtet werden kann. Anstelle eines booleschen visible-Parameters Diese Version von AnimatedVisibility verwendet eine Lambda-Funktion, die das übergeordnete Element Zielstatus des Übergangs in einen booleschen Wert.

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 lassen sich Übergangsanimationen in derselben zusammensetzbaren Funktion wie ist dies eine gute Option. Wenn Sie an einer komplexen Komponente arbeiten mit einer Reihe animierter Werte zu verknüpfen. Sie sollten die die Animationsimplementierung über die zusammensetzbare Benutzeroberfläche.

Erstellen Sie dazu eine Klasse, die alle Animationswerte und ein eine "update"-Funktion, die eine Instanz dieser Klasse zurückgibt. Der Übergang Implementierung in die neue separate Funktion extrahiert. Dieses Muster ist nützlich, wenn Sie die Animationslogik zentralisieren oder komplexe Animationen wiederverwendbar.

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, aber werden die Animationen gestartet, sobald die Komposition beginnt. solange sie nicht entfernt werden. Sie können eine Instanz von InfiniteTransition erstellen mit rememberInfiniteTransition. Untergeordnete Animationen können hinzugefügt werden mit animateColor, animatedFloat oder animatedValue. Außerdem müssen Sie eine unfiniteRepeatable zum Angeben der Animation. Spezifikationen.

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 High-Level-Animations-APIs basieren auf der Low-Level-Animations-APIs.

Die animate*AsState-Funktionen sind die einfachsten APIs, die eine Instant- als Animationswert verwendet werden. Es wird durch Animatable unterstützt, was eine koroutinebasierte API zur Animation eines einzelnen Werts. updateTransition erstellt einen Übergangsobjekt, das mehrere Animationswerte verwalten und basierend auf diesen zu einer Zustandsänderung. rememberInfiniteTransition ist ähnlich, erstellt aber ein Endloser Übergang, mit dem mehrere Animationen verwaltet werden können, die weiter ausgeführt werden auf unbestimmte Zeit. Alle diese APIs sind zusammensetzbar, mit Ausnahme von Animatable, das Diese Animationen können außerhalb der Komposition erstellt werden.

Alle diese APIs basieren auf der grundlegenden Animation API. Die meisten Apps interagieren nicht direkt mit Animation, einige der Anpassungen Funktionen für Animation sind über übergeordnete APIs verfügbar. Weitere Informationen finden Sie unter Passen Sie Animationen an, um mehr über die AnimationVector und AnimationSpec.

Diagramm, das die Beziehung zwischen den verschiedenen Low-Level-Animations-APIs zeigt

Animatable: Koroutinebasierte Einzelwertanimation

Animatable ist ein Werthalter, der den Wert animieren kann, sobald er über animateTo. Dies ist die API, mit der die Implementierung von animate*AsState gesichert wird. Es sorgt für eine einheitliche Fortsetzung und gegenseitige Exklusivität, d. h., das Die Wertänderung erfolgt immer kontinuierlich und alle laufenden Animationen werden abgebrochen.

Viele Funktionen von Animatable, einschließlich animateTo, werden als gesperrt angeboten Funktionen. Das bedeutet, dass sie in eine geeignete Koroutine eingebunden werden müssen. Umfang. Sie können beispielsweise die zusammensetzbare Funktion LaunchedEffect verwenden, um ein für die Dauer des angegebenen Schlüssel/Wert-Paars festgelegt.

// 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 den Anfangswert von Color.Gray. Je nach Wert des booleschen Flags ok wird die Farbe zu Color.Green oder Color.Red animiert. Alle nachfolgenden Durch Ändern in den booleschen Wert wird die Animation in die andere Farbe gestartet. Wenn es eine wenn der Wert geändert wird, die Animation abgebrochen wird und der Neue Animation beginnt beim aktuellen Snapshot-Wert mit der aktuellen Geschwindigkeit.

Dies ist die Animationsimplementierung, die die animate*AsState API sichert die wir im vorherigen Abschnitt erwähnt haben. Im Vergleich zu animate*AsState bei Verwendung von Mit Animatable können wir mehrere Aspekte direkt präziser steuern. Erstens: Animatable kann einen Anfangswert haben, der sich vom ersten Zielwert unterscheidet. Das obige Codebeispiel zeigt zunächst ein graues Feld, das sofort zu Grün oder Rot. Zweitens bietet Animatable mehr Vorgänge für den Inhaltswert, nämlich snapTo und animateDecay. snapTo setzt den aktuellen Wert sofort auf den Zielwert. Dies ist nützlich, wenn der Animationen selbst sind nicht die einzige Informationsquelle und müssen mit anderen z. B. Touch-Events. animateDecay startet eine Animation, die langsamer wird von der gegebenen Geschwindigkeit ab. Dies ist nützlich, um das Fling-Verhalten zu implementieren. Weitere Informationen finden Sie unter Weitere Informationen finden Sie unter Geste und Animation.

Standardmäßig unterstützt Animatable Float und Color, aber jeder Datentyp kann durch Angabe einer TwoWayConverter verwendet werden. Weitere Informationen finden Sie unter AnimationVector für weitere Informationen ein.

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, auf der Animation aufbauen. 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-Modul für Animationen, das von den übergeordneten APIs verwendet wird.

TargetBasedAnimation

Andere APIs decken die meisten Anwendungsfälle ab, aber die direkte Verwendung von TargetBasedAnimation können Sie die Wiedergabedauer der Animation selbst steuern. Im Beispiel unten Die Wiedergabedauer von TargetAnimation wird manuell basierend auf dem Frame gesteuert. Uhrzeit von withFrameNanos angegeben.

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, DecayAnimation erfordert keine Angabe einer targetValue. Stattdessen berechnet es seine targetValue basierend auf den von initialVelocity festgelegten Startbedingungen und initialValue und die bereitgestellte DecayAnimationSpec.

Decay-Animationen werden oft nach einem Flachgeste verwendet, um Elemente anhalten. Die Animationsgeschwindigkeit beginnt bei dem von initialVelocityVector festgelegten Wert und wird mit der Zeit langsamer.