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

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

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

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

Анимированное появление и исчезновение

Зеленый композитный объект, показывающий и скрывающий себя
Рисунок 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 ВыходПереход
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 для управления анимацией см. в разделе Анимация нескольких свойств одновременно с помощью перехода .

Анимация на основе целевого состояния

Компоновочный элемент 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")
    }
}

По умолчанию исходное содержимое плавно исчезает, а затем появляется целевое (это поведение называется «fade through »). Вы можете настроить это поведение анимации, указав объект 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 анимирует переход между двумя макетами с помощью анимации плавного перехода. При переключении значения, переданного current параметру, содержимое переключается с помощью анимации плавного перехода.

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

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

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

Анимация изменений размера компонуемых объектов

Зеленый компонуемый анимационный объект, плавно изменяющий свой размер.
Рисунок 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 %}