Модификаторы анимации и составные элементы

Compose поставляется со встроенными композициями и модификаторами для обработки распространенных случаев использования анимации.

Встроенные анимированные композиции

Анимируйте появление и исчезновение с помощью AnimatedVisibility

Зеленый композитный объект, показывающий и скрывающий себя
Рисунок 1. Анимация появления и исчезновения элемента в столбце

Композиционный элемент AnimatedVisibility анимирует появление и исчезновение своего содержимого.

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

По умолчанию контент появляется плавно и расширяется, а исчезает плавно и сжимается. Переход можно настроить, указав EnterTransition и 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)
    )
}

Как видно из примера выше, можно объединить несколько объектов EnterTransition или ExitTransition с помощью оператора + , и каждый из них принимает необязательные параметры для настройки своего поведения. Подробнее см. в справочных материалах.

Примеры EnterTransition и ExitTransition

EnterTransition ВыходПереход
fadeIn
плавное появление анимации
fadeOut
анимация затухания
slideIn
слайд в анимации
slideOut
анимация выдвижения
slideInHorizontally
анимация горизонтального слайда
slideOutHorizontally
анимация горизонтального раздвижения
slideInVertically
слайд в вертикальной анимации
slideOutVertically
анимация вертикального выдвижения
scaleIn
масштаб в анимации
scaleOut
анимация масштабирования
expandIn
развернуть в анимации
shrinkOut
анимация сжатия
expandHorizontally
анимация горизонтального расширения
shrinkHorizontally
анимация сжатия по горизонтали
expandVertically
анимация вертикального расширения
shrinkVertically
сжимать вертикально анимацию

AnimatedVisibility также предлагает вариант, принимающий MutableTransitionState . Это позволяет запускать анимацию сразу после добавления AnimatedVisibility в дерево композиции. Это также полезно для наблюдения за состоянием анимации.

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

Анимированный вход и выход для детей

Содержимое AnimatedVisibility (прямых или косвенных дочерних элементов) может использовать модификатор animateEnterExit для задания различных анимаций для каждого из них. Визуальный эффект для каждого из этих дочерних элементов представляет собой комбинацию анимаций, указанных в компонуемом элементе AnimatedVisibility , и собственных анимаций входа и выхода дочернего элемента.

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

В некоторых случаях может потребоваться, чтобы AnimatedVisibility вообще не применяла анимацию, чтобы каждый дочерний элемент мог иметь свою собственную анимацию с помощью animateEnterExit . Для этого укажите EnterTransition.None и ExitTransition.None в компонуемом объекте AnimatedVisibility .

Добавить пользовательскую анимацию

Если вы хотите добавить пользовательские эффекты анимации помимо встроенных анимаций входа и выхода, обратитесь к базовому экземпляру Transition через свойство transition внутри лямбда-выражения content для AnimatedVisibility . Любые состояния анимации, добавленные к экземпляру Transition, будут выполняться одновременно с анимациями входа и выхода AnimatedVisibility . AnimatedVisibility ожидает завершения всех анимаций в Transition , прежде чем удалить его содержимое. Для анимаций выхода, созданных независимо от Transition (например, с помощью animate*AsState ), AnimatedVisibility не сможет их учитывать и, следовательно, может удалить компонуемое содержимое до их завершения.

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

Подробную информацию о Transition см. в updateTransition .

Анимация на основе целевого состояния с помощью AnimatedContent

Компоновочный элемент AnimatedContent анимирует свое содержимое, изменяя его в зависимости от целевого состояния.

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

Обратите внимание, что всегда следует использовать параметр лямбда и учитывать его применительно к содержимому. API использует это значение в качестве ключа для определения отображаемого в данный момент содержимого.

По умолчанию исходное содержимое плавно исчезает, а затем появляется целевое (это поведение называется плавным переходом ). Вы можете настроить это поведение анимации, указав объект ContentTransform в параметре transitionSpec . Вы можете создать ContentTransform , объединив EnterTransition с ExitTransition с помощью инфиксной функции with . Вы можете применить SizeTransform к ContentTransform , присоединив его с помощью инфиксной функции 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 определяет, как должно отображаться целевое содержимое, а ExitTransition — как должно исчезать исходное содержимое. Помимо всех функций EnterTransition и ExitTransition , доступных для AnimatedVisibility , AnimatedContent предлагает slideIntoContainer и slideOutOfContainer . Это удобные альтернативы slideInHorizontally/Vertically и slideOutHorizontally/Vertically которые рассчитывают расстояние скольжения на основе размеров исходного и целевого содержимого AnimatedContent .

SizeTransform определяет, как размер должен изменяться в зависимости от исходного и целевого содержимого. При создании анимации вы можете управлять как исходным, так и целевым размером. SizeTransform также управляет тем, должно ли содержимое обрезаться по размеру компонента во время анимации.

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

Анимировать переходы входа и выхода ребенка

Как и AnimatedVisibility , модификатор animateEnterExit доступен внутри лямбда-функции content объекта AnimatedContent . Используйте его, чтобы применить EnterAnimation и ExitAnimation к каждому прямому или косвенному дочернему элементу отдельно.

Добавить пользовательскую анимацию

Как и AnimatedVisibility , поле transition доступно внутри лямбда-функции content объекта AnimatedContent . Используйте его для создания собственного эффекта анимации, который будет выполняться одновременно с переходом AnimatedContent . Подробности см. в описании updateTransition .

Анимация между двумя макетами с помощью Crossfade

Crossfade анимирует переход между двумя макетами с помощью анимации плавного перехода. При переключении значения, переданного current параметру, содержимое переключается с помощью анимации плавного перехода.

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

Встроенные модификаторы анимации

Анимируйте изменения размера компонуемых объектов с помощью animateContentSize

Зеленый компонуемый анимационный объект, плавно изменяющий свой размер.
Рисунок 2. Композиция плавно анимируется между маленьким и большим размером.

Модификатор animateContentSize анимирует изменение размера.

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
        }

) {
}

Анимация элементов списка

Если вы хотите анимировать переупорядочивание элементов в списке или сетке Lazy, ознакомьтесь с документацией по анимации элементов макета Lazy .

{% дословно %} {% endverbatim %} {% дословно %} {% endverbatim %}