Krótki przewodnik po animacjach w sekcji Tworzenie

Funkcja tworzenia wiadomości ma wiele wbudowanych mechanizmów animacji i może być przytłaczająca. musisz wybrać. Poniżej znajdziesz listę typowych zastosowań animacji. Dla: bardziej szczegółowe informacje na temat pełnego zestawu różnych dostępnych opcji interfejsu API. przeczytaj pełną dokumentację tworzenia animacji.

Animowanie wspólnych właściwości komponentów

Compose zapewnia wygodne interfejsy API, które pozwalają rozwiązać wiele typowych problemów w przypadku animacji. W tej sekcji pokazujemy, jak animować typowe właściwości elementu kompozycyjnego.

animacja pojawiania się i znikania,

Zielony element składany, który wyświetla się i ukrywa
Rysunek 1. Animacja pojawiania się i znikania elementu w kolumnie

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

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 parametru AnimatedVisibility umożliwiają skonfigurowanie funkcja kompozycyjna zachowuje się, gdy pojawia się i znika. Przeczytaj w całości dokumentacji.

Inną opcją animowania widoczności funkcji kompozycyjnej jest alfa na przestrzeni czasu za pomocą 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 wartości alfa wiąże się jednak z tym, że kompozybilny element pozostanie w kompozycji i nadal będzie zajmować wyznaczoną mu przestrzeń. Może to spowodować, że czytniki ekranu i inne mechanizmy ułatwień dostępu nadal będą traktować element jako element na ekranie. Z drugiej strony AnimatedVisibility w końcu usuwa elementu z kompozycji.

Animowanie wartości alfa komponentu
Rysunek 2. Animowanie kanału alfa składanego elementu

Animowanie koloru 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 komponenta

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

Ta opcja jest bardziej wydajna niż Modifier.background(). Dla jednego ustawienia kolorów dozwolone jest użycie koloru Modifier.background(), ale gdy animowanie koloru w czasie, może spowodować większą liczbę zmian kompozycji niż niezbędną.

Informacje o animowaniu koloru tła w nieskończoności znajdziesz w sekcji powtarzanie animacji.

Animuj rozmiar elementu kompozycyjnego

Rozmiar zielonego elementu kompozycyjnego zmienia się płynnie.
Rysunek 4. kompozycyjna płynna animacja 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() – animacje między zmianami rozmiaru kompozycyjnego.

Jeśli na przykład masz pole zawierające tekst, które można rozwinąć od 1 do Możesz użyć wielu linii Modifier.animateContentSize(), aby zwiększyć płynność 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ć atrybutu AnimatedContent z SizeTransform, aby opisać jak powinny wprowadzać się zmiany rozmiaru.

Animuj położenie funkcji kompozycyjnej

Funkcja kompozycyjna w kolorze zielonym płynnie animowana w dół i w prawo
Rysunek 5. Przenoszenie komponentu o przesunięcie

Aby animować położenie funkcji kompozycyjnej, użyj operatora Modifier.offset{ } w połączeniu z 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 elementy kompozycyjne nie były rysowane nad ani pod innymi elementów kompozycyjnych podczas animowania pozycji lub rozmiaru, użyj funkcji Modifier.layout{ }. Ten modyfikator rozszerza zmiany rozmiaru i położenia na element nadrzędny, który wpływa na inne elementy podrzędne.

Na przykład jeśli przenosisz element Box w elemencie Column i innych elementach podrzędnych które musisz przesunąć, gdy przesuwa się Box, uwzględnij informacje o przesunięciu Modifier.layout{ } w następujący 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. Animacja za pomocą Modifier.layout{ }

Animowanie wypełniania kompozytu

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

Rys. 8. Animacja wzniesienia w komponowalnym na kliknięcie

Aby animować wysokość elementu kompozycyjnego, użyj funkcji animateDpAsState w połączeniu z Modifier.graphicsLayer{ } W przypadku jednorazowych zmian wysokości użyj opcji Modifier.shadow(). Jeśli animujesz cień, za pomocą funkcji Modyfikator Modifier.graphicsLayer{ } jest skuteczniejszą opcją.

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 nad poziomem morza na różne wartości dla każdego stanu.

Animowanie skali, przesunięcia lub obrotu tekstu

Tekst do kompozycji mówi
Rysunek 9. Tekst płynnie zmieniający rozmiar

Podczas animowania skali, przesunięcia lub obrotu tekstu ustaw textMotion od TextStyle do TextMotion.Animated. Dzięki temu przejścia między animacjami tekstu będą płynniejsze. Użyj Modifier.graphicsLayer{ }, aby obrócić lub zmienić rozmiar tekstu albo go przetłumaczyć.

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

Kolor tekstu w animacji

Słowa
Rysunek 10. Przykład animowanego 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

Tekst na zielonym ekranie
Rysunek 11. Używanie komponentu AnimatedContent do animowania zmian między różnymi komponentami (zwolnione)

Użyj AnimatedContent, aby przełączać się między różnymi elementami kompozycyjnymi, jeśli: aby standardowe rozjaśnianie elementów kompozycyjnych, 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ć, tak aby wyświetlać wiele różnych rodzajów wiadomości przejściach typu wyjściowego. Więcej informacji znajdziesz w dokumentacji dotyczącej AnimatedContent lub w tym poście na blogu na temat AnimatedContent.

animacje podczas nawigacji do różnych miejsc docelowych.

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.

Aby animować przejścia między składanymi, gdy używasz artefaktu navigation-compose, określ enterTransitionexitTransition w składanym. Możesz też ustawić animację domyślną używany w przypadku wszystkich miejsc 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 rodzajów przejść, które można stosować do treści wchodzących i wychodzących. Więcej informacji znajdziesz w dokumentacji.

Powtarzanie animacji

Zielone tło, które przechodzi w niebieskie, w nieskończoność przez animację między tymi dwoma kolorami.
Rysunek 13. Kolor tła animowany między 2 wartościami, bez końca

Aby animacja była powtarzana w nieskończoność, użyj rememberInfiniteTransitioninfiniteRepeatable animationSpec. Zmień RepeatModes na i określić, jak ma się to odbywać.

Użyj 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
}

Rozpoczęcie animacji podczas uruchamiania komponentu

LaunchedEffect jest wykonywane, gdy element kompozycyjny wchodzi do kompozycji. Zaczyna się możesz utworzyć animację przy uruchomieniu funkcji kompozycyjnej, zmiany stanu. Użycie Animatable z metodą animateTo do uruchamiania animacja 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 animowanymi zielonymi strzałkami między nimi, animowanymi po kolei.
Rysunek 14. Diagram pokazujący, jak sekwencyjna animacja jest odtwarzana jeden po drugim.

Używaj wewnętrznych interfejsów API Animatable do wykonywania sekwencyjnych lub równoczesnych ani animacji. Wywołuję animateTo (Animatable) jeden po drugim Kolejne animacje będą czekać na zakończenie poprzednich animacji . 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 animacji równoczesnych

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.

użyj wewnętrznych interfejsów API (Animatable#animateTo() lub animate); interfejs API Transition do tworzenia równoczesnych animacji. Jeśli w kontekście coroutine używasz wielu funkcji launch, animacje są uruchamiane jednocześnie:

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 za pomocą tego samego stanu sterować jednocześnie wieloma animacjami w różnych usługach. Animowany przykład poniżej 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 edytorze mogą powodować problemy z wydajnością. Wynika to z natury animacji: szybkie przesuwanie lub zmienianie pikseli na ekranie. klatka po klatce, aby stworzyć iluzję ruchu.

Zastanów się nad różnymi fazami tworzenia kompozycji: kompozycji, układu i rysowania. Jeśli animacja zmienia fazę układu, wymaga ponownego rozmieszczenia i narysowania wszystkich dotkniętych kompozytowych komponentów. Jeśli animacja występuje w fazie rysowania, domyślnie będzie działać wydajniej niż w fazie układu, ponieważ będzie miała mniej pracy do wykonania.

Aby zapewnić, że podczas animacji aplikacja będzie wykonywać jak najmniej działań, w miarę możliwości wybieraj wersję lambda funkcji Modifier. Spowoduje to pominięcie ponownego tworzenia kompozycji i wykonanie animacji poza fazą tworzenia kompozycji. W przeciwnym razie użyj parametru Modifier.graphicsLayer{ }, ponieważ ten modyfikator zawsze działa w fazie rysowania. Więcej informacji na ten temat znajdziesz w sekcji o odwracaniu odczytów w artykule zapoznaj się z dokumentacją wyników.

Zmienianie czasu animacji

Domyślnie kompozytor używa animacji elastycznej w przypadku większości animacji. Sprężyny lub animacje oparte na fizyce wydają się bardziej naturalne. Mogą być też przerywane, ponieważ uwzględniają bieżącą prędkość obiektu zamiast stałego czasu. Jeśli chcesz zastąpić ustawienia domyślne, użyj wszystkich interfejsów API animacji pokazanych powyżej możesz ustawić animationSpec, aby dostosować sposób działania animacji, czy wolisz, aby wykonywały się przez określony czas, czy były bardziej dynamiczne.

Oto podsumowanie różnych opcji animationSpec:

  • spring: animacja oparta na fizyce, domyślna dla wszystkich animacji. Ty można zmienić sztywność lub współczynnik tłumienia, aby uzyskać inną animację wygląd i styl.
  • tween (skrót od Between): animacja zależna od czasu trwania, animacja między 2 wartościami za pomocą funkcji Easing.
  • keyframes: specyfikacja określająca wartości w określonych kluczowych punktach animacji.
  • repeatable: specyfikacja oparta na czasie, która jest odtwarzana określoną liczbę razy (określoną przez RepeatMode).
  • infiniteRepeatable: specyfikacja oparta na czasie, która działa bez końca.
  • snap: wartość końcowa jest ustawiana natychmiast, bez animacji.
Tutaj wpisz tekst alternatywny
Rysunek 16. Brak ustawionej specyfikacji w porównaniu ze specyfikacją niestandardowych sprężyn

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

Dodatkowe materiały

Więcej przykładów zabawnych animacji w Compose: