Kurzanleitung zu Animationen in Compose

Compose verfügt über viele integrierte Animationsmechanismen und es kann schwierig sein, zu wissen, welchen man auswählen soll. Nachfolgend finden Sie eine Liste häufiger Anwendungsfälle für Animationen. Ausführlichere Informationen zu den verschiedenen verfügbaren API-Optionen finden Sie in der vollständigen Dokumentation zum Erstellen einer Animation.

Häufige zusammensetzbare Eigenschaften animieren

Compose bietet praktische APIs, mit denen Sie viele gängige Animationsanwendungsfälle lösen können. In diesem Abschnitt wird gezeigt, wie Sie allgemeine Eigenschaften einer zusammensetzbaren Funktion animieren.

Animation zum Ein-/Verschwinden

Grüne zusammensetzbare Funktion, die sich selbst ein- und ausgeblendet wird
Abbildung 1: Darstellung und Verschwinden eines Elements in einer Spalte animieren

Mit AnimatedVisibility können Sie eine zusammensetzbare Funktion ein- oder ausblenden. Untergeordnete Elemente innerhalb von AnimatedVisibility können Modifier.animateEnterExit() für ihren eigenen Eingang oder Ausgang 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 sich eine zusammensetzbare Funktion verhält, wenn sie ein- und ausgeblendet wird. Weitere Informationen finden Sie in der vollständigen Dokumentation.

Eine weitere Option zum Animieren der Sichtbarkeit einer zusammensetzbaren Funktion besteht darin, die Alphaversion 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)
) {
}

Beim Ändern der Alpha-Phase bleibt die zusammensetzbare Funktion jedoch in der Komposition bestehen und belegt weiterhin den Raum, in dem sie angelegt ist. Dies kann dazu führen, dass Screenreader und andere Bedienungshilfen das Element auf dem Bildschirm weiterhin berücksichtigen. Andererseits entfernt AnimatedVisibility das Element schließlich aus der Komposition.

Alpha einer zusammensetzbaren Funktion animieren
Abbildung 2: Alpha einer zusammensetzbaren Funktion animieren

Hintergrundfarbe animieren

Zusammensetzbar, bei dem sich die Hintergrundfarbe im Laufe der Zeit als Animation ändert, wobei die Farben ineinander übergehen
Abbildung 3: Hintergrundfarbe einer zusammensetzbaren Funktion 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 einzelne Farbeinstellung akzeptabel. Wenn Sie jedoch eine Farbe im Zeitverlauf animieren, kann dies zu mehr Neuzusammensetzungen als nötig führen.

Wie Sie die Hintergrundfarbe unbegrenzt animieren, erfahren Sie unter Animationsabschnitt wiederholen.

Größe einer zusammensetzbaren Funktion animieren

Grünes zusammensetzbares Asset, dessen Größe sich nahtlos ändert.
Abbildung 4: Reibungslose Animation zwischen einer kleinen und einer großen Bildgröße

Mit der Funktion „Compose“ können Sie die Größe von zusammensetzbaren Funktionen auf verschiedene Arten animieren. Verwenden Sie animateContentSize() für Animationen zwischen zusammensetzbaren Größenänderungen.

Bei einem Feld mit Text, der sich beispielsweise von einer Zeile auf mehrere Zeilen erweitern lässt, können Sie mit Modifier.animateContentSize() einen weicheren Ü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 stattfinden sollen.

Position der zusammensetzbaren Funktion animieren

Grünes Set-up, das stufenlos von unten und rechts nach unten animiert wird
Abbildung 5: Zusammensetzbare Funktion, die um einen Offset verschoben wird

Um die Position einer zusammensetzbaren Funktion zu animieren, 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 möchten, dass zusammensetzbare Funktionen bei der Animation von Position oder Größe nicht über oder unter anderen zusammensetzbaren Funktionen eingeblendet werden, verwenden Sie Modifier.layout{ }. Dadurch werden Änderungen an Größe und Position an das übergeordnete Element weitergegeben, was sich dann auf andere untergeordnete Elemente auswirkt.

Wenn Sie beispielsweise ein Box innerhalb einer Column verschieben und die anderen untergeordneten Elemente verschoben werden müssen, wenn das Box verschoben wird, fügen Sie die Versatzinformationen mit Modifier.layout{ } so hinzu:

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)
    )
}

2 Felder,wobei das zweite Feld die X- und Y-Position animiert und das dritte Feld reagiert, indem es sich ebenfalls um Y bewegt.
Abbildung 6: Animation mit Modifier.layout{ }

Abstand in einer zusammensetzbaren Funktion animieren

Grüne, zusammensetzbare Funktion, die durch Klicken kleiner und größer wird, wobei der Innenrand animiert wird
Abbildung 7: Zusammensetzbar mit animierten Abständen

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. Bei Klick animierter Höhenunterschied

Um die Höhe einer zusammensetzbaren Funktion zu animieren, verwenden Sie animateDpAsState in Kombination mit Modifier.graphicsLayer{ }. Für einmalige Höhenänderungen verwenden Sie Modifier.shadow(). Wenn Sie den Schatten animieren, ist der Modifier.graphicsLayer{ }-Modifikator 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 das Höhenattribut auf verschiedene Werte pro Status festlegen.

Textskalierung, Verschiebung oder Drehung animieren

Zusammensetzbarer Text mit Text
Abbildung 9. Reibungslose Animationen zwischen zwei Größen

Wenn Sie den Maßstab, die Übersetzung oder die Drehung von Text animieren möchten, setzen Sie den textMotion-Parameter für TextStyle auf TextMotion.Animated. So sind flüssigere Übergänge zwischen Textanimationen Mit Modifier.graphicsLayer{ } können Sie den Text übersetzen, drehen oder 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 animierte Textfarbe

Verwende zum Animieren der Textfarbe das Lambda 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

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

Verwenden Sie AnimatedContent, um zwischen verschiedenen zusammensetzbaren Funktionen zu animieren. Wenn Sie nur eine Standardüberblendung zwischen zusammensetzbaren Funktionen vornehmen möchten, 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 Ausgangsübergängen angezeigt werden. Weitere Informationen finden Sie in der Dokumentation zu AnimatedContent oder in diesem Blogpost zu AnimatedContent.

Während der Navigation zu verschiedenen Zielen Animationen erstellen

Zwei zusammensetzbare Funktionen, eine grüne mit der Aufschrift „Land“ und eine mit der Aufschrift „Detail“. Verschieben Sie die Details, indem Sie sie animieren, indem Sie sie über die zusammensetzbare Funktion „Landingpage“ verschieben.
Abbildung 12. Mit „navigation-compose“ Animationen zwischen zusammensetzbaren Funktionen erstellen

Wenn Sie bei Verwendung des Artefakts navigation-compose Übergänge zwischen zusammensetzbaren Funktionen animieren möchten, geben Sie für eine zusammensetzbare Funktion enterTransition und exitTransition an. Sie können auch die Standardanimation festlegen, die 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 Eingabe- und Exit-Übergängen, die unterschiedliche Auswirkungen auf die eingehenden und ausgehenden Inhalte haben. 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 unbegrenzt zwischen zwei Werten animiert

Wenn du die Animation kontinuierlich wiederholen möchtest, verwende rememberInfiniteTransition mit einem infiniteRepeatable-animationSpec. Ändern Sie RepeatModes, um festzulegen, wie vor- und zurückgegangen werden soll.

Mit finiteRepeatable wiederholen Sie den Vorgang eine bestimmte Anzahl von Malen.

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 eine zusammensetzbare Funktion in die Zusammensetzung aufgenommen wird. Beim Start einer zusammensetzbaren Funktion wird eine Animation gestartet. Sie können damit die Änderung des Animationsstatus steuern. Verwenden Sie Animatable mit der Methode animateTo, 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 jeweils animierten grünen Pfeilen, die jeweils nacheinander animiert werden.
Abbildung 14. Diagramm, das den Verlauf einer sequenziellen Animation darstellt.

Verwenden Sie die Animatable-Coroutine-APIs, um sequenzielle oder gleichzeitige Animationen auszuführen. Wenn animateTo im Animatable nacheinander aufgerufen wird, wartet jede Animation, bis die vorherigen Animationen beendet sind, bevor sie fortgesetzt wird . Das liegt daran, dass es sich um eine Anhalten-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))
}

Animationen gleichzeitig erstellen

Drei Kreise mit jeweils animierten grünen Pfeilen, die alle gleichzeitig animiert sind.
Abbildung 15. Diagramm, das den Fortschritt gleichzeitiger Animationen anzeigt.

Verwenden Sie die Coroutine-APIs (Animatable#animateTo() oder animate) oder die Transition API, um gleichzeitige Animationen zu erreichen. Wenn Sie mehrere Startfunktionen in einem Koroutinenkontext 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)
    }
}

Sie können die updateTransition API verwenden, um denselben Status zu verwenden und viele verschiedene Attributanimationen gleichzeitig auszuführen. Im folgenden Beispiel werden zwei Eigenschaften 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 der Funktion „Compose“ können zu Leistungsproblemen führen. Dies liegt an der Art einer Animation: schnelles Bewegen oder Ändern von Pixeln auf dem Bildschirm und Frame für Frame, um den Eindruck einer Bewegung zu erzeugen.

Sehen Sie sich die verschiedenen Phasen des Verfassens an: Komposition, Layout und Zeichnen. Wenn sich die Layoutphase durch die Animation ändert, müssen alle betroffenen zusammensetzbaren Funktionen neu gestaltet und gezeichnet werden. Wenn die Animation in der Zeichenphase stattfindet, ist sie standardmäßig leistungsfähiger als die Animation in der Layoutphase, da insgesamt weniger Arbeit erforderlich wäre.

Damit deine App bei der Animation so wenig wie möglich ausführt, wähle nach Möglichkeit die Lambda-Version einer Modifier aus. Dadurch wird die Neuzusammensetzung übersprungen und die Animation wird außerhalb der Zusammensetzungsphase ausgeführt. Verwenden Sie andernfalls Modifier.graphicsLayer{ }, da dieser Modifikator immer in der Zeichenphase ausgeführt wird. Weitere Informationen dazu finden Sie in der Leistungsdokumentation im Abschnitt Aussetzen von Lesevorgängen.

Timing der Animation ändern

Standardmäßig werden für die meisten Animationen Frühlingsanimationen verwendet. Federn, also physikbasierte Animationen, wirken natürlicher. Sie sind auch unterbrechbar, da sie die aktuelle Geschwindigkeit des Objekts anstelle einer festen Zeit berücksichtigen. Wenn Sie die Standardeinstellung überschreiben möchten, können Sie bei allen oben gezeigten Animations-APIs ein animationSpec festlegen, um die Ausführung einer Animation anzupassen. Dabei spielt es keine Rolle, ob die Animation über eine bestimmte Dauer oder mit mehr Schwingungen ausgeführt werden soll.

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

  • spring: Physikbasierte Animationen, Standard für alle Animationen Sie können die Steifheit oder das dampingRatio ändern, um ein anderes Erscheinungsbild der Animation zu erreichen.
  • tween (kurz für between): Dauerbasierte Animation, wird zwischen zwei Werten mit einer Easing-Funktion animiert.
  • keyframes: Spezifikation zur Angabe von Werten an bestimmten wichtigen Punkten in einer Animation.
  • repeatable: Dauerbasierte Spezifikation, die eine bestimmte Anzahl von Ausführungen ausführt, angegeben durch RepeatMode.
  • infiniteRepeatable: Dauerbasierte Spezifikation, die unbegrenzt ausgeführt wird.
  • snap: wird direkt und ohne Animation an den Endwert angedockt.
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 der Funktion „Compose“ finden Sie hier: