На этой странице описывается, как создавать анимации на основе значений в 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) )
Вам не нужно создавать экземпляр какого-либо класса анимации или обрабатывать прерывания. Внутри системы будет создан объект анимации (а именно, экземпляр 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 и запускает анимации входа, выхода и sizeTransform по мере необходимости при изменении 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") } } } }
Оформите переход и сделайте его многоразовым.
Для простых случаев использования допустимо определять анимацию переходов в том же компоненте, что и пользовательский интерфейс. Однако при работе со сложным компонентом с множеством анимируемых значений может потребоваться отделить реализацию анимации от компонента пользовательского интерфейса.
Это можно сделать, создав класс, содержащий все значения анимации, и функцию 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 содержит одну или несколько дочерних анимаций, как и 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 , вы можете получить доступ к некоторым его возможностям настройки через 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) )
В приведенном выше примере вы создаете и запоминаете экземпляр Animatable с начальным значением Color.Gray . В зависимости от значения логического флага ok , цвет анимируется либо до Color.Green , либо до Color.Red . Любое последующее изменение логического значения запускает анимацию до другого цвета. Если анимация уже выполняется, когда значение изменяется, Compose отменяет анимацию, и начинается новая анимация с текущего значения с текущей скоростью.
Этот 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
- Модификаторы анимации и компоненты анимации