Krótki przewodnik po animacjach w sekcji Tworzenie

Narzędzie Compose ma wiele wbudowanych mechanizmów animacji i wybór jednego z nich może być przytłaczający. Poniżej znajdziesz listę typowych zastosowań animacji. Więcej szczegółowych informacji na temat pełnego zestawu różnych dostępnych opcji interfejsu API znajdziesz w pełnej dokumentacji tworzenia animacji.

Animuj wspólne właściwości elementów kompozycyjnych

Compose zapewnia wygodne interfejsy API, które pozwalają rozwiązać wiele typowych przypadków użycia animacji. W tej sekcji pokazujemy, jak animować typowe właściwości funkcji kompozycyjnej.

Animacja pojawianie się / znikanie

Zielony element kompozycyjny, który wyświetla się i ukrywa
Rysunek 1. Animowanie wyglądu i znikania elementu w kolumnie

Użyj AnimatedVisibility, aby ukryć lub wyświetlić funkcję kompozycyjną. Dzieci w domenie AnimatedVisibility mogą używać Modifier.animateEnterExit() do tworzenia własnych przejść do wejścia lub wyjścia.

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
    // ...
}

Parametry wejścia i wyjścia AnimatedVisibility pozwalają skonfigurować zachowanie funkcji kompozycyjnej, gdy jest wyświetlana i znika. Więcej informacji znajdziesz w pełnej dokumentacji.

Innym sposobem animowania widoczności funkcji kompozycyjnej jest animowanie wersji alfa w czasie za pomocą funkcji animateFloatAsState:

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

Jednak zmiana alfa wiąże się z zastrzeżeniem, że funkcja kompozycyjna pozostaje w kompozycji i nadal zajmuje przestrzeń, w której jest umieszczona. Mogło to spowodować, że czytniki ekranu i inne mechanizmy ułatwień dostępu nadal będą uwzględniać element na ekranie. Z drugiej strony AnimatedVisibility usuwa w końcu element z kompozycji.

Animowanie wersji alfa funkcji kompozycyjnej
Rysunek 2. Animowanie wersji alfa funkcji kompozycyjnej

Animuj kolor tła

Funkcja kompozycyjna, której kolor tła zmienia się z czasem jako animacja, w której kolory przechodzą na siebie.
Rysunek 3. Animowanie koloru tła w trybie kompozycyjnym

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

Ta opcja jest skuteczniejsza niż Modifier.background(). W przypadku jednorazowego ustawienia kolorów możesz użyć atrybutu Modifier.background(), ale jeśli animujesz kolor w miarę upływu czasu, może to spowodować więcej zmian kompozycji, niż jest to konieczne.

O nieskończonej animowaniu koloru tła znajdziesz w sekcji o powtarzaniu sekcji animacji.

Animuj rozmiar elementu kompozycyjnego

Rozmiar zielonego elementu kompozycyjnego zmienia się płynnie.
Rysunek 4. Aplikacja kompozycyjna, która płynnie przełącza się między małym a większym rozmiarem

Funkcja tworzenia umożliwia animowanie rozmiaru elementów kompozycyjnych na kilka różnych sposobów. Używaj animateContentSize() do tworzenia animacji między zmianami rozmiaru, które można tworzyć.

Jeśli na przykład masz pole zawierające tekst, który można rozwinąć do wielu wierszy, użyj funkcji Modifier.animateContentSize(), by zapewnić płynniejsze przejście:

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
        }

) {
}

Możesz też użyć właściwości AnimatedContent z SizeTransform, aby opisać, jak powinny być wprowadzane zmiany rozmiaru.

Animuj położenie funkcji kompozycyjnej

Funkcja kompozycyjna w kolorze zielonym płynnie animowana w dół i w prawo
Rysunek 5. Funkcja kompozycyjna przesuwająca się o przesunięcie

Aby animować położenie funkcji kompozycyjnej, użyj właściwości Modifier.offset{ } w połączeniu z funkcją 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
        }
)

Jeśli chcesz, aby elementy kompozycyjne nie były rysowane na innych elementach kompozycyjnych ani pod nimi podczas animowania położenia lub rozmiaru, użyj właściwości Modifier.layout{ }. Ten modyfikator propaguje zmiany rozmiaru i pozycji do elementu nadrzędnego, co z kolei wpływa na inne elementy podrzędne.

Jeśli np. przenosisz element Box w obrębie elementu Column, a inne elementy podrzędne muszą się przemieszczać razem z elementem Box, uwzględnij informacje o przesunięciu za pomocą parametru Modifier.layout{ } w ten sposób:

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 pola, w których druga ramka animuje swoje położenie X i Y, a trzecia odpowiada, poruszając się również o Y.
Rysunek 6. Animowanie przy użyciu Modifier.layout{ }

Animowane dopełnienie elementu kompozycyjnego

Zielona funkcja kompozycyjna staje się coraz mniejsza z każdym kliknięciem, z animowanym dopełnieniem
Rysunek 7. Kompozycyjne z animowanym dopełnieniem

Aby animować dopełnienie funkcji kompozycyjnej, użyj właściwości animateDpAsState w połączeniu z parametrem 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
        }
)

Animuj wysokość elementu kompozycyjnego

Rysunek 8. Animacja wysokości elementu kompozycyjnego po kliknięciu

Aby animować wysokość elementu kompozycyjnego, użyj właściwości animateDpAsState w połączeniu z funkcją Modifier.graphicsLayer{ }. W przypadku jednorazowej zmiany wysokości użyj parametru Modifier.shadow(). Jeśli animujesz cień, bardziej skutecznym rozwiązaniem jest modyfikator Modifier.graphicsLayer{ }.

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

Możesz też użyć funkcji kompozycyjnej Card i ustawić właściwość wysokości względnej na różne wartości dla każdego stanu.

Animuj skalę, przesunięcie i obrót tekstu

Tekst do kompozycji mówi
Rysunek 9. Płynne animowanie tekstu między dwoma rozmiarami

Podczas animowania skali, przesunięcia lub obrotu tekstu ustaw parametr textMotion na TextStyle na TextMotion.Animated. Zapewnia to płynniejsze przejścia między animacjami tekstowymi. Użyj narzędzia Modifier.graphicsLayer{ }, aby przetłumaczyć, obrócić lub przeskalować tekst.

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

Animuj kolor tekstu

Słowa
Rysunek 10. Przykład animowania koloru tekstu

Aby animować kolor tekstu, użyj funkcji lambda color w komponencie kompozycyjnym 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
    },
    // ...
)

Przełączanie się między różnymi typami treści

Zielony ekran mówi
Rysunek 11. Używanie AnimatedContent do animowania zmian między różnymi elementami kompozycyjnymi (spowalnianie)

Używaj funkcji AnimatedContent, aby przełączać się między różnymi elementami kompozycyjnymi. Jeśli chcesz, aby elementy kompozycyjne były animowane, użyj funkcji 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 można dostosować, tak aby wyświetlać wiele różnych rodzajów przejść, Więcej informacji znajdziesz w dokumentacji na stronie AnimatedContent oraz w tym poście na blogu AnimatedContent.

Animuj podczas nawigacji do różnych miejsc

2 kompozycje, jeden zielony z napisem „Lądowanie”, a drugi niebieski z nazwą „Szczegóły” animowany przez przesuwanie kompozycyjnego detalu po stronie docelowej.
Rysunek 12. Przełączanie się między elementami kompozycyjnymi przy użyciu funkcji nawigacji i tworzenia wiadomości

Aby animować przejścia między elementami kompozycyjnymi, gdy używany jest artefakt nawigacja-kompozycje, w funkcji kompozycyjnej określ enterTransition i exitTransition. Możesz też ustawić animację domyślną do używania we wszystkich miejscach docelowych na najwyższym poziomie NavHost:

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(
            // ...
        )
    }
}

Jest wiele różnych rodzajów przejść wejścia i wyjścia, które mają odmienne efekty w stosunku do treści przychodzących i wychodzących. Więcej informacji znajdziesz w dokumentacji.

Powtarzanie animacji

Zielone tło, które w nieskończoność przekształca się w niebieskie tło, które wyświetla się w wybranym kolorze.
Rysunek 13. Nieskończone animowanie koloru tła między dwiema wartościami

Użyj rememberInfiniteTransition z infiniteRepeatable animationSpec, by powtarzać animację w sposób ciągły. Zmień RepeatModes, aby określić, w jaki sposób ma się poruszać wstecz i z powrotem.

Użyj funkcji finiteRepeatable, aby powtórzyć określoną liczbę razy.

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
}

Włącz animację przy uruchomieniu funkcji kompozycyjnej

Narzędzie LaunchedEffect jest uruchamiane, gdy do kompozycji pojawi się funkcja kompozycyjna. Przy uruchamianiu funkcji kompozycyjnej uruchamia animację, którą można wykorzystać do zmiany stanu animacji. Użycie Animatable z metodą animateTo do uruchomienia animacji po uruchomieniu:

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

