Kurzanleitung zu Animationen in Compose

Compose bietet viele integrierte Animationsmechanismen und es kann schwierig sein, den richtigen auszuwählen. 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 Eigenschaften eines Composeables animieren können.

Ein-/Ausblenden animieren

Grüner zusammensetzbarer Inhalt wird angezeigt und ausgeblendet
Abbildung 1: Das Erscheinen und Verschwinden eines Elements in einer Spalte animieren

Mit AnimatedVisibility kannst du ein Composable ein- oder ausblenden. 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 Parametern „enter“ und „exit“ von AnimatedVisibility können Sie konfigurieren, wie sich ein Composeable beim Ein- und Ausblenden verhält. 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)
) {
}

Wenn Sie den Alphawert ändern, bleibt das Element jedoch in der Komposition und nimmt weiterhin den ihm zugewiesenen Platz ein. Dies kann dazu führen, dass Screenreader und andere Bedienungshilfen das Element auf dem Bildschirm weiterhin berücksichtigen. Mit AnimatedVisibility wird das Element dagegen irgendwann aus der Komposition entfernt.

Alpha eines Composeables animieren
Abbildung 2: Alpha eines Composeables 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 zum Endlos-Animieren der Hintergrundfarbe finden Sie im Abschnitt Animation wiederholen.

Größe eines Composeables animieren

Grüner zusammensetzbarer Bereich, der seine Größe reibungslos animiert.
Abbildung 4: Kompositionen, die flüssig zwischen einer kleinen und einer größeren Größe animiert werden

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 Feld mit Text haben, der sich von einer auf mehrere Zeilen ausdehnen kann, können Sie mit Modifier.animateContentSize() einen flüssigeren Übergang 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 Größenänderungen erfolgen sollen.

Position des Composeables animieren

Grüner zusammensetzbarer Bereich, der sich reibungslos nach unten und rechts animiert
Abbildung 5. Komponierbares Verschieben um einen Versatz

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 dafür sorgen möchten, dass Elemente beim Animieren von Position oder Größe nicht über oder unter anderen Elementen gezeichnet werden, verwenden Sie Modifier.layout{ }. Dieser Modifikator überträgt Größe und Position an das übergeordnete Element, was sich dann auf andere untergeordnete Elemente auswirkt.

Wenn Sie beispielsweise eine Box innerhalb einer Column verschieben und die anderen untergeordneten Elemente sich ebenfalls verschieben sollen, fügen Sie die Informationen zum Offset wie unten dargestellt in Modifier.layout{ } ein:Box

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 Feld seine X‑,Y‑Position animiert und das dritte Feld sich ebenfalls um den Wert Y bewegt.
Abbildung 6: Animationen mit Modifier.layout{ }
erstellen

Ränder eines Composeables animieren

Grüner zusammensetzbarer Bereich, der beim Klicken kleiner und größer wird, mit animiertem Abstand
Abbildung 7. Mit animiertem Abstand komponierbar

Wenn Sie den Abstand eines Composeables 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
        }
)

Höhe eines Composeables animieren

Abbildung 8. Elevation des Composeables wird beim Klicken animiert

Verwenden Sie animateDpAsState in Kombination mit Modifier.graphicsLayer{ }, um die Höhe eines Composeables zu animieren. Verwenden Sie für einmalige Höhenänderungen Modifier.shadow(). Wenn Sie den Schatten animieren, ist die Verwendung des Modifier.graphicsLayer{ }-Modifikators die leistungsfähigere 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 das Card-Kompositelement verwenden und die Höhe für jeden Status auf einen anderen Wert festlegen.

Text skalieren, verschieben oder drehen

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

Wenn Sie die Skalierung, Verschiebung oder Drehung von Text animieren möchten, legen Sie den Parameter textMotion auf TextStyle auf TextMotion.Animated fest. 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

Wenn Sie die Textfarbe animieren möchten, verwenden Sie das color-Lambda für das BasicText-Komposit:

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-Text
Abbildung 11. Mit „AnimatedContent“ Änderungen zwischen verschiedenen Composeables 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. Zwischen Composables mit „navigation-compose“ animieren

Wenn Sie Übergänge zwischen Komponenten animieren möchten, wenn Sie das Artefakt navigation-compose verwenden, geben Sie enterTransition und exitTransition für eine Komponente an. Sie können die Standardanimation auch so festlegen, dass sie für alle Ziele auf der obersten Ebene NavHost verwendet wird:

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 Ausblendungsübergängen, die unterschiedliche Effekte auf die ein- und ausgehenden Inhalte anwenden. Weitere Informationen finden Sie in der Dokumentation.

Animation wiederholen

Ein grüner Hintergrund, der durch eine Animation zwischen den beiden Farben in einen blauen Hintergrund übergeht.
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 Starten eines Composeables starten

LaunchedEffect wird ausgeführt, wenn ein Composeable in die Komposition eintritt. Damit wird beim Starten eines Composeables eine Animation gestartet. Sie können damit den Änderungsstatus der Animation steuern. 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 . Das liegt daran, dass es sich um eine Pausierfunktion 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 gleichzeitig animiert werden.
Abbildung 15. Diagramm, das den Fortschritt mehrerer gleichzeitiger Animationen zeigt

Verwenden Sie die Coroutinen-APIs (Animatable#animateTo() oder animate) oder die Transition API, um gleichzeitige Animationen zu erzielen. Wenn Sie mehrere Startfunktionen in einem coroutine-Kontext 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 denselben Status verwenden, um viele verschiedene Unterkunftsanimationen gleichzeitig zu steuern. Im folgenden Beispiel werden zwei Eigenschaften animiert, die durch einen Zustandswechsel 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 an der Natur einer Animation: Pixel werden auf dem Bildschirm schnell und Frame für Frame bewegt oder geändert, 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 Composeables 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. Dadurch wird die Neukomposition übersprungen und die Animation wird außerhalb der Kompositionphase ausgeführt. Andernfalls verwenden Sie Modifier.graphicsLayer{ }, da dieser Modifikator immer in der Zeichenphase ausgeführt wird. Weitere Informationen finden Sie im Abschnitt Aussetzung von Lesevorgängen der Leistungsdokumentation.

Timing der Animation ändern

In Compose werden standardmäßig Feder-Animationen für die meisten Animationen 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 Animation, 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 (Kurzform für between): Dauerbasierte Animation, bei der der Übergang zwischen zwei Werten mit einer Easing-Funktion animiert wird.
  • 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: Der Endwert wird sofort und ohne Animation erreicht.
Geben Sie hier den Alt-Text ein.
Abbildung 16 Keine Spezifikationsgruppe im Vergleich zu benutzerdefinierter Spring-Spezifikationsgruppe

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

Weitere Informationen

Weitere Beispiele für lustige Animationen in Compose: