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, który wyświetla się i ukrywa
Rysunek 1. Animacja pojawiania się i znikania elementu w kolumnie

Komponent AnimatedVisibility animuje pojawianie się i znikanie zawartoś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ę w ramach efektu otwierania i rozszerzania, a znika w ramach efektu zamykania i kurczenia. 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 dokumentach referencyjnych.

Przykłady dotyczące EnterTransition i ExitTransition

EnterTransition ExitTransition
fadeIn
animacja wstępowania
fadeOut
animacja znikania
slideIn
animacja wsuwania
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 się 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 wejść i wyjść dla dzieci

Treści w elementach podrzędnych AnimatedVisibility (bezpośrednich lub pośrednich) 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 komponencie AnimatedVisibility oraz animacji wchodzenia i wychodzenia z ekranu elementów podrzędnych.

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 AnimatedVisibility nie stosował żadnych animacji, aby każde dziecko mogło mieć własne animacje w animateEnterExit. Aby to zrobić, określ wartości EnterTransition.NoneExitTransition.None w komponentach AnimatedVisibility.

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 treści lambda dla AnimatedVisibility. Wszystkie stany animacji dodane do wystąpienia 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ć, dlatego 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 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. 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 animacja rozmiaru powinna wyglądać w przypadku treści początkowych i docelowych. Podczas tworzenia animacji masz dostęp do rozmiaru początkowego 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ść elementów

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 ramach treści lambda funkcji AnimatedContent. Użyj tego, aby utworzyć niestandardowy efekt animacji, który będzie działał jednocześnie z AnimatedContent. Szczegółowe informacje znajdziesz w 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

Zielona kompozycja z animacją płynnej 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.