На этой странице описывается, как создавать анимации на основе значений в Jetpack Compose, с акцентом на API, которые анимируют значения в зависимости от их текущего и целевого состояний.
Анимируйте одно значение с помощью 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) )
You don't need to create an instance of any animation class or handle interruption. Under the hood, an animation object (namely, an Animatable instance) will be created and remembered at the call site, with the first target value as its initial value. From there on, any time you supply this composable a different target value, an animation is automatically started towards that value. If there's already an animation in flight, the animation starts from its current value (and velocity) and animates toward the target value. During the animation, this composable gets recomposed and returns an updated animation value every frame.
По умолчанию 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 and AnimatedContent are available as extension functions of Transition . The targetState for Transition.AnimatedVisibility and Transition.AnimatedContent is derived from the Transition , and trigger enter, exit, and sizeTransform animations as needed when the Transition 's targetState changes. These extension functions let you hoist all enter, exit, and sizeTransform animations that would otherwise be internal to AnimatedVisibility / AnimatedContent into the Transition . With these extension functions, you can observe AnimatedVisibility / AnimatedContent 's state change from outside. Instead of a boolean visible parameter, this version of AnimatedVisibility takes a lambda that converts the parent transition's target state into a boolean.
Подробности см. в разделах 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") } } } }
Оформите переход и сделайте его многоразовым.
Для простых случаев использования допустимо определять анимацию переходов в том же компоненте, что и пользовательский интерфейс. Однако при работе со сложным компонентом с множеством анимируемых значений может потребоваться отделить реализацию анимации от компонента пользовательского интерфейса.
Это можно сделать, создав класс, содержащий все значения анимации, и функцию update , возвращающую экземпляр этого класса. Реализацию перехода можно вынести в отдельную функцию. Этот шаблон полезен, когда необходимо централизовать логику анимации или сделать сложные анимации многократно используемыми.
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 holds one or more child animations like Transition , but the animations start running as soon as they enter the composition and don't stop unless they're removed. You can create an instance of InfiniteTransition with rememberInfiniteTransition , and add child animations with animateColor , animateFloat , or animateValue . You also need to specify an infiniteRepeatable to specify the animation specifications.
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 creates a transition object that can manage multiple animating values and run them when a state changes. rememberInfiniteTransition is similar, but it creates an infinite transition that can manage multiple animations that continue indefinitely. All of these APIs are composables except for Animatable , which means you can create these animations outside of composition.
Все эти API основаны на более фундаментальном API Animation . Хотя большинство приложений не будут напрямую взаимодействовать с Animation , вы можете получить доступ к некоторым его возможностям настройки через API более высокого уровня. См. раздел «Настройка анимации» для получения дополнительной информации об AnimationVector и AnimationSpec .
Animatable : Анимация с использованием одного значения на основе сопрограмм.
Animatable — это объект-держатель значения, который может анимировать изменение этого значения с помощью animateTo . Это API, поддерживающий реализацию animate*AsState . Он обеспечивает согласованное продолжение и взаимоисключаемость, что означает, что изменение значения всегда происходит непрерывно, и Compose отменяет любую текущую анимацию.
Многие функции 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) )
In the preceding example, you create and remember an instance of Animatable with the initial value of Color.Gray . Depending on the value of the boolean flag ok , the color animates to either Color.Green or Color.Red . Any subsequent change to the boolean value starts an animation to the other color. If an animation is in progress when the value changes, Compose cancels the animation, and the new animation starts from the current snapshot value with the current velocity.
Этот API Animatable является базовой реализацией функции 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 отключен.
- Настройка анимации
- Анимация в Compose
- Модификаторы анимации и компоненты анимации