Modyfikatory animacji i elementy kompozycyjne

Składnik kompozytorski zawiera wbudowane komponenty i modyfikatory do obsługi typowych przypadków użycia animacji.

Wbudowane animowane komponenty

Dodawanie animacji do efektu pojawiania się i znikania za pomocą AnimatedVisibility

Zielony element składany wyświetlający się i ukrywający
Rysunek 1. Animacja pojawiania się i znikania elementu w kolumnie

Komponent 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ę, rozjaśniając i rozwijając, oraz znika w wyniku zanikania i pomniejszania. Przejście można dostosować, podając wartości 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 widać w przykładzie powyżej, możesz łączyć wiele obiektów EnterTransition lub ExitTransition za pomocą operatora +. Każdy z nich akceptuje opcjonalne parametry, które umożliwiają dostosowanie jego działania. Więcej informacji znajdziesz w dokumentacji.

Przykłady dotyczące EnterTransition i ExitTransition

Wprowadzenie ExitTransition
fadeIn
animacja wstępowania
fadeOut
animacja znikania
slideIn
animacja przesuwania
slideOut
animacja przesuwania
slideInHorizontally
animacja przesuwu w poziomie
slideOutHorizontally
animacja przesuwania w poziomie
slideInVertically
animacja przesuwania w dół
slideOutVertically
animacja przesuwania w dół
scaleIn
animacja odskalowania
scaleOut
animacja skalowania w dół
expandIn
animacja rozszerzania
shrinkOut
animacja zmniejszania
expandHorizontally
animacja rozszerzania w poziomie
shrinkHorizontally
animacja pomniejszania w poziomie
expandVertically
animacja rozszerzania w pionie
shrinkVertically
animacja zmniejszania w pionie

AnimatedVisibility oferuje też wariant, który przyjmuje parametr MutableTransitionState. Umożliwia to uruchomienie animacji, gdy tylko element AnimatedVisibility zostanie dodany do drzewa kompozycji. Jest to też przydatne 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"
        }
    )
}

Animacja wejścia i wyjścia dla dzieci

Treści w poziomie AnimatedVisibility (bezpośrednich lub pośrednich podrzędnych) mogą używać modyfikatora animateEnterExit, aby określić różne zachowanie animacji dla każdego z nich. Efekt wizualny dla każdego z tych elementów podrzędnych jest kombinacją animacji określonych w komponowalnym elemencie AnimatedVisibility oraz 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ć stosować żadnych animacji w przypadku AnimatedVisibility, aby dzieci mogły mieć własne animacje w funkcji animateEnterExit. Aby to zrobić, określ EnterTransition.None i ExitTransition.None w funkcji kompozycyjnej AnimatedVisibility.

Dodaj animację niestandardową

Jeśli chcesz dodać niestandardowe efekty animacji poza wbudowanymi animacjami wejścia i wyjścia, przejdź do instancji Transition za pomocą właściwości transition w parametrze lambda treści AnimatedVisibility. Wszystkie stany animacji dodane do instancji przejścia będą uruchamiane jednocześnie z animacjami wejścia i wyjścia obiektu AnimatedVisibility. AnimatedVisibility czeka, aż wszystkie animacje w elemencie Transition zostaną zakończone, zanim usunie swoją treść. W przypadku animacji wyjścia utworzonych niezależnie od parametru Transition (np. przy użyciu animate*AsState) usługa AnimatedVisibility nie będzie w stanie ich uwzględnić i dlatego może usunąć kompozycję przed zakończeniem.

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

Szczegółowe informacje o Transition znajdziesz w funkcji updateTransition.

Animuj na podstawie stanu docelowego za pomocą funkcji AnimatedContent

Komponent AnimatedContent animuje zawartość, która zmienia się w zależności od 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")
    }
}

Pamiętaj, że zawsze należy używać parametru lambda i odzwierciedlać go w treści. Interfejs API używa tej wartości jako klucza do identyfikowania treści, które są obecnie wyświetlane.

Domyślnie początkowa treść znika, a następnie pojawia się treść docelowa (to zachowanie nazywa się fade through). Możesz dostosować to zachowanie animacji, podając obiekt ContentTransform w parametrze transitionSpec. Możesz utworzyć ContentTransform, łącząc EnterTransition z ExitTransition za pomocą funkcji infiksu with. Możesz zastosować wzór SizeTransform do ContentTransform, dołączając go za pomocą funkcji infiksu 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 mają wyglądać treści docelowe, a ExitTransition określa, jak mają zniknąć początkowe treści. Oprócz wszystkich funkcji EnterTransitionExitTransition dostępnych w AnimatedVisibility, AnimatedContent oferuje funkcje slideIntoContainerslideOutOfContainer. Są to wygodne alternatywy dla funkcji slideInHorizontally/VerticallyslideOutHorizontally/Vertically, które obliczają odległość slajdu na podstawie rozmiarów początkowych treści i docelowych treści treści AnimatedContent.

SizeTransform określa, jak rozmiar powinien się animować między początkową a docelową zawartością. Podczas tworzenia animacji masz dostęp zarówno do rozmiaru początkowego, jak i docelowego. Parametr SizeTransform określa też, czy podczas animacji treść powinna zostać przycięta 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ść rozpoczęcia i zakończenia elementów podrzędnych

Podobnie jak w przypadku AnimatedVisibility, modyfikator animateEnterExit jest dostępny w treściach lambda w AnimatedContent. Dzięki temu możesz oddzielnie zastosować zasady EnterAnimation i ExitAnimation do każdego bezpośredniego lub pośredniego elementu podrzędnego.

Dodawanie animacji niestandardowej

Podobnie jak pole AnimatedVisibility, pole transition jest dostępne w lambda treści w AnimatedContent. Użyj tego, aby utworzyć niestandardowy efekt animacji, który będzie działał jednocześnie z AnimatedContent. Więcej informacji znajdziesz w artykule updateTransition.

Animacja przejścia między dwoma układami za pomocą Crossfade

Crossfade przełącza się między 2 układami za pomocą animacji przejścia. Za pomocą przełącznika wartości przekazywanej do parametru current można przełączać zawartość za pomocą animacji przejścia.

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

Animowanie zmian rozmiaru za pomocą animateContentSize

Zielony element składany z animacją zmiany rozmiaru.
Rysunek 2. Aplikacja kompozycyjna, która płynnie przełącza się między małym a większym 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ć przetasowanie elementów na liście lub w siatkowaniu opartym na technologii Lazy, zapoznaj się z dokumentacją dotyczącą animacji elementów w ramach układu Lazy.