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

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

По умолчанию начальное содержимое плавно исчезает, а затем плавно появляется целевое содержимое (это поведение называется «прозрачное исчезание »). Вы можете настроить это поведение анимации, указав объект ContentTransform в параметре transitionSpec . Вы можете создать экземпляр ContentTransform , объединив объект EnterTransition с объектом ExitTransition , используя функцию with infix. Вы можете применить SizeTransform к объекту ContentTransform , прикрепив его с using функции с infix.

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
        }

) {
}

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

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

{% verbatim %} {% endverbatim %} {% verbatim %} {% endverbatim %}