Modyfikatory animacji i elementy kompozycyjne

Składnik składania 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, który wyświetla się i ukrywa
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ści pojawiają się w ramach animacji otwierania i zamykania, a znikają w ramach animacji zamykania i zwijania. 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

EnterTransition ExitTransition
fadeIn
animacja wkracania
fadeOut
animacja znikania
slideIn
animacja przesuwania
slideOut
animacja przesuwania
slideInHorizontally
animacja przesuwania 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 zmniejszania w poziomie
expandVertically
animacja rozszerzania w pionie
shrinkVertically
animacja zmniejszania w pionie

AnimatedVisibility oferuje też wariant, który przyjmuje parametr MutableTransitionState. Dzięki temu możesz uruchomić animację, gdy tylko element AnimatedVisibility zostanie dodany do drzewa kompozycji. Jest ona też przydatna 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 rozpoczęcia i zakończenia wyświetlania elementów dla dzieci

Treści w poziomie AnimatedVisibility (bezpośrednie lub pośrednio podrzędnym) 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ć, w komponowalnym elemencie AnimatedVisibility podaj wartości EnterTransition.NoneExitTransition.None.

Dodawanie animacji niestandardowej

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 ramach funkcji lambda 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 AnimatedVisibility. AnimatedVisibility czeka, aż wszystkie animacje w Transition się zakończą, a dopiero potem usuwa 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ć, a zatem może usunąć komponent 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 funkcji updateTransition.

Animacja na podstawie stanu docelowego za pomocą 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 musisz 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. Funkcję ContentTransform możesz utworzyć, łącząc funkcję EnterTransition z funkcją ExitTransition za pomocą funkcji wstępnej with. Możesz zastosować SizeTransform do ContentTransform, dołączając ją 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 funkcja AnimatedContent udostępnia 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. 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 pojawiania się i znikania dziecka

Podobnie jak w przypadku AnimatedVisibility modyfikator animateEnterExit jest dostępny w lambda treści AnimatedContent. Użyj tego, aby zastosować EnterAnimation i ExitAnimation do każdego bezpośredniego lub pośredniego podrzędnego obiektu osobno.

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. Szczegółowe informacje znajdziesz w sekcji 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. animacja płynnie przechodząca z mniejszego do większego rozmiaru

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.