Kurzanleitung zu Animationen in Compose

Compose bietet viele integrierte Animationsmechanismen. Es kann schwierig sein, den richtigen auszuwählen. Im Folgenden finden Sie eine Liste mit häufigen Anwendungsfällen für Animationen. Weitere Informationen zu den verschiedenen API-Optionen, die Ihnen zur Verfügung stehen, finden Sie in der vollständigen Dokumentation zu Compose Animation.

Häufig verwendete zusammensetzbare Eigenschaften animieren

Compose bietet praktische APIs, mit denen Sie viele gängige Anwendungsfälle für Animationen abdecken können. In diesem Abschnitt wird gezeigt, wie Sie gängige Eigenschaften eines Composables animieren können.

Ein- und Ausblenden animieren

Grüne Komponente, die sich selbst ein- und ausblendet
Abbildung 1. Ein- und Ausblenden eines Elements in einer Spalte animieren

Mit AnimatedVisibility können Sie ein Composable ein- oder ausblenden. Kinder innerhalb von AnimatedVisibility können Modifier.animateEnterExit() für ihren eigenen Ein- oder Ausstieg 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 Ein- und Ausstiegsparametern von AnimatedVisibility können Sie konfigurieren, wie sich eine Composable verhält, wenn sie angezeigt und ausgeblendet wird. Weitere Informationen finden Sie in der vollständigen Dokumentation.

Eine weitere Möglichkeit, die Sichtbarkeit eines Composables zu animieren, besteht darin, den Alphawert im Zeitverlauf mit animateFloatAsState 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)
) {
}

Wenn Sie den Alphawert ändern, bleibt die Composable jedoch in der Komposition und nimmt weiterhin den ihr zugewiesenen Platz ein. Das kann dazu führen, dass Screenreader und andere Bedienungshilfen das Element weiterhin auf dem Bildschirm berücksichtigen. AnimatedVisibility entfernt das Element schließlich aus der Komposition.

Alpha eines Composables animieren
Abbildung 2: Alpha einer Composable-Funktion animieren

Hintergrundfarbe animieren

Composable mit Hintergrundfarbe, die sich im Laufe der Zeit als Animation ändert, wobei die Farben ineinander übergehen.
Abbildung 3: Hintergrundfarbe von Composables 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. Wenn Sie jedoch eine Farbe im Zeitverlauf animieren, kann dies zu mehr Neukompositionen als nötig führen.

Informationen zum unendlichen Animieren der Hintergrundfarbe finden Sie unter Animationsabschnitt wiederholen.

Größe eines Composables animieren

Die grüne Komponente animiert ihre Größenänderung flüssig.
Abbildung 4: Composable, das reibungslos zwischen einer kleinen und einer größeren Größe animiert wird

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

Wenn Sie beispielsweise ein Feld mit Text haben, der von einer auf mehrere Zeilen erweitert werden kann, können Sie Modifier.animateContentSize() verwenden, um einen flüssigeren Übergang zu erzielen:

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 sich die Größe ändern soll.

Position von Composable animieren

Grüne Komponente, die sanft nach unten rechts animiert wird
Abbildung 5. Composable-Verschiebung um einen Offset

Wenn Sie die Position eines Composables 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
        }
)

aus.

Wenn Sie dafür sorgen möchten, dass Composables bei der Animation von Position oder Größe nicht über oder unter anderen Composables gezeichnet werden, verwenden Sie Modifier.layout{ }. Dieser Modifikator überträgt Größen- und Positionsänderungen an das übergeordnete Element, was sich dann auf andere untergeordnete Elemente auswirkt.

Wenn Sie beispielsweise ein Box innerhalb eines Column verschieben und die anderen untergeordneten Elemente sich mit dem Box bewegen müssen, fügen Sie die Offsetinformationen mit Modifier.layout{ } wie folgt ein:

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 Kästen,wobei der zweite Kasten seine X- und Y-Position animiert und der dritte Kasten darauf reagiert, indem er sich ebenfalls um den Y-Betrag bewegt.
Abbildung 6: Animieren mit Modifier.layout{ }

Padding eines Composables animieren

Grüne zusammensetzbare Funktion, die bei einem Klick kleiner und größer wird, wobei das Padding animiert wird
Abbildung 7. Composable mit animiertem Padding

Wenn Sie das Padding eines Composables animieren möchten, 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
        }
)

Erhebung eines Composables animieren

Abbildung 8. Composable-Erhebung wird bei Klick animiert

Wenn Sie die Erhebung eines Composables animieren möchten, verwenden Sie animateDpAsState in Kombination mit Modifier.graphicsLayer{ }. Für einmalige Änderungen der Erhebung verwenden Sie 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 Eigenschaft „elevation“ für jeden Status auf unterschiedliche Werte festlegen.

Textskalierung, ‑verschiebung oder ‑rotation animieren

Ein Text-Composable, das „Hallo“ anzeigt und zwischen einer kleinen und einer größeren Größe animiert wird.
Abbildung 9. Text, der flüssig zwischen zwei Größen animiert wird

Wenn Sie die Skalierung, Translation oder Drehung von Text animieren, legen Sie den Parameter textMotion für TextStyle auf TextMotion.Animated fest. So werden Übergänge zwischen Textanimationen flüssiger. 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)
    )
}

Schriftfarbe animieren

Die Worte „Hello Compose“ ändern ihre Farbe zwischen Grün und Blau.
Abbildung 10. Beispiel für die Animation der Textfarbe

Wenn Sie die Textfarbe animieren möchten, verwenden Sie den Lambda-Ausdruck color für die zusammensetzbare 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

Grüner Bildschirm mit dem Text „Wird geladen“, blauer Bildschirm mit dem Text „Geladen“ und weißer Bildschirm mit dem Text „Fehler“. Die verschiedenen Composables werden mit einer einfachen Animation durchlaufen.
Abbildung 11. Mit AnimatedContent Änderungen zwischen verschiedenen Composables animieren (verlangsamt)

Verwenden Sie AnimatedContent, um zwischen verschiedenen Composables zu animieren. Wenn Sie nur ein standardmäßiges Ein- und Ausblenden zwischen Composables wünschen, verwenden Sie 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 Ausblendungen angezeigt werden. Weitere Informationen finden Sie in der Dokumentation zu AnimatedContent oder in diesem Blogpost zu AnimatedContent.

Animationen während der Navigation zu verschiedenen Zielen

Zwei Composables, eines in Grün mit der Aufschrift „Landing“ und eines in Blau mit der Aufschrift „Detail“. Das Detail-Composable wird über das Landing-Composable geschoben.
Abbildung 12. Animationen zwischen kombinierbaren Funktionen mit „navigation-compose“ erstellen

Wenn Sie Übergänge zwischen kombinierbaren Funktionen animieren möchten, wenn Sie das Artefakt navigation-compose verwenden, geben Sie enterTransition und exitTransition für eine kombinierbare Funktion an. Sie können auch die Standardanimation festlegen, die für alle Ziele auf der obersten Ebene NavHost verwendet werden soll:

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 Ein- und Ausblendungen, die unterschiedliche Effekte auf die ein- und ausgehenden Inhalte anwenden. Weitere Informationen finden Sie in der Dokumentation.

Animation wiederholen

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

Verwenden Sie rememberInfiniteTransition mit einem infiniteRepeatable animationSpec, um die Animation fortlaufend zu wiederholen. Ändern Sie RepeatModes, um anzugeben, wie die Bewegung erfolgen soll.

Verwenden Sie repeatable, um eine bestimmte Anzahl von Wiederholungen festzulegen.

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 eines Composables starten

LaunchedEffect wird ausgeführt, wenn eine zusammensetzbare Funktion in die Komposition aufgenommen wird. Damit wird beim Starten eines Composables eine Animation gestartet. Sie können damit den Animationsstatus ändern. Animatable mit der Methode animateTo verwenden, um die Animation beim Start 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 zwischen den Kreisen animiert werden, wobei die Animationen nacheinander erfolgen.
Abbildung 14. Diagramm, das zeigt, wie eine sequenzielle Animation nach und nach abläuft.

Verwenden Sie die Animatable-Coroutine-APIs, um sequenzielle oder gleichzeitige Animationen auszuführen. Wenn Sie animateTo für das Animatable nacheinander aufrufen, wartet jede Animation, bis die vorherigen Animationen abgeschlossen sind, bevor sie fortfährt . Das liegt daran, dass es sich um eine suspend-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))
}

Gleichzeitige Animationen erstellen

Drei Kreise mit grünen Pfeilen, die jeweils zu einem Kreis animiert werden und gleichzeitig animiert werden.
Abbildung 15. Diagramm, das zeigt, wie gleichzeitige Animationen gleichzeitig ablaufen.

Verwenden Sie die Coroutine-APIs (Animatable#animateTo() oder animate) oder die Transition API, um gleichzeitige Animationen zu erzielen. Wenn Sie mehrere Startfunktionen in einem Coroutinenkontext verwenden, werden die Animationen gleichzeitig gestartet:

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 mit demselben Status viele verschiedene Attributanimationen gleichzeitig steuern. Im folgenden Beispiel werden zwei Attribute animiert, 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. Das liegt daran, dass eine Animation aus sich schnell bewegenden oder ändernden Pixeln auf dem Bildschirm besteht, die Frame für Frame die Illusion von Bewegung erzeugen.

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

Damit Ihre App während der Animation so wenig wie möglich ausführt, sollten Sie nach Möglichkeit die Lambda-Version von Modifier verwenden. Dadurch wird die Neukomposition übersprungen und die Animation außerhalb der Kompositionsphase ausgeführt. Andernfalls verwenden Sie Modifier.graphicsLayer{ }, da dieser Modifikator immer in der Zeichenphase ausgeführt wird. Weitere Informationen finden Sie in der Leistungsdokumentation im Abschnitt Lesevorgänge verzögern.

Timing der Animation ändern

Standardmäßig werden in Compose für die meisten Animationen Federanimationen verwendet. Feder- oder physikbasierte Animationen wirken natürlicher. Sie können auch unterbrochen werden, da sie die aktuelle Geschwindigkeit des Objekts berücksichtigen und nicht eine feste Zeit. Wenn Sie die Standardeinstellung überschreiben möchten, können Sie mit allen zuvor gezeigten Animations-APIs ein animationSpec festlegen, um die Ausführung einer Animation anzupassen, z. B. um sie über einen bestimmten Zeitraum auszuführen oder sie dynamischer zu gestalten.

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

  • spring: Physikbasierte Animation, die Standardeinstellung für alle Animationen. Sie können die Steifigkeit oder das Dämpfungsverhältnis ändern, um eine andere Animation zu erzielen.
  • tween (kurz für between): Dauerbasierte Animation, die mit einer Easing-Funktion zwischen zwei Werten animiert.
  • keyframes: Spezifikation zum Festlegen 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 ausgeführt wird.
  • snap: Der Wert wird sofort ohne Animation auf den Endwert gesetzt.
Zwei Animationen, die zeigen, wie sich die Verwendung eines benutzerdefinierten Spring-Spezifikationssatzes von der Verwendung keines Spezifikationssatzes unterscheidet.
Abbildung 16. Kein Spezifikationssatz im Vergleich zum benutzerdefinierten Spezifikationssatz

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

Zusätzliche Ressourcen

Weitere Beispiele für interessante Animationen in Compose finden Sie hier: