Krótki przewodnik po animacjach w sekcji Tworzenie

Funkcja tworzenia filmów ma wiele wbudowanych mechanizmów animacji i trudno jest zastanawiać się, który z nich wybrać. Poniżej znajduje się lista typowych przypadków użycia animacji. Szczegółowe informacje o pełnym zestawie różnych dostępnych opcji interfejsu API znajdziesz w pełnej dokumentacji funkcji tworzenia animacji.

Animacja wspólnych właściwości elementów kompozycyjnych

Interfejs Compose udostępnia wygodne interfejsy API, które pozwalają rozwiązywać typowe zastosowania animacji. W tej sekcji pokażemy, jak animować typowe właściwości funkcji kompozycyjnej.

Animacja pojawiania się / znikania

Zielona funkcja kompozycyjna pokazująca się i ukrywająca siebie
Rysunek 1. Animowanie wyglądu i zniknięcia elementu w kolumnie

Użyj AnimatedVisibility, aby ukryć lub wyświetlić kompozycję. Dzieci w domenie AnimatedVisibility mogą używać Modifier.animateEnterExit() jako własnego 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 właściwości AnimatedVisibility pozwalają skonfigurować zachowanie elementu kompozycyjnego, gdy się pojawia i znika. Aby dowiedzieć się więcej, przeczytaj pełną dokumentację.

Inną opcją animowania widoczności funkcji kompozycyjnej jest animowanie wersji alfa w czasie za pomocą narzędzia 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)
) {
}

Zmiana wersji alfa wiąże się jednak z zastrzeżeniem, że element kompozycyjny pozostały w kompozycji i nadal zajmuje miejsce, w którym jest ułożony. Może to spowodować, że czytniki ekranu i inne mechanizmy ułatwień dostępu nadal będą uwzględniać element widoczny na ekranie. Z drugiej strony AnimatedVisibility usuwa element z kompozycji.

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

Animacja koloru tła

Funkcja kompozycyjna, której kolor tła zmienia się z biegiem czasu jako animacja, w której kolory przenikają się do siebie.
Rysunek 3. Animowanie koloru tła elementu kompozycyjnego

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ż użycie Modifier.background(). Modifier.background() jest akceptowalny w przypadku ustawiania kolorów w ramach jednego zdjęcia, ale animowanie koloru w dłuższym okresie może spowodować powstanie większej liczby zmian kompozycji niż to konieczne.

Jeśli chcesz animować kolor tła w nieskończoność, przeczytaj sekcję o powtarzaniu sekcji animacji.

Animacja rozmiaru funkcji kompozycyjnej

Zielona kompozycyjna animowana animacja jej rozmiaru płynnie się zmienia.
Rysunek 4. Możliwość płynnego przechodzenia między dużymi i większymi animacjami

Tworzenie umożliwia animowanie rozmiaru elementu kompozycyjnego na kilka różnych sposobów. Użyj animateContentSize() do animacji między zmianami rozmiaru kompozycyjnego.

Jeśli na przykład masz pole zawierające tekst, który można rozwinąć z jednego do kilku wierszy, możesz użyć elementu Modifier.animateContentSize(), aby uzyskać 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ć AnimatedContent z SizeTransform, aby opisać sposób wprowadzania zmiany rozmiaru.

Animacja pozycji elementu kompozycyjnego

Zielony komponent kompozycyjny płynnie animowany w dół i w prawo
Rysunek 5. Element kompozycyjny – przesunięcie o przesunięcie

Aby animować pozycję funkcji kompozycyjnej, użyj funkcji 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
        }
)

Aby mieć pewność, że podczas animowania położenia lub rozmiaru kompozycje nie będą przeciągane nad lub pod innymi elementami kompozycyjnymi, użyj funkcji Modifier.layout{ }. Ten modyfikator rozpowszechnia zmiany rozmiaru i pozycji w elemencie nadrzędnym, co wpływa na inne elementy podrzędne.

Jeśli na przykład przenosisz element Box w obrębie elementu Column, a inne elementy podrzędne muszą się przesunąć, gdy Box się porusza, podaj informacje o przesunięciu za pomocą właściwości 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 pudełka, przy czym drugie pole animuje swoje położenie X i Y, a trzecie reaguje, przesuwając się o wartość Y.
Rysunek 6. Animowanie za pomocą aplikacji Modifier.layout{ }

Animacja dopełnienia funkcji kompozycyjnej

Zielony kompilacja powiększa się i zmniejsza po kliknięciu, a dopełnienie jest animowane
Rysunek 7. Funkcja kompozycyjna z animacją dopełnienia

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

Animacja wysokości elementu kompozycyjnego

Rysunek 8. Animacja wysokości w komponencie po kliknięciu

Aby animować wysokość elementu kompozycyjnego, użyj funkcji animateDpAsState w połączeniu z funkcją Modifier.graphicsLayer{ }. Aby jednorazowo zmienić wysokość, użyj funkcji Modifier.shadow(). Jeśli animujesz cień, skuteczniejszą opcją jest użycie modyfikatora 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ść height na różne wartości dla poszczególnych stanów.

Animacja skali, tłumaczenia lub obrotu tekstu

Tekst do tworzenia
Rysunek 9. Płynne animowanie tekstu między dwoma rozmiarami

Podczas animowania skali, przesunięcia lub obrotu tekstu ustaw parametr textMotion w elemencie TextStyle na TextMotion.Animated. Zapewnia to płynne przejścia między animacjami tekstowymi. Użyj narzędzia Modifier.graphicsLayer{ }, aby przetłumaczyć, obrócić lub skalować 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 lambda color w komponencie 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

Zielone tło z napisem
Rysunek 11. Animowanie zmian w różnych funkcjach kompozycyjnych (spowolnienie) za pomocą animacji WebView

AnimatedContent służy do animowania różnych elementów kompozycyjnych. Jeśli chcesz po prostu użyć standardowego zanikania między elementami kompozycyjnymi, użyj 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ć, aby pokazywał wiele różnych rodzajów wejścia i wyjścia. Więcej informacji znajdziesz w dokumentacji na stronie AnimatedContent lub w tym poście na blogu AnimatedContent.

Animuj podczas podróży

Dwa elementy kompozycyjne, jeden zielony z tekstem „Lądowanie”, a drugi niebieski z tekstem „Szczegóły”, animowanym przez przesunięcie elementu kompozycyjnego na stronie docelowej.
Rysunek 12. Animowanie elementów kompozycyjnych za pomocą funkcji Navigation-compose

Aby animować przejścia między elementami kompozycyjnymi przy użyciu artefaktu navigation-compose, określ w nim enterTransition i exitTransition. Możesz też ustawić domyślną animację, która będzie używana 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(
            // ...
        )
    }
}

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

Powtarzanie animacji

Zielone tło, które zmienia się w niebieskie tło w nieskończoność przez animowanie się między dwoma kolorami.
Rysunek 13. Animacja koloru tła w nieskończoność między 2 wartościami

Aby stale powtarzać animację, użyj funkcji rememberInfiniteTransition i infiniteRepeatable animationSpec. Zmień atrybut RepeatModes, aby określić sposób przechodzenia tam i z powrotem.

Aby powtarzać określoną liczbę razy, użyj funkcji finiteRepeatable.

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
}

Rozpoczynanie animacji przy uruchamianiu funkcji kompozycyjnej

LaunchedEffect jest uruchamiany, gdy do kompozycji wchodzi funkcja kompozycyjna. Uruchamia animację po uruchomieniu funkcji kompozycyjnej. Możesz ją wykorzystać do określenia zmiany stanu animacji. Użycie polecenia Animatable z metodą animateTo do uruchamiania animacji przy uruchomieniu:

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

Tworzenie animacji sekwencyjnych

Cztery okręgi z zielonymi strzałkami w górę animowane jedna po drugiej.
Rysunek 14. Schemat pokazujący postęp sekwencyjnej animacji, jeden po drugim.

Użycie interfejsów API współbieżnych Animatable do wyświetlania animacji sekwencyjnych lub równoczesnych. Wywołanie animateTo Animatable po kolei sprawia, że każda animacja czeka na zakończenie poprzednich animacji, zanim będzie można kontynuować . Dzieje się tak, ponieważ jest to funkcja zawieszania.

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

3 kółka z zielonymi strzałkami, które się animują i jednocześnie animują wszystkie elementy.
Rysunek 15. Schemat pokazujący postęp równoczesnych animacji.

Aby uzyskać równoczesne animacje, użyj współbieżnych interfejsów API (Animatable#animateTo() lub animate) albo interfejsu API Transition. Jeśli używasz wielu funkcji uruchamiania w kontekście współużytkowania, są one uruchamiane razem z animacjami:

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

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

Możesz użyć interfejsu API updateTransition, aby używać tego samego stanu do jednoczesnego uruchamiania wielu różnych animacji właściwości. W przykładzie poniżej animujemy 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
    }
}

Optymalizowanie wydajności animacji

Animacje w funkcji tworzenia mogą powodować problemy z wydajnością. Wynika to z natury animacji: szybkiego ruchu lub zmiany pikseli na ekranie, klatka po klatce, tworząc iluzję ruchu.

Poznaj różne fazy tworzenia wiadomości: kompozycję, układ i rysowanie. Jeśli animacja zmieni fazę układu, wszystkie elementy kompozycyjne, których dotyczy problem, muszą przekazać układ i przerysować. Jeśli animacja jest w fazie rysowania, domyślnie jest skuteczniejsza niż w przypadku wyświetlania w wersji z układem, ponieważ jej praca wymaga mniej pracy.

Aby aplikacja działała w jak najmniejszym stopniu podczas animowania, w miarę możliwości wybierz wersję lambda interfejsu Modifier. Spowoduje to pominięcie zmiany kompozycji i wykona animację poza fazą kompozycji. W przeciwnym razie użyj funkcji Modifier.graphicsLayer{ }, ponieważ ten modyfikator zawsze działa na etapie rysowania. Więcej informacji na ten temat znajdziesz w sekcji na temat wstrzymywania odczytów w dokumentacji wydajności.

Zmiana czasu animacji

Funkcja tworzenia domyślnie używa w przypadku większości animacji animacji sprężyny. Sprężyny czy animacje oparte na fizyce są bardziej naturalne. Są również przerywane, ponieważ uwzględniają prędkość obiektu, a nie stały czas. Jeśli chcesz zastąpić ustawienia domyślne, wszystkie interfejsy API animacji przedstawione powyżej mogą skonfigurować animationSpec w celu dostosowania sposobu uruchamiania animacji, niezależnie od tego, czy ma ona być wykonywana przez określony czas, czy też ma być wykonywana w większym stopniu.

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 oparta na czasie trwania, która wyświetla animację między 2 wartościami za pomocą funkcji Easing.
  • keyframes: specyfikacja określania wartości w określonych kluczowych momentach animacji.
  • repeatable: specyfikacja oparta na czasie trwania, która jest uruchamiana określoną liczbę razy. Parametr ten określa parametr RepeatMode.
  • infiniteRepeatable: specyfikacja zależna od czasu trwania działająca w nieskończoność.
  • snap: powoduje natychmiastowe przyciąganie do wartości końcowej bez animacji.
Tutaj wpisz tekst alternatywny
Rysunek 16. Brak ustawionych specyfikacji w porównaniu do niestandardowych specyfikacji Spring

Aby dowiedzieć się więcej o parametrze animationSpecs, przeczytaj pełną dokumentację.

Dodatkowe materiały

Więcej przykładów zabawnych animacji w funkcji Utwórz znajdziesz w tych artykułach: