Анимируйте отдельное значение с помощью animate*AsState
Функции animate*AsState
— это простейшие API анимации в Compose для анимации одного значения. Вы указываете только целевое значение (или конечное значение), а API начинает анимацию от текущего значения до указанного значения.
Ниже приведен пример анимации альфы с использованием этого API. Просто обернув целевое значение в animateFloatAsState
, значение альфы теперь является значением анимации между предоставленными значениями ( 1f
или 0.5f
в данном случае).
var enabled by remember { mutableStateOf(true) } val animatedAlpha: Float by animateFloatAsState(if (enabled) 1f else 0.5f, label = "alpha") Box( Modifier .fillMaxSize() .graphicsLayer { alpha = animatedAlpha } .background(Color.Red) )
Обратите внимание, что вам не нужно создавать экземпляр какого-либо класса анимации или обрабатывать прерывание. Под капотом объект анимации (а именно, экземпляр Animatable
) будет создан и сохранен в месте вызова с первым целевым значением в качестве его начального значения. С этого момента, всякий раз, когда вы предоставляете этому компонуемому объекту другое целевое значение, анимация автоматически запускается в направлении этого значения. Если анимация уже находится в полете, анимация начинается с ее текущего значения (и скорости) и анимируется в направлении целевого значения. Во время анимации этот компонуемый объект перекомпоновывается и возвращает обновленное значение анимации каждый кадр.
Из коробки Compose предоставляет функции animate*AsState
для Float
, Color
, Dp
, Size
, Offset
, Rect
, Int
, IntOffset
и IntSize
. Вы можете легко добавить поддержку других типов данных, предоставив TwoWayConverter
для animateValueAsState
, который принимает универсальный тип.
Вы можете настроить характеристики анимации, указав AnimationSpec
. Для получения дополнительной информации см. AnimationSpec .
Анимируйте несколько свойств одновременно с помощью перехода
Transition
управляет одной или несколькими анимациями как дочерними и запускает их одновременно между несколькими состояниями.
Состояния могут быть любого типа данных. Во многих случаях вы можете использовать пользовательский тип enum
для обеспечения безопасности типов, как в этом примере:
enum class BoxState { Collapsed, Expanded }
updateTransition
создает и запоминает экземпляр Transition
и обновляет его состояние.
var currentState by remember { mutableStateOf(BoxState.Collapsed) } val transition = updateTransition(currentState, label = "box state")
Затем вы можете использовать одну из функций расширения animate*
для определения дочерней анимации в этом переходе. Укажите целевые значения для каждого из состояний. Эти функции animate*
возвращают значение анимации, которое обновляется каждый кадр во время анимации, когда состояние перехода обновляется с помощью updateTransition
.
val rect by transition.animateRect(label = "rectangle") { state -> when (state) { BoxState.Collapsed -> Rect(0f, 0f, 100f, 100f) BoxState.Expanded -> Rect(100f, 100f, 300f, 300f) } } val borderWidth by transition.animateDp(label = "border width") { state -> when (state) { BoxState.Collapsed -> 1.dp BoxState.Expanded -> 0.dp } }
При желании можно передать параметр transitionSpec
, чтобы указать разные AnimationSpec
для каждой комбинации изменений состояния перехода. Подробнее см. AnimationSpec .
val color by transition.animateColor( transitionSpec = { when { BoxState.Expanded isTransitioningTo BoxState.Collapsed -> spring(stiffness = 50f) else -> tween(durationMillis = 500) } }, label = "color" ) { state -> when (state) { BoxState.Collapsed -> MaterialTheme.colorScheme.primary BoxState.Expanded -> MaterialTheme.colorScheme.background } }
После того, как переход достигнет целевого состояния, Transition.currentState
будет таким же, как Transition.targetState
. Это можно использовать как сигнал того, завершился ли переход.
Иногда мы хотим иметь начальное состояние, отличное от первого целевого состояния. Мы можем использовать updateTransition
с MutableTransitionState
, чтобы добиться этого. Например, это позволяет нам начать анимацию, как только код входит в композицию.
// Start in collapsed state and immediately animate to expanded var currentState = remember { MutableTransitionState(BoxState.Collapsed) } currentState.targetState = BoxState.Expanded val transition = rememberTransition(currentState, label = "box state") // ……
Для более сложного перехода, включающего несколько составных функций, можно использовать createChildTransition
для создания дочернего перехода. Этот метод полезен для разделения задач между несколькими подкомпонентами в сложном составном. Родительский переход будет знать все значения анимации в дочерних переходах.
enum class DialerState { DialerMinimized, NumberPad } @Composable fun DialerButton(isVisibleTransition: Transition<Boolean>) { // `isVisibleTransition` spares the need for the content to know // about other DialerStates. Instead, the content can focus on // animating the state change between visible and not visible. } @Composable fun NumberPad(isVisibleTransition: Transition<Boolean>) { // `isVisibleTransition` spares the need for the content to know // about other DialerStates. Instead, the content can focus on // animating the state change between visible and not visible. } @Composable fun Dialer(dialerState: DialerState) { val transition = updateTransition(dialerState, label = "dialer state") Box { // Creates separate child transitions of Boolean type for NumberPad // and DialerButton for any content animation between visible and // not visible NumberPad( transition.createChildTransition { it == DialerState.NumberPad } ) DialerButton( transition.createChildTransition { it == DialerState.DialerMinimized } ) } }
Используйте переход с AnimatedVisibility
и AnimatedContent
AnimatedVisibility
и AnimatedContent
доступны как функции расширения Transition
. targetState
для Transition.AnimatedVisibility
и Transition.AnimatedContent
выводится из Transition
и запускает переходы входа/выхода по мере необходимости, когда targetState
Transition
изменяется. Эти функции расширения позволяют поднять все анимации входа/выхода/sizeTransform, которые в противном случае были бы внутренними для AnimatedVisibility
/ AnimatedContent
, в Transition
. С помощью этих функций расширения изменение состояния AnimatedVisibility
/ AnimatedContent
можно наблюдать снаружи. Вместо visible
логического параметра эта версия AnimatedVisibility
принимает лямбду, которая преобразует целевое состояние родительского перехода в логическое значение.
Подробную информацию смотрите в разделах AnimatedVisibility и AnimatedContent .
var selected by remember { mutableStateOf(false) } // Animates changes when `selected` is changed. val transition = updateTransition(selected, label = "selected state") val borderColor by transition.animateColor(label = "border color") { isSelected -> if (isSelected) Color.Magenta else Color.White } val elevation by transition.animateDp(label = "elevation") { isSelected -> if (isSelected) 10.dp else 2.dp } Surface( onClick = { selected = !selected }, shape = RoundedCornerShape(8.dp), border = BorderStroke(2.dp, borderColor), shadowElevation = elevation ) { Column( modifier = Modifier .fillMaxWidth() .padding(16.dp) ) { Text(text = "Hello, world!") // AnimatedVisibility as a part of the transition. transition.AnimatedVisibility( visible = { targetSelected -> targetSelected }, enter = expandVertically(), exit = shrinkVertically() ) { Text(text = "It is fine today.") } // AnimatedContent as a part of the transition. transition.AnimatedContent { targetState -> if (targetState) { Text(text = "Selected") } else { Icon(imageVector = Icons.Default.Phone, contentDescription = "Phone") } } } }
Инкапсулируйте переход и сделайте его пригодным для повторного использования
Для простых случаев использования определение анимаций перехода в том же компонуемом элементе, что и ваш UI, является вполне допустимым вариантом. Однако, когда вы работаете над сложным компонентом с несколькими анимированными значениями, вам может потребоваться отделить реализацию анимации от компонуемого UI.
Это можно сделать, создав класс, содержащий все значения анимации, и функцию «обновления», которая возвращает экземпляр этого класса. Реализацию перехода можно извлечь в новую отдельную функцию. Этот шаблон полезен, когда необходимо централизовать логику анимации или сделать сложные анимации повторно используемыми.
enum class BoxState { Collapsed, Expanded } @Composable fun AnimatingBox(boxState: BoxState) { val transitionData = updateTransitionData(boxState) // UI tree Box( modifier = Modifier .background(transitionData.color) .size(transitionData.size) ) } // Holds the animation values. private class TransitionData( color: State<Color>, size: State<Dp> ) { val color by color val size by size } // Create a Transition and return its animation values. @Composable private fun updateTransitionData(boxState: BoxState): TransitionData { val transition = updateTransition(boxState, label = "box state") val color = transition.animateColor(label = "color") { state -> when (state) { BoxState.Collapsed -> Color.Gray BoxState.Expanded -> Color.Red } } val size = transition.animateDp(label = "size") { state -> when (state) { BoxState.Collapsed -> 64.dp BoxState.Expanded -> 128.dp } } return remember(transition) { TransitionData(color, size) } }
Создайте бесконечно повторяющуюся анимацию с помощью rememberInfiniteTransition
InfiniteTransition
содержит одну или несколько дочерних анимаций, таких как Transition
, но анимации начинают работать, как только они попадают в композицию, и не останавливаются, пока их не удалят. Вы можете создать экземпляр InfiniteTransition
с помощью rememberInfiniteTransition
. Дочерние анимации можно добавлять с помощью animateColor
, animatedFloat
или animatedValue
. Вам также нужно указать infiniteRepeatable для указания спецификаций анимации.
val infiniteTransition = rememberInfiniteTransition(label = "infinite") val color by infiniteTransition.animateColor( initialValue = Color.Red, targetValue = Color.Green, animationSpec = infiniteRepeatable( animation = tween(1000, easing = LinearEasing), repeatMode = RepeatMode.Reverse ), label = "color" ) Box( Modifier .fillMaxSize() .background(color) )
API анимации низкого уровня
Все API анимации высокого уровня, упомянутые в предыдущем разделе, построены на основе API анимации низкого уровня.
Функции animate*AsState
— это простейшие API, которые визуализируют мгновенное изменение значения как значение анимации. Они поддерживаются Animatable
, который является API на основе сопрограмм для анимации одного значения. updateTransition
создает объект перехода, который может управлять несколькими анимационными значениями и запускать их на основе изменения состояния. rememberInfiniteTransition
похож, но он создает бесконечный переход, который может управлять несколькими анимациями, которые продолжают выполняться бесконечно. Все эти API являются компонуемыми, за исключением Animatable
, что означает, что эти анимации могут быть созданы вне композиции.
Все эти API основаны на более фундаментальном API Animation
. Хотя большинство приложений не будут напрямую взаимодействовать с Animation
, некоторые возможности настройки Animation
доступны через API более высокого уровня. См. раздел Настройка анимаций для получения дополнительной информации о AnimationVector
и AnimationSpec
.
Animatable
: анимация отдельных значений на основе сопрограмм
Animatable
— это держатель значения, который может анимировать значение по мере его изменения с помощью animateTo
. Это API, поддерживающее реализацию animate*AsState
. Он обеспечивает последовательное продолжение и взаимоисключаемость, что означает, что изменение значения всегда непрерывно, а любая текущая анимация будет отменена.
Многие функции Animatable
, включая animateTo
, предоставляются как функции приостановки. Это означает, что их необходимо обернуть в соответствующую область действия сопрограммы. Например, можно использовать LaunchedEffect
composable для создания области действия только на время действия указанного значения ключа.
// Start out gray and animate to green/red based on `ok` val color = remember { Animatable(Color.Gray) } LaunchedEffect(ok) { color.animateTo(if (ok) Color.Green else Color.Red) } Box( Modifier .fillMaxSize() .background(color.value) )
В примере выше мы создаем и запоминаем экземпляр Animatable
с начальным значением Color.Gray
. В зависимости от значения логического флага ok
цвет анимируется либо в Color.Green
, либо Color.Red
. Любое последующее изменение логического значения запускает анимацию для другого цвета. Если при изменении значения идет текущая анимация, анимация отменяется, и новая анимация начинается с текущего значения снимка с текущей скоростью.
Это реализация анимации, которая поддерживает API animate*AsState
, упомянутый в предыдущем разделе. По сравнению с animate*AsState
, использование Animatable
напрямую дает нам более детальный контроль в нескольких отношениях. Во-первых, Animatable
может иметь начальное значение, отличное от его первого целевого значения. Например, приведенный выше пример кода сначала показывает серый ящик, который немедленно начинает анимироваться либо зеленым, либо красным цветом. Во-вторых, Animatable
предоставляет больше операций над значением содержимого, а именно snapTo
и animateDecay
. snapTo
немедленно устанавливает текущее значение равным целевому значению. Это полезно, когда сама анимация не является единственным источником истины и должна быть синхронизирована с другими состояниями, такими как события касания. animateDecay
запускает анимацию, которая замедляется с заданной скорости. Это полезно для реализации поведения броска. Подробнее см. в разделе Жесты и анимация .
Из коробки Animatable
поддерживает Float
и Color
, но можно использовать любой тип данных, предоставив TwoWayConverter
. Для получения дополнительной информации см. AnimationVector .
Вы можете настроить характеристики анимации, указав AnimationSpec
. Для получения дополнительной информации см. AnimationSpec .
Animation
: Анимация, управляемая вручную
Animation
— это API анимации самого низкого уровня из доступных. Многие из анимаций, которые мы видели до сих пор, построены на основе Animation. Существует два подтипа Animation
: TargetBasedAnimation
и DecayAnimation
.
Animation
должна использоваться только для ручного управления временем анимации. Animation
не имеет состояния и не имеет понятия жизненного цикла. Она служит в качестве движка расчета анимации, который используют API более высокого уровня.
TargetBasedAnimation
Другие API охватывают большинство вариантов использования, но использование TargetBasedAnimation
напрямую позволяет вам контролировать время воспроизведения анимации самостоятельно. В примере ниже время воспроизведения TargetAnimation
контролируется вручную на основе времени кадра, предоставленного withFrameNanos
.
val anim = remember { TargetBasedAnimation( animationSpec = tween(200), typeConverter = Float.VectorConverter, initialValue = 200f, targetValue = 1000f ) } var playTime by remember { mutableLongStateOf(0L) } LaunchedEffect(anim) { val startTime = withFrameNanos { it } do { playTime = withFrameNanos { it } - startTime val animationValue = anim.getValueFromNanos(playTime) } while (someCustomCondition()) }
DecayAnimation
В отличие от TargetBasedAnimation
, DecayAnimation
не требует предоставления targetValue
. Вместо этого он вычисляет targetValue
на основе начальных условий, установленных initialVelocity
и initialValue
и предоставленного DecayAnimationSpec
.
Анимации затухания часто используются после жеста рывка, чтобы замедлить элементы до полной остановки. Скорость анимации начинается со значения, установленного initialVelocityVector
, и замедляется с течением времени.
Рекомендовано для вас
- Примечание: текст ссылки отображается, когда JavaScript отключен.
- Настроить анимацию {:#customize-animations}
- Анимации в Compose
- Модификаторы анимации и компоновочные элементы