Modyfikatory animacji i elementy kompozycyjne

Compose zawiera wbudowane funkcje kompozycyjne i modyfikatory do obsługi typowych przypadków użycia animacji.

Wbudowane elementy kompozycyjne z animacją

Compose udostępnia kilka funkcji kompozycyjnych, które animują pojawianie się i znikanie treści oraz zmiany układu.

Animowanie pojawiania się i znikania

Zielony element kompozycyjny, który pojawia się i znika
Rysunek 1. Animowanie pojawiania się i znikania elementu w kolumnie.

Funkcja kompozycyjna AnimatedVisibility animuje pojawianie się i znikanie treści.

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

Domyślnie treść pojawia się przez rozjaśnienie i rozwinięcie, a znika przez ściemnienie i zmniejszenie. Dostosuj to przejście, określając obiekty EnterTransition i ExitTransition.

var visible by remember { mutableStateOf(true) }
val density = LocalDensity.current
AnimatedVisibility(
    visible = visible,
    enter = slideInVertically {
        // Slide in from 40 dp from the top.
        with(density) { -40.dp.roundToPx() }
    } + expandVertically(
        // Expand from the top.
        expandFrom = Alignment.Top
    ) + fadeIn(
        // Fade in with the initial alpha of 0.3f.
        initialAlpha = 0.3f
    ),
    exit = slideOutVertically() + shrinkVertically() + fadeOut()
) {
    Text(
        "Hello",
        Modifier
            .fillMaxWidth()
            .height(200.dp)
    )
}

Jak pokazano w poprzednim przykładzie, możesz połączyć wiele obiektów EnterTransition lub ExitTransition za pomocą operatora +. Każdy z nich akceptuje opcjonalne parametry, które pozwalają dostosować jego działanie. Więcej informacji znajdziesz na stronach z materiałami referencyjnymi.

Przykłady przejść wejścia i wyjścia

EnterTransition ExitTransition
fadeIn
Element interfejsu stopniowo pojawia się w widoku.
fadeOut
Element interfejsu stopniowo znika z widoku.
slideIn
Element interfejsu przesuwa się na ekranie.
slideOut
Element interfejsu wysuwa się poza ekran.
slideInHorizontally
Element interfejsu przesuwa się poziomo do widocznego obszaru.
slideOutHorizontally
Element interfejsu przesuwa się poziomo poza widok.
slideInVertically
Element interfejsu przesuwa się pionowo do widocznego obszaru.
slideOutVertically
Element interfejsu przesuwa się pionowo poza widoczny obszar.
scaleIn
Element interfejsu powiększa się i pojawia się w widoku.
scaleOut
Element interfejsu zmniejsza się i znika z widoku.
expandIn
Element interfejsu rozwija się z punktu centralnego.
shrinkOut
Element interfejsu zmniejsza się i znika w centralnym punkcie.
expandHorizontally
Element interfejsu rozwija się poziomo i pojawia się w widoku.
shrinkHorizontally
Element interfejsu kurczy się w poziomie i znika z widoku.
expandVertically
Element interfejsu rozwija się pionowo do widocznego obszaru.
shrinkVertically
Element interfejsu zmniejsza się w pionie i znika z widoku.

AnimatedVisibility oferuje też wariant, który przyjmuje argument MutableTransitionState. Dzięki temu możesz uruchomić animację, gdy tylko element kompozycyjny AnimatedVisibility zostanie dodany do drzewa kompozycji. Jest też przydatny do obserwowania stanu animacji.

// Create a MutableTransitionState<Boolean> for the AnimatedVisibility.
val state = remember {
    MutableTransitionState(false).apply {
        // Start the animation immediately.
        targetState = true
    }
}
Column {
    AnimatedVisibility(visibleState = state) {
        Text(text = "Hello, world!")
    }

    // Use the MutableTransitionState to know the current animation state
    // of the AnimatedVisibility.
    Text(
        text = when {
            state.isIdle && state.currentState -> "Visible"
            !state.isIdle && state.currentState -> "Disappearing"
            state.isIdle && !state.currentState -> "Invisible"
            else -> "Appearing"
        }
    )
}

Animowanie pojawiania się i znikania elementów podrzędnych

Treści w ramach AnimatedVisibility (bezpośrednie lub pośrednie elementy podrzędne) mogą używać modyfikatora animateEnterExit, aby określić różne zachowania animacji dla każdego z nich. Efekt wizualny dla każdego z tych elementów podrzędnych jest połączeniem animacji określonych w funkcji kompozycyjnej AnimatedVisibility oraz własnych animacji wejścia i wyjścia elementu podrzędnego.

var visible by remember { mutableStateOf(true) }

AnimatedVisibility(
    visible = visible,
    enter = fadeIn(),
    exit = fadeOut()
) {
    // Fade in/out the background and the foreground.
    Box(
        Modifier
            .fillMaxSize()
            .background(Color.DarkGray)
    ) {
        Box(
            Modifier
                .align(Alignment.Center)
                .animateEnterExit(
                    // Slide in/out the inner box.
                    enter = slideInVertically(),
                    exit = slideOutVertically()
                )
                .sizeIn(minWidth = 256.dp, minHeight = 64.dp)
                .background(Color.Red)
        ) {
            // Content of the notification…
        }
    }
}

W niektórych przypadkach możesz nie chcieć, aby AnimatedVisibility stosował jakiekolwiek animacje, dzięki czemu każde dziecko może mieć własne, odrębne animacje według animateEnterExit. Aby to osiągnąć, określ EnterTransition.None i ExitTransition.None w funkcji kompozycyjnej AnimatedVisibility.

Dodaj animację niestandardową

Jeśli chcesz dodać niestandardowe efekty animacji oprócz wbudowanych animacji wejścia i wyjścia, uzyskaj dostęp do instancji Transition za pomocą właściwości transition w funkcji lambda treści dla AnimatedVisibility. Wszystkie stany animacji dodane do instancji Transition będą uruchamiane jednocześnie z animacjami wejścia i wyjścia elementu AnimatedVisibility. AnimatedVisibility czeka, aż wszystkie animacje w Transition zostaną zakończone, zanim usunie zawartość. W przypadku animacji wyjścia utworzonych niezależnie od Transition (np. przy użyciu animate*AsState) AnimatedVisibility nie będzie w stanie ich uwzględnić, więc może usunąć element kompozycyjny treści, zanim się zakończą.

var visible by remember { mutableStateOf(true) }

AnimatedVisibility(
    visible = visible,
    enter = fadeIn(),
    exit = fadeOut()
) { // this: AnimatedVisibilityScope
    // Use AnimatedVisibilityScope#transition to add a custom animation
    // to the AnimatedVisibility.
    val background by transition.animateColor(label = "color") { state ->
        if (state == EnterExitState.Visible) Color.Blue else Color.Gray
    }
    Box(
        modifier = Modifier
            .size(128.dp)
            .background(background)
    )
}

Więcej informacji o używaniu Transition do zarządzania animacjami znajdziesz w artykule Animowanie wielu właściwości jednocześnie za pomocą przejścia.

Animowanie na podstawie stanu docelowego

Funkcja kompozycyjna AnimatedContent animuje swoją zawartość, gdy zmienia się ona na podstawie stanu docelowego.

Row {
    var count by remember { mutableIntStateOf(0) }
    Button(onClick = { count++ }) {
        Text("Add")
    }
    AnimatedContent(
        targetState = count,
        label = "animated content"
    ) { targetCount ->
        // Make sure to use `targetCount`, not `count`.
        Text(text = "Count: $targetCount")
    }
}

Domyślnie początkowa treść zanika, a następnie pojawia się treść docelowa (to zachowanie jest nazywane zanikaniem). Możesz dostosować działanie animacji, określając obiekt ContentTransform w parametrze transitionSpec. Instancję ContentTransform możesz utworzyć, łącząc obiekt EnterTransition z obiektem ExitTransition za pomocą funkcji wrostkowej with. Możesz zastosować SizeTransform do obiektu ContentTransform, dołączając go za pomocą funkcji wrostkowej using.

AnimatedContent(
    targetState = count,
    transitionSpec = {
        // Compare the incoming number with the previous number.
        if (targetState > initialState) {
            // If the target number is larger, it slides up and fades in
            // while the initial (smaller) number slides up and fades out.
            slideInVertically { height -> height } + fadeIn() togetherWith
                slideOutVertically { height -> -height } + fadeOut()
        } else {
            // If the target number is smaller, it slides down and fades in
            // while the initial number slides down and fades out.
            slideInVertically { height -> -height } + fadeIn() togetherWith
                slideOutVertically { height -> height } + fadeOut()
        }.using(
            // Disable clipping since the faded slide-in/out should
            // be displayed out of bounds.
            SizeTransform(clip = false)
        )
    }, label = "animated content"
) { targetCount ->
    Text(text = "$targetCount")
}

EnterTransition określa, jak powinna pojawiać się treść docelowa, a ExitTransition – jak powinna znikać treść początkowa. Oprócz wszystkich funkcji EnterTransition i ExitTransition dostępnych w AnimatedVisibility, AnimatedContent oferuje slideIntoContainer i slideOutOfContainer. Są to wygodne alternatywy dla slideInHorizontally/Vertically i slideOutHorizontally/Vertically, które obliczają odległość przesunięcia na podstawie rozmiarów początkowej i docelowej zawartości elementu AnimatedContent.

SizeTransform określa, jak rozmiar powinien się zmieniać między zawartością początkową a docelową. Podczas tworzenia animacji masz dostęp zarówno do rozmiaru początkowego, jak i docelowego. SizeTransform określa też, czy podczas animacji zawartość powinna być przycinana do rozmiaru komponentu.

var expanded by remember { mutableStateOf(false) }
Surface(
    color = MaterialTheme.colorScheme.primary,
    onClick = { expanded = !expanded }
) {
    AnimatedContent(
        targetState = expanded,
        transitionSpec = {
            fadeIn(animationSpec = tween(150, 150)) togetherWith
                fadeOut(animationSpec = tween(150)) using
                SizeTransform { initialSize, targetSize ->
                    if (targetState) {
                        keyframes {
                            // Expand horizontally first.
                            IntSize(targetSize.width, initialSize.height) at 150
                            durationMillis = 300
                        }
                    } else {
                        keyframes {
                            // Shrink vertically first.
                            IntSize(initialSize.width, targetSize.height) at 150
                            durationMillis = 300
                        }
                    }
                }
        }, label = "size transform"
    ) { targetExpanded ->
        if (targetExpanded) {
            Expanded()
        } else {
            ContentIcon()
        }
    }
}

Animowanie przejść wejścia i wyjścia elementów podrzędnych

Podobnie jak AnimatedVisibility, modyfikator animateEnterExit jest dostępny w funkcji lambda treści AnimatedContent. Użyj tego, aby zastosować EnterAnimation i ExitAnimation do każdego z bezpośrednich lub pośrednich elementów podrzędnych osobno.

Dodaj animację niestandardową

Podobnie jak AnimatedVisibility, pole transition jest dostępne w funkcji lambda treści AnimatedContent. Użyj tego, aby utworzyć niestandardowy efekt animacji, który będzie działać jednocześnie z przejściem AnimatedContent. Szczegółowe informacje znajdziesz w sekcji updateTransition.

Animowanie między 2 układami

Crossfade animuje przejście między 2 układami za pomocą animacji przenikania. Przełączając wartość przekazywaną do parametru current, można przełączać treści za pomocą animacji przenikania.

var currentPage by remember { mutableStateOf("A") }
Crossfade(targetState = currentPage, label = "cross fade") { screen ->
    when (screen) {
        "A" -> Text("Page A")
        "B" -> Text("Page B")
    }
}

Wbudowane modyfikatory animacji

Compose udostępnia modyfikatory do animowania konkretnych zmian bezpośrednio w elementach kompozycyjnych.

Animowanie zmian rozmiaru funkcji kompozycyjnej

Zielony element kompozycyjny płynnie zmieniający rozmiar.
Rysunek 2. Element kompozycyjny płynnie animujący przejście między małym a dużym rozmiarem

Modyfikator animateContentSize animuje zmianę rozmiaru.

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
        }

) {
}

Animacje elementów listy

Jeśli chcesz animować zmianę kolejności elementów na liście lub w siatce Lazy, zapoznaj się z dokumentacją animacji elementów układu Lazy.