Modyfikatory animacji i elementy kompozycyjne

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

Wbudowane komponenty animowane

Animowanie pojawiania się i znikania za pomocą AnimatedVisibility

Kompozycja w kolorze zielonym, która wyświetla się i ukrywa
Rysunek 1. Animowanie pojawiania się i znikania elementu w kolumnie

Kompozycja 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ę, gdy jest rozjaśniana i powiększana, a znika, gdy jest przyciemniana i pomniejszana. Przejście można dostosować, określając 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 pozwalają dostosować jego działanie. Więcej informacji znajdziesz w sekcji Referencje.

Przykłady: EnterTransition i ExitTransition

EnterTransition ExitTransition
fadeIn
animacja pojawiania się
fadeOut
animacja zanikania,
slideIn
animacja wsuwania,
slideOut
animacja wysuwania
slideInHorizontally
animacja wsuwania w poziomie,
slideOutHorizontally
animacja wysuwania w poziomie,
slideInVertically
animacja wsuwania pionowego,
slideOutVertically
animacja wysuwania w pionie
scaleIn
animacja powiększania,
scaleOut
animacja skalowania w poziomie,
expandIn
rozwiń w animacji,
shrinkOut
animacja zmniejszania,
expandHorizontally
animacja rozwijania w poziomie,
shrinkHorizontally
animacja zmniejszania w poziomie,
expandVertically
animacja rozwijania w pionie,
shrinkVertically
animacja zmniejszania w pionie,

AnimatedVisibility oferuje też wariant, który przyjmuje MutableTransitionState. Dzięki temu możesz uruchomić animację od razu po dodaniu elementu AnimatedVisibility do drzewa kompozycji. Przydaje się też 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 wejść i wyjść dla dzieci

Treści w 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 każdego z tych elementów podrzędnych jest połączeniem animacji określonych w funkcji kompozycyjnej 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 chcieć, aby atrybut AnimatedVisibility nie stosował żadnych animacji, dzięki czemu każde dziecko będzie mogło mieć własne, odrębne animacje za pomocą atrybutu animateEnterExit. Aby to osiągnąć, określ wartości EnterTransition.NoneExitTransition.None w funkcji AnimatedVisibility.

Dodawanie animacji niestandardowej

Jeśli chcesz dodać niestandardowe efekty animacji wykraczające poza wbudowane animacje wejścia i wyjścia, uzyskaj dostęp do bazowej instancji Transition za pomocą właściwości transition w lambdzie treści dla AnimatedVisibility. Wszystkie stany animacji dodane do instancji przejścia będą działać jednocześnie z animacjami wejścia i wyjścia elementu AnimatedVisibility. AnimatedVisibility czeka, aż wszystkie animacje w Transition zostaną zakończone, zanim usunie jego zawartość. W przypadku animacji wyjścia utworzonych niezależnie od Transition (np. za pomocą animate*AsState) AnimatedVisibility nie będzie w stanie ich uwzględnić, dlatego może usunąć komponenty treści przed ich 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 sekcji updateTransition.

Animowanie na podstawie stanu docelowego za pomocą AnimatedContent

Kompozycja AnimatedContent animuje swoje treści, gdy zmieniają się one 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 musisz używać parametru lambda i odzwierciedlać go w treści. Interfejs API używa tej wartości jako klucza do identyfikowania aktualnie wyświetlanych treści.

Domyślnie początkowa treść zanika, a potem pojawia się treść docelowa (takie zachowanie nazywa się przejściem przez zanikanie). Możesz dostosować to zachowanie animacji, określając obiekt ContentTransform w parametrze transitionSpec. Możesz utworzyć ContentTransform, łącząc EnterTransition z ExitTransition za pomocą funkcji wrostkowej with. Możesz zastosować SizeTransform do 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 powinny wyglądać treści docelowe, aExitTransition określa, jak powinny znikać treści początkowe. Oprócz wszystkich funkcji EnterTransitionExitTransition dostępnych w AnimatedVisibility, AnimatedContent oferuje slideIntoContainerslideOutOfContainer. Są to wygodne alternatywy dla slideInHorizontally/VerticallyslideOutHorizontally/Vertically, które obliczają odległość slajdu na podstawie rozmiarów treści początkowych i docelowych w AnimatedContent.

SizeTransform określa, jak rozmiar powinien się zmieniać między treściami początkowymi a docelowymi. Podczas tworzenia animacji masz dostęp zarówno do rozmiaru początkowego, jak i docelowego. SizeTransform określa też, czy podczas animacji treść ma 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ść podczas wchodzenia i wychodzenia z podrzędnych elementów

Podobnie jak w przypadku AnimatedVisibility, modyfikator animateEnterExit jest dostępny w lambdzie treści AnimatedContent. Użyj tego, aby zastosować EnterAnimationExitAnimation do każdego z bezpośrednich lub pośrednich elementów podrzędnych oddzielnie.

Dodawanie animacji niestandardowej

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

Animowanie przejścia między 2 układami za pomocą Crossfade

Crossfade animuje przejście między dwoma układami za pomocą animacji przenikania. Przełączając wartość przekazywaną do parametru current, możesz 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

Animowanie zmian rozmiaru komponentu za pomocą animateContentSize

Zielony komponent, który płynnie zmienia rozmiar.
Rysunek 2. Kompozycja z płynną animacją przejścia 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ć zmianę kolejności elementów na liście lub w siatce Lazy, zapoznaj się z dokumentacją animacji elementów układu Lazy.