Twórz sekwencyjne animacje

Cztery okręgi z animowanymi zielonymi strzałkami między nimi, animowanymi po kolei.
Rysunek 14. Schemat pokazujący po kolei, jak rozwija się animacja sekwencyjna.

Interfejsy API współprogramu Animatable umożliwiają tworzenie sekwencyjnych lub równoczesnych animacji. Wywoływanie animateTo w Animatable jeden po drugim sprawia, że każda animacja czeka na zakończenie poprzednich animacji przed kontynuacją . Dzieje się tak, ponieważ jest to funkcja zawieszenia.

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

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

Tworzenie równoczesnych animacji

Trzy animowane okręgi z zielonymi strzałkami, które są połączone w tym samym czasie.
Rysunek 15. Diagram pokazujący postęp równoczesnych animacji.

Aby wyświetlać równoczesne animacje, użyj interfejsów API współrzędnych (Animatable#animateTo() lub animate) albo interfejsu API Transition. Jeśli używasz wielu funkcji uruchamiania w kontekście współpracy, uruchamiają one animacje w tym samym czasie:

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

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

Za pomocą interfejsu API updateTransition możesz używać tego samego stanu do uruchamiania wielu różnych animacji właściwości jednocześnie. W poniższym przykładzie animowane są 2 właściwości kontrolowane przez zmianę stanu: rect i 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
    }
}

Optymalizacja wydajności animacji

Animacje w Compose mogą powodować problemy z wydajnością. Wynika to z natury animacji: szybkiego przesuwania lub zmiany pikseli na ekranie, klatka po klatce w celu utworzenia iluzji ruchu.

Weź pod uwagę różne fazy tworzenia wiadomości: kompozycję, układ i rysowanie. Jeśli animacja zmieni się w fazie układu, wszystkie elementy kompozycyjne, których dotyczy problem, muszą zostać przekazane i przerysowane. Jeśli animacja ma miejsce w fazie rysowania, domyślnie jest ona wydajniejsza niż w przypadku uruchamiania w fazie układu, bo miałoby to mniej nakładu pracy.

Aby mieć pewność, że aplikacja będzie robić jak najmniej animacji podczas tworzenia animacji, w miarę możliwości wybierz wersję lambda funkcji Modifier. Spowoduje to pominięcie zmiany kompozycji i wykonanie animacji poza fazą kompozycji. W przeciwnym razie użyj metody Modifier.graphicsLayer{ }, ponieważ modyfikator zawsze działa w fazie rysowania. Więcej informacji na ten temat znajdziesz w sekcji dotyczącej odwracania odczytów w dokumentacji wydajności.

Zmiana czasu animacji

Domyślnie w tworzeniu większości animacji używane są animacje wiosenne. Sprężyny, czyli animacje oparte na fizyce, wyglądają bardziej naturalnie. Są również przerywane, ponieważ uwzględniają bieżącą prędkość obiektu, a nie stały czas. Jeśli chcesz zastąpić ustawienia domyślne, we wszystkich pokazanych powyżej interfejsach API animacji możesz ustawić animationSpec, aby dostosować sposób działania animacji, niezależnie od tego, czy ma ona być wykonywana przez określony czas, czy ma być bardziej płynna.

Oto podsumowanie różnych opcji animationSpec:

  • spring: animacja oparta na fizyce, domyślna dla wszystkich animacji. Możesz zmienić sztywność lub współczynnik tłumienia, aby uzyskać inny wygląd i styl animacji.
  • tween (skrót od Between): animacja zależna od czasu trwania, która wyświetla się między 2 wartościami przy użyciu funkcji Easing.
  • keyframes: specyfikacja określania wartości w konkretnych kluczowych momentach animacji.
  • repeatable: specyfikacja oparta na czasie trwania, która działa określoną liczbę razy, określona jako RepeatMode.
  • infiniteRepeatable: specyfikacja oparta na czasie trwania, która działa bezterminowo.
  • snap: automatycznie przyciąga reklamę do wartości końcowej bez żadnej animacji.
Wpisz tutaj swój tekst alternatywny
Rysunek 16. Brak ustawionej specyfikacji w porównaniu z zestawem specyfikacji niestandardowej sprężyny

Więcej informacji na temat animationSpecs znajdziesz w pełnej dokumentacji.

Dodatkowe materiały

Więcej przykładów zabawnych animacji w funkcji tworzenia wiadomości znajdziesz na tych stronach: