Анимируйте одно значение с помощью animate*AsState
Функции animate*AsState
— это простейшие API-интерфейсы анимации в Compose для анимации одного значения. Вы указываете только целевое значение (или конечное значение), и API запускает анимацию от текущего значения до указанного значения.
Ниже приведен пример анимации альфа-канала с использованием этого API. Просто обернув целевое значение в animateFloatAsState
, значение альфа теперь является значением анимации между предоставленными значениями (в данном случае 1f
или 0.5f
).
var enabled by remember { mutableStateOf(true) } val alpha: Float by animateFloatAsState(if (enabled) 1f else 0.5f, label = "alpha") Box( Modifier .fillMaxSize() .graphicsLayer(alpha = alpha) .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
изменяется. Эти функции расширения позволяют поднимать в Transition
все анимации входа/выхода/sizeTransform, которые в противном случае были бы внутренними для AnimatedVisibility
/ AnimatedContent
. С помощью этих функций расширения изменение состояния 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") } } } }
Инкапсулируйте переход и сделайте его многоразовым
Для простых случаев использования определение анимации перехода в том же компоненте, что и ваш пользовательский интерфейс, является вполне допустимым вариантом. Однако, когда вы работаете над сложным компонентом с множеством анимированных значений, вам может потребоваться отделить реализацию анимации от составного пользовательского интерфейса.
Вы можете сделать это, создав класс, который содержит все значения анимации, и функцию «обновления», которая возвращает экземпляр этого класса. Реализация перехода может быть выделена в новую отдельную функцию. Этот шаблон полезен, когда необходимо централизовать логику анимации или сделать сложную анимацию многоразовой.
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
. Вам также необходимо указать InfinRepeatable , чтобы указать спецификации анимации.
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
для создания области только на время действия указанного значения ключа.
// 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
- Модификаторы анимации и составные элементы