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

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 внутри лямбда-выражения содержимого для 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 { mutableStateOf(0) }
    Button(onClick = { count++ }) {
        Text("Add")
    }
    AnimatedContent(targetState = count) { targetCount ->
        // Make sure to use `targetCount`, not `count`.
        Text(text = "Count: $targetCount")
    }
}

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

По умолчанию исходное содержимое исчезает, а затем появляется целевое содержимое (это поведение называется сквозным исчезновением ). Вы можете настроить это поведение анимации, указав объект 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() with
                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() with
                slideOutVertically { height -> height } + fadeOut()
        }.using(
            // Disable clipping since the faded slide-in/out should
            // be displayed out of bounds.
            SizeTransform(clip = false)
        )
    }
) { 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)) with
                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
                        }
                    }
                }
        }
    ) { targetExpanded ->
        if (targetExpanded) {
            Expanded()
        } else {
            ContentIcon()
        }
    }
}

Анимация переходов входа и выхода ребенка

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

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

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

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

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

var currentPage by remember { mutableStateOf("A") }
Crossfade(targetState = currentPage) { 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
        }

) {
}

Список анимаций элементов

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

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