Kurzanleitung zu Animationen in Compose

Compose verfügt über viele integrierte Animationsmechanismen und es kann schwierig sein, entscheiden Sie sich für eins. Im Folgenden finden Sie eine Liste gängiger Anwendungsfälle für Animationen. Weitere Informationen zu den verschiedenen verfügbaren API-Optionen findest du in der vollständigen Dokumentation zur Erstellung von Animationen.

Gängige zusammensetzbare Eigenschaften animieren

Compose bietet praktische APIs, mit denen sich viele gängige Anwendungsfälle für Animationen lösen lassen. In diesem Abschnitt wird gezeigt, wie Sie gängige einer zusammensetzbaren Funktion.

Animieren eines Erscheinens / Verschwindens

Grüner zusammensetzbarer Inhalt, der sich selbst ein- und ausblendet
Abbildung 1: Darstellung und Verschwinden eines Elements in einer Spalte animieren

Verwende AnimatedVisibility, um eine zusammensetzbare Funktion ein- oder auszublenden. Untergeordnete Elemente von AnimatedVisibility können Modifier.animateEnterExit() für ihren eigenen Eintritts- oder Ausstiegsübergang verwenden.

var visible by remember {
    mutableStateOf(true)
}
// Animated visibility will eventually remove the item from the composition once the animation has finished.
AnimatedVisibility(visible) {
    // your composable here
    // ...
}

Mit den Eingabe- und Exit-Parametern von AnimatedVisibility können Sie konfigurieren, wie wie sich eine zusammensetzbare Funktion verhält, wenn sie erscheint und wieder verschwindet. Weitere Informationen finden Sie in der vollständigen Dokumentation.

Eine weitere Möglichkeit, die Sichtbarkeit eines Composeables zu animieren, besteht darin, den Alphawert mit animateFloatAsState im Zeitverlauf zu animieren:

var visible by remember {
    mutableStateOf(true)
}
val animatedAlpha by animateFloatAsState(
    targetValue = if (visible) 1.0f else 0f,
    label = "alpha"
)
Box(
    modifier = Modifier
        .size(200.dp)
        .graphicsLayer {
            alpha = animatedAlpha
        }
        .clip(RoundedCornerShape(8.dp))
        .background(colorGreen)
        .align(Alignment.TopCenter)
) {
}

Die Änderung des Alpha-Werts bedeutet jedoch, dass die zusammensetzbare Funktion bestehend aus in der Komposition und nimmt weiterhin den Platz ein, in dem es sich befindet. Dieses dazu führen, dass Screenreader und andere Bedienungshilfen das Element auf dem Bildschirm. Mit AnimatedVisibility wird das Element dagegen irgendwann aus der Komposition entfernt.

Alpha eines Composeables animieren
Abbildung 2: Alphaversion einer zusammensetzbaren Funktion animieren

Hintergrundfarbe animieren

Kombinierbar mit einer Hintergrundfarbe, die sich im Laufe der Zeit als Animation ändert, wobei die Farben ineinander übergehen.
Abbildung 3: Hintergrundfarbe von Composeable animieren

val animatedColor by animateColorAsState(
    if (animateBackgroundColor) colorGreen else colorBlue,
    label = "color"
)
Column(
    modifier = Modifier.drawBehind {
        drawRect(animatedColor)
    }
) {
    // your composable here
}

Diese Option ist leistungsfähiger als die Verwendung von Modifier.background(). Modifier.background() ist für eine einmalige Farbeinstellung akzeptabel, aber wenn eine Farbe im Zeitverlauf animiert wird, kann dies zu mehr Neuzusammensetzungen als nötig führen.

Informationen zur unendlichen Animierung der Hintergrundfarbe finden Sie unter Animation wiederholen .

Größe eines Composeables animieren

Grüner zusammensetzbarer Bereich, der seine Größe reibungslos animiert.
Abbildung 4: Zusammensetzbare Animation zwischen einer kleinen und einer größeren Größe

In Compose können Sie die Größe von Composeables auf verschiedene Arten animieren. Verwenden Sie animateContentSize() für Animationen zwischen Größenänderungen von Kompositionen.

Wenn Sie beispielsweise ein Textfeld haben, das Text enthält, der sich von einem zu Mehrere Linien können Sie Modifier.animateContentSize() verwenden, um eine gleichmäßigere Übergang:

var expanded by remember { mutableStateOf(false) }
Box(
    modifier = Modifier
        .background(colorBlue)
        .animateContentSize()
        .height(if (expanded) 400.dp else 200.dp)
        .fillMaxWidth()
        .clickable(
            interactionSource = remember { MutableInteractionSource() },
            indication = null
        ) {
            expanded = !expanded
        }

) {
}

Sie können auch AnimatedContent mit einem SizeTransform verwenden, um zu beschreiben, wie Größenänderungen erfolgen sollen.

Position der zusammensetzbaren Funktion animieren

Grüner zusammensetzbarer Bereich, der sich reibungslos nach unten und rechts animiert
Abbildung 5: Zusammensetzbares Verschieben durch einen Offset

Wenn Sie die Position eines Composeables animieren möchten, verwenden Sie Modifier.offset{ } in Kombination mit animateIntOffsetAsState().

var moved by remember { mutableStateOf(false) }
val pxToMove = with(LocalDensity.current) {
    100.dp.toPx().roundToInt()
}
val offset by animateIntOffsetAsState(
    targetValue = if (moved) {
        IntOffset(pxToMove, pxToMove)
    } else {
        IntOffset.Zero
    },
    label = "offset"
)

Box(
    modifier = Modifier
        .offset {
            offset
        }
        .background(colorBlue)
        .size(100.dp)
        .clickable(
            interactionSource = remember { MutableInteractionSource() },
            indication = null
        ) {
            moved = !moved
        }
)

Wenn Sie sicherstellen möchten, dass zusammensetzbare Funktionen nicht über oder unter anderen zusammensetzbare Funktionen verwenden, wenn Sie für die Animation von Position oder Größe Modifier.layout{ } verwenden. Dieser Modifikator überträgt Größe und Position an das übergeordnete Element, was sich dann auf andere untergeordnete Elemente auswirkt.

Wenn Sie z. B. ein Box innerhalb einer Column und die anderen untergeordneten Elemente verschieben, verschieben müssen, wenn Box bewegt wird, fügen Sie die Versatzinformationen mit Modifier.layout{ } so:

var toggled by remember {
    mutableStateOf(false)
}
val interactionSource = remember {
    MutableInteractionSource()
}
Column(
    modifier = Modifier
        .padding(16.dp)
        .fillMaxSize()
        .clickable(indication = null, interactionSource = interactionSource) {
            toggled = !toggled
        }
) {
    val offsetTarget = if (toggled) {
        IntOffset(150, 150)
    } else {
        IntOffset.Zero
    }
    val offset = animateIntOffsetAsState(
        targetValue = offsetTarget, label = "offset"
    )
    Box(
        modifier = Modifier
            .size(100.dp)
            .background(colorBlue)
    )
    Box(
        modifier = Modifier
            .layout { measurable, constraints ->
                val offsetValue = if (isLookingAhead) offsetTarget else offset.value
                val placeable = measurable.measure(constraints)
                layout(placeable.width + offsetValue.x, placeable.height + offsetValue.y) {
                    placeable.placeRelative(offsetValue)
                }
            }
            .size(100.dp)
            .background(colorGreen)
    )
    Box(
        modifier = Modifier
            .size(100.dp)
            .background(colorBlue)
    )
}

Zwei Felder, wobei das zweite seine X‑ und Y‑Position animiert und das dritte sich ebenfalls um den Wert Y bewegt.
Abbildung 6: Animation mit Modifier.layout{ }

Padding einer zusammensetzbaren Funktion animieren

Die grüne zusammensetzbare Funktion wird durch einen Klick kleiner und größer und die Abstände sind animiert.
Abbildung 7. Kombinierbar mit animierten Padding-Werten

Um den Abstand einer zusammensetzbaren Funktion zu animieren, verwenden Sie animateDpAsState in Kombination mit Modifier.padding():

var toggled by remember {
    mutableStateOf(false)
}
val animatedPadding by animateDpAsState(
    if (toggled) {
        0.dp
    } else {
        20.dp
    },
    label = "padding"
)
Box(
    modifier = Modifier
        .aspectRatio(1f)
        .fillMaxSize()
        .padding(animatedPadding)
        .background(Color(0xff53D9A1))
        .clickable(
            interactionSource = remember { MutableInteractionSource() },
            indication = null
        ) {
            toggled = !toggled
        }
)

Höhe einer zusammensetzbaren Funktion animieren

Abbildung 8. Die Höhenanimation von zusammensetzbaren Funktionen wird beim Klicken animiert.

Um die Höhe einer zusammensetzbaren Funktion zu animieren, verwenden Sie animateDpAsState in Kombination mit Modifier.graphicsLayer{ }. Verwenden Sie für einmalige Höhenänderungen Modifier.shadow(). Wenn Sie den Schatten animieren, ist die Verwendung des Modifier.graphicsLayer{ }-Modifikators die leistungsstärkere Option.

val mutableInteractionSource = remember {
    MutableInteractionSource()
}
val pressed = mutableInteractionSource.collectIsPressedAsState()
val elevation = animateDpAsState(
    targetValue = if (pressed.value) {
        32.dp
    } else {
        8.dp
    },
    label = "elevation"
)
Box(
    modifier = Modifier
        .size(100.dp)
        .align(Alignment.Center)
        .graphicsLayer {
            this.shadowElevation = elevation.value.toPx()
        }
        .clickable(interactionSource = mutableInteractionSource, indication = null) {
        }
        .background(colorGreen)
) {
}

Alternativ können Sie die zusammensetzbare Funktion Card verwenden und die Höheneigenschaft auf für die einzelnen Bundesstaaten.

Text skalieren, verschieben oder drehen

Text, der zu einem Spruch kombiniert werden kann
Abbildung 9: Text wird nahtlos zwischen zwei Größen animiert.

Legen Sie beim Animieren der Skalierung, Verschiebung oder Drehung von Text den textMotion fest. auf TextStyle auf TextMotion.Animated gesetzt. So werden flüssigere Übergänge zwischen Textanimationen ermöglicht. Verwenden Sie Modifier.graphicsLayer{ }, um den Text zu übersetzen, zu drehen oder zu skalieren.

val infiniteTransition = rememberInfiniteTransition(label = "infinite transition")
val scale by infiniteTransition.animateFloat(
    initialValue = 1f,
    targetValue = 8f,
    animationSpec = infiniteRepeatable(tween(1000), RepeatMode.Reverse),
    label = "scale"
)
Box(modifier = Modifier.fillMaxSize()) {
    Text(
        text = "Hello",
        modifier = Modifier
            .graphicsLayer {
                scaleX = scale
                scaleY = scale
                transformOrigin = TransformOrigin.Center
            }
            .align(Alignment.Center),
        // Text composable does not take TextMotion as a parameter.
        // Provide it via style argument but make sure that we are copying from current theme
        style = LocalTextStyle.current.copy(textMotion = TextMotion.Animated)
    )
}

Textfarbe animieren

Der Text
Abbildung 10. Beispiel für eine animierte Textfarbe

Um die Textfarbe zu animieren, verwende die Lambda-Funktion color in der zusammensetzbaren Funktion BasicText:

val infiniteTransition = rememberInfiniteTransition(label = "infinite transition")
val animatedColor by infiniteTransition.animateColor(
    initialValue = Color(0xFF60DDAD),
    targetValue = Color(0xFF4285F4),
    animationSpec = infiniteRepeatable(tween(1000), RepeatMode.Reverse),
    label = "color"
)

BasicText(
    text = "Hello Compose",
    color = {
        animatedColor
    },
    // ...
)

Zwischen verschiedenen Inhaltstypen wechseln

Greenscreen mit Text
Abbildung 11. Änderungen zwischen verschiedenen zusammensetzbaren Funktionen mit animateContent animieren (verlangsamt)

Mit AnimatedContent kannst du zwischen verschiedenen Composeables animieren. Wenn du nur einen Standard-Überblendungseffekt zwischen Composeables haben möchtest, verwende Crossfade.

var state by remember {
    mutableStateOf(UiState.Loading)
}
AnimatedContent(
    state,
    transitionSpec = {
        fadeIn(
            animationSpec = tween(3000)
        ) togetherWith fadeOut(animationSpec = tween(3000))
    },
    modifier = Modifier.clickable(
        interactionSource = remember { MutableInteractionSource() },
        indication = null
    ) {
        state = when (state) {
            UiState.Loading -> UiState.Loaded
            UiState.Loaded -> UiState.Error
            UiState.Error -> UiState.Loading
        }
    },
    label = "Animated Content"
) { targetState ->
    when (targetState) {
        UiState.Loading -> {
            LoadingScreen()
        }
        UiState.Loaded -> {
            LoadedScreen()
        }
        UiState.Error -> {
            ErrorScreen()
        }
    }
}

AnimatedContent kann so angepasst werden, dass viele verschiedene Arten von Ein- und Ausblendungsübergängen angezeigt werden. Weitere Informationen finden Sie in der Dokumentation zu AnimatedContent oder in diesem Blogpost zu AnimatedContent.

Animationen beim Wechseln zwischen verschiedenen Zielen

Zwei Elemente, ein grünes mit der Aufschrift „Landing“ und ein blaues mit der Aufschrift „Detail“, die durch Ziehen des Detailelements über das Landingpage-Element animiert werden.
Abbildung 12. Animieren von zusammensetzbaren Funktionen mit „navigation-compose“

Um Übergänge zwischen zusammensetzbaren Funktionen zu animieren, wenn Sie die navigation-compose verwenden, geben Sie enterTransition und exitTransition für eine zusammensetzbare Funktion. Sie können die Standardanimation auch wird für alle Ziele auf der obersten Ebene NavHost verwendet:

val navController = rememberNavController()
NavHost(
    navController = navController, startDestination = "landing",
    enterTransition = { EnterTransition.None },
    exitTransition = { ExitTransition.None }
) {
    composable("landing") {
        ScreenLanding(
            // ...
        )
    }
    composable(
        "detail/{photoUrl}",
        arguments = listOf(navArgument("photoUrl") { type = NavType.StringType }),
        enterTransition = {
            fadeIn(
                animationSpec = tween(
                    300, easing = LinearEasing
                )
            ) + slideIntoContainer(
                animationSpec = tween(300, easing = EaseIn),
                towards = AnimatedContentTransitionScope.SlideDirection.Start
            )
        },
        exitTransition = {
            fadeOut(
                animationSpec = tween(
                    300, easing = LinearEasing
                )
            ) + slideOutOfContainer(
                animationSpec = tween(300, easing = EaseOut),
                towards = AnimatedContentTransitionScope.SlideDirection.End
            )
        }
    ) { backStackEntry ->
        ScreenDetails(
            // ...
        )
    }
}

Es gibt viele verschiedene Arten von Wechsel zwischen Ein- und Ausblenden. unterschiedliche Auswirkungen auf eingehende und ausgehende Inhalte haben, siehe Dokumentation.

Animation wiederholen

Ein grüner Hintergrund, der sich durch Animieren der beiden Farben in einen blauen Hintergrund verwandelt.
Abbildung 13. Hintergrundfarbe, die unendlich zwischen zwei Werten animiert wird

Verwenden Sie rememberInfiniteTransition mit einem infiniteRepeatable animationSpec, um die Animation fortlaufend zu wiederholen. Ändern Sie RepeatModes, um anzugeben, wie vor- und zurückgegangen werden soll.

Mit finiteRepeatable können Sie eine bestimmte Anzahl von Wiederholungen festlegen.

val infiniteTransition = rememberInfiniteTransition(label = "infinite")
val color by infiniteTransition.animateColor(
    initialValue = Color.Green,
    targetValue = Color.Blue,
    animationSpec = infiniteRepeatable(
        animation = tween(1000, easing = LinearEasing),
        repeatMode = RepeatMode.Reverse
    ),
    label = "color"
)
Column(
    modifier = Modifier.drawBehind {
        drawRect(color)
    }
) {
    // your composable here
}

Animation beim Start einer zusammensetzbaren Funktion starten

LaunchedEffect wird ausgeführt, wenn ein Composeable in die Komposition eintritt. Los gehts eine Animation beim Start einer zusammensetzbaren Funktion. Statusänderung. Animatable mit der Methode animateTo verwenden, um die Animation beim Starten zu starten:

val alphaAnimation = remember {
    Animatable(0f)
}
LaunchedEffect(Unit) {
    alphaAnimation.animateTo(1f)
}
Box(
    modifier = Modifier.graphicsLayer {
        alpha = alphaAnimation.value
    }
)

Sequenzielle Animationen erstellen

Vier Kreise mit grünen Pfeilen, die nacheinander animiert werden.
Abbildung 14. Diagramm, das die einzelnen Schritte einer sequentiellen Animation zeigt

Verwenden Sie die Animatable-Coroutinen-APIs, um sequenzielle oder parallele Animationen auszuführen. Wenn Sie animateTo nacheinander auf Animatable anwenden, wartet jede Animation, bis die vorherigen Animationen abgeschlossen sind, bevor sie fortfährt. Der Grund hierfür ist, dass es sich um eine Sperren-Funktion handelt.

val alphaAnimation = remember { Animatable(0f) }
val yAnimation = remember { Animatable(0f) }

LaunchedEffect("animationKey") {
    alphaAnimation.animateTo(1f)
    yAnimation.animateTo(100f)
    yAnimation.animateTo(500f, animationSpec = tween(100))
}

Parallele Animationen erstellen

Drei Kreise mit grünen Pfeilen, die jeweils als Animation dienen und alle gleichzeitig animieren
Abbildung 15. Diagramm, das den Fortschritt gleichzeitiger Animationen zeigt.

Verwenden Sie die Koroutine-APIs (Animatable#animateTo() oder animate) oder die Transition API verwenden, um gleichzeitige Animationen zu erstellen. Wenn Sie mehrere Funktionen im Kontext einer Koroutine starten, starten sie die Animationen im gleichen Zeit:

val alphaAnimation = remember { Animatable(0f) }
val yAnimation = remember { Animatable(0f) }

LaunchedEffect("animationKey") {
    launch {
        alphaAnimation.animateTo(1f)
    }
    launch {
        yAnimation.animateTo(100f)
    }
}

Mit der updateTransition API können Sie denselben Status verwenden, um viele verschiedene Unterkunftsanimationen gleichzeitig zu steuern. Im Beispiel unten wird eine Animation zwei Eigenschaften, die durch eine Statusänderung gesteuert werden, rect und borderWidth:

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

val rect by transition.animateRect(label = "rect") { state ->
    when (state) {
        BoxState.Collapsed -> Rect(0f, 0f, 100f, 100f)
        BoxState.Expanded -> Rect(100f, 100f, 300f, 300f)
    }
}
val borderWidth by transition.animateDp(label = "borderWidth") { state ->
    when (state) {
        BoxState.Collapsed -> 1.dp
        BoxState.Expanded -> 0.dp
    }
}

Animationsleistung optimieren

Animationen in „Compose“ können zu Leistungsproblemen führen. Dies liegt an der Natur des Animation: schnelles Verschieben oder Ändern von Pixeln auf dem Bildschirm, Frame für Frame, um die Illusion von Bewegung zu erzeugen.

Berücksichtigen Sie die verschiedenen Phasen des Komponierens: Komposition, Layout und Zeichnen. Wenn sich durch Ihre Animation die Layoutphase ändert, müssen alle betroffenen Composables neu layoutet und neu gezeichnet werden. Wenn die Animation in der Zeichenphase erfolgt, ist sie standardmäßig leistungsfähiger als wenn Sie die Animation in der Layoutphase ausführen würden, da insgesamt weniger Arbeit anfällt.

Damit Ihre App während der Animation so wenig wie möglich belastet wird, wählen Sie nach Möglichkeit die Lambda-Version eines Modifier aus. Dabei wird die Neuzusammensetzung übersprungen und außerhalb der Kompositionsphase zu erstellen. Andernfalls verwenden Sie Modifier.graphicsLayer{ }, da dieser Modifikator immer in der Zeichnung ausgeführt wird . Weitere Informationen hierzu finden Sie im Abschnitt Lesevorgänge aussetzen in Leistungsdokumentation.

Timing der Animation ändern

Beim Schreiben werden standardmäßig Frühlingsanimationen verwendet. Federn oder physikbasierte Animationen wirken natürlicher. Sie können auch unterbrochen werden, da sie die aktuelle Geschwindigkeit des Objekts anstelle einer festen Zeit berücksichtigen. Wenn Sie die Standardeinstellung überschreiben möchten, können Sie mit allen oben genannten Animation APIs einen animationSpec festlegen, um die Ausführung einer Animation anzupassen. So können Sie beispielsweise festlegen, dass sie über einen bestimmten Zeitraum ausgeführt werden soll oder dass sie federleichter sein soll.

Im Folgenden finden Sie eine Zusammenfassung der verschiedenen animationSpec-Optionen:

  • spring: Physikbasierte Animationen, die Standardeinstellung für alle Animationen. Sie können die Steifigkeit oder das Dämpfungsverhältnis ändern, um ein anderes Erscheinungsbild der Animation zu erzielen.
  • tween (kurz für zwischen): Animationsdauer nach Dauer, animiert mit der Funktion Easing zwischen zwei Werten.
  • keyframes: Spezifikation zum Angeben von Werten an bestimmten Schlüsselpunkten in einer Animation.
  • repeatable: Dauerbasierte Spezifikation, die eine bestimmte Anzahl von Malen ausgeführt wird, die durch RepeatMode angegeben wird.
  • infiniteRepeatable: Dauerbasierte Spezifikation, die unbegrenzt läuft.
  • snap: Andockt sofort und ohne Animation an den Endwert.
Alt-Text hier eingeben
Abbildung 16. Kein Spezifikationssatz im Vergleich zu benutzerdefiniertem Spring-Spezifikationssatz

Weitere Informationen zu animationSpecs finden Sie in der vollständigen Dokumentation.

Weitere Informationen

Weitere Beispiele für lustige Animationen in „Schreiben“ finden Sie hier: