Kurzanleitung zu Animationen in Compose

Compose verfügt über viele integrierte Animationsmechanismen, und es kann schwierig sein, zu entscheiden, welcher ausgewählt werden soll. Nachfolgend finden Sie eine Liste mit gängigen Anwendungsfällen für Animationen. Ausführliche Informationen zu den verschiedenen verfügbaren API-Optionen finden Sie in der Dokumentation zur Erstellung von Animationen.

Gängige zusammensetzbare Eigenschaften animieren

Compose bietet praktische APIs, mit denen Sie viele gängige Anwendungsfälle für Animationen bewältigen können. In diesem Abschnitt wird gezeigt, wie Sie allgemeine Eigenschaften einer zusammensetzbaren Funktion animieren.

Animieren eines Erscheinens / Verschwindens

Grüne zusammensetzbare Funktion, die sich ein- und ausblenden lässt
Abbildung 1. Darstellung und Verschwinden eines Elements in einer Spalte animieren

Verwenden Sie AnimatedVisibility, um eine zusammensetzbare Funktion ein- oder auszublenden. Untergeordnete Elemente in AnimatedVisibility können Modifier.animateEnterExit() für ihren eigenen Übergang zum Ein- oder Aussteigen 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 verhalten, wenn sie ein- und ausgeblendet wird. Weitere Informationen finden Sie in der vollständigen Dokumentation.

Eine weitere Möglichkeit, die Sichtbarkeit einer zusammensetzbaren Funktion zu animieren, besteht darin, den Alpha 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)
) {
}

Bei einer Änderung des Alpha-Werts wird jedoch vorausgesetzt, dass die zusammensetzbare Funktion in der Zusammensetzung verbleibt und den dafür vorgesehenen Platz einnimmt. Dies könnte 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 Zusammensetzung.

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

Hintergrundfarbe animieren

Zusammensetzbar, wobei sich die Hintergrundfarbe als Animation im Laufe der Zeit ändert, bei der die Farben übergangen werden
Abbildung 3: Hintergrundfarbe von zusammensetzbaren Funktionen 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 Farbeinstellung in einer Aufnahme akzeptabel. Wenn Sie eine Farbe über einen längeren Zeitraum animieren, kann dies jedoch zu mehr Neuzusammensetzungen als nötig führen.

Informationen zum unendlichen Animieren der Hintergrundfarbe finden Sie im Abschnitt Animation wiederholen.

Größe einer zusammensetzbaren Funktion animieren

Grüne zusammensetzbare Funktion, deren Größe animiert wird, ändert sich reibungslos.
Abbildung 4: Zusammensetzbare Animation zwischen einer kleinen und einer größeren Größe

Mit „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.

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 reibungsloseren Ü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
        }

) {
}

Du kannst auch AnimatedContent mit einem SizeTransform verwenden, um zu beschreiben, wie Größenänderungen vorgenommen werden sollen.

Position der zusammensetzbaren Funktion animieren

Grüne, zusammensetzbare Funktion mit sanfter Animation nach unten und rechts
Abbildung 5. Zusammensetzbares Verschieben durch einen Offset

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 sicherstellen möchten, dass zusammensetzbare Funktionen beim Animieren von Position oder Größe nicht über oder unter anderen zusammensetzbaren Funktionen gezeichnet werden, verwenden Sie Modifier.layout{ }. Dieser Modifikator leitet Größen- und Positionsänderungen an das übergeordnete Element weiter, was sich dann auf andere untergeordnete Elemente auswirkt.

Wenn Sie beispielsweise ein Box innerhalb einer Column verschieben und die anderen untergeordneten Elemente beim Verschieben von Box verschoben werden müssen, fügen Sie die Offset-Informationen so mit Modifier.layout{ } 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 Boxen,wobei die zweite Box ihre X- und Y-Position animiert. Die dritte Box reagiert, indem sie sich ebenfalls um den Y-Wert 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. Kann mit animierten Padding-Elementen kombiniert werden

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 Modifier.shadow() für einmalige Höhenänderungen. 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 unterschiedliche Werte pro Bundesstaat festlegen.

Textskalierung, -übersetzung oder -drehung animieren

Zusammensetzbarer Text mit Text
Abbildung 9. Text wird nahtlos zwischen zwei Größen animiert

Wenn Sie die Skalierung, Übersetzung oder Drehung von Text animieren, legen Sie den Parameter textMotion für TextStyle auf TextMotion.Animated fest. Dies sorgt für reibungslosere Übergänge zwischen Textanimationen. Verwenden Sie Modifier.graphicsLayer{ } zum Übersetzen, Drehen oder Skalieren des Textes.

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 die Animation der 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)

Verwenden Sie AnimatedContent für Animationen zwischen zusammensetzbaren Funktionen. Wenn nur eine Standardüberblendung zwischen den zusammensetzbaren Funktionen erfolgen soll, 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 Übergängen beim Ein- und Ausblenden angezeigt werden. Weitere Informationen finden Sie in der Dokumentation zu AnimatedContent oder in diesem Blogpost zu AnimatedContent.

Animiere zu verschiedenen Zielen

Zwei zusammensetzbare Elemente, eine mit der Aufschrift „Landing“ und eine blaue „Detail“
Abbildung 12. Mit „Navigations-Compose“ zwischen zusammensetzbaren Funktionen animieren

Wenn Sie mit dem Artefakt navigation-compose Übergänge zwischen zusammensetzbaren Funktionen animieren möchten, geben Sie enterTransition und exitTransition für eine zusammensetzbare Funktion an. Sie können auch festlegen, dass die Standardanimation 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 Ausstiegsübergängen, die unterschiedliche Auswirkungen auf die ein- und ausgehenden Inhalte haben. Weitere Informationen finden Sie in der Dokumentation.

Animation wiederholen

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

Verwende rememberInfiniteTransition mit einem infiniteRepeatable animationSpec, um die Animation fortlaufend zu wiederholen. Ändern Sie RepeatModes, um anzugeben, wie er hin und her verlaufen 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 eine zusammensetzbare Funktion in die Zusammensetzung aufgenommen wird. Beim Start einer zusammensetzbaren Funktion wird eine Animation gestartet. Damit können Sie 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 grünen Pfeilen, die nacheinander animiert werden
Abbildung 14. Diagramm, das den Ablauf einer sequenziellen Animation zeigt.

Verwenden Sie die Animatable-Coroutine-APIs, um sequenzielle oder gleichzeitige Animationen auszuführen. Wenn animateTo für die Animatable nacheinander aufgerufen wird, wartet jede Animation auf den Abschluss der vorherigen Animationen, bevor sie fortfahren . 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))
}

Gleichzeitige 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, um gleichzeitige Animationen zu erstellen. Wenn Sie mehrere Startfunktionen in einem Koroutinekontext 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 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 „Compose“ können zu Leistungsproblemen führen. Das liegt an der Art einer Animation: das schnelle Bewegen oder Ändern von Pixeln auf dem Bildschirm, Frame für Frame, um den Eindruck von Bewegung zu vermitteln.

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

Damit deine App während der Animation so wenig wie möglich macht, wähle nach Möglichkeit die Lambda-Version einer Modifier aus. Dadurch wird die Neuzusammensetzung übersprungen und die Animation außerhalb der Erstellungsphase ausgeführt. Andernfalls wird Modifier.graphicsLayer{ } verwendet, da dieser Modifikator immer in der Zeichenphase ausgeführt wird. Weitere Informationen dazu finden Sie in der Leistungsdokumentation im Abschnitt Lesevorgänge aussetzen.

Timing der Animation ändern

Beim Schreiben werden standardmäßig Frühlingsanimationen verwendet. Federn oder physikbasierte Animationen fühlen sich natürlicher an. Sie sind auch unterbrechbar, da sie die aktuelle Geschwindigkeit des Objekts anstelle einer festen Zeit berücksichtigen. Wenn Sie die Standardeinstellung überschreiben möchten, haben alle oben gezeigten Animations-APIs die Möglichkeit, eine animationSpec festzulegen, um die Ausführung einer Animation anzupassen, unabhängig davon, ob sie über eine bestimmte Dauer oder über einen längeren Zeitraum ausgeführt werden soll.

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

  • spring: Physikbasierte Animationen, die Standardeinstellung für alle Animationen. Sie können das Steifheits- oder dämpfungsverhältnis ändern, um ein anderes Design der Animation zu erreichen.
  • tween (kurz für zwischen): Dauerbasierte Animation, animiert zwischen zwei Werten mit einer Easing-Funktion.
  • keyframes: Spezifikation für die Angabe von Werten an bestimmten wichtigen Punkten in einer Animation.
  • repeatable: Dauerbasierte Spezifikation, die eine bestimmte Anzahl von Ausführungen hat, angegeben durch RepeatMode.
  • infiniteRepeatable: Dauerbasierte Spezifikation, die unbegrenzt gültig ist.
  • 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.

Zusätzliche Ressourcen

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