Анимации¶
Анимация очень важна для создания отличного пользовательского опыта. Неподвижные объекты должны преодолеть инерцию, когда они начинают двигаться. Объекты в движении обладают импульсом и редко останавливаются сразу. Анимации позволяют передать физически правдоподобное движение в интерфейсе.
React Native предоставляет две взаимодополняющие системы анимации: Animated
для гранулированного и интерактивного управления конкретными значениями, и LayoutAnimation
для анимированных глобальных операций с макетом.
Animated
API¶
API Animated
разработан для краткого выражения широкого спектра интересных моделей анимации и взаимодействия в очень производительной форме. Animated
фокусируется на декларативных отношениях между входами и выходами, с настраиваемыми преобразованиями между ними, и методами start
/stop
для управления выполнением анимации по времени.
Animated
экспортирует шесть типов анимируемых компонентов: View
, Text
, Image
, ScrollView
, FlatList
и SectionList
, но вы также можете создать свой собственный, используя Animated.createAnimatedComponent()
.
Например, представление контейнера, которое затухает при его установке, может выглядеть следующим образом:
Давайте разберем, что здесь происходит. В конструкторе FadeInView
, новое Animated.Value
под названием fadeAnim
инициализируется как часть state
. Свойство opacity на View
отображается на это анимированное значение. За кулисами извлекается числовое значение и используется для установки непрозрачности.
Когда компонент монтируется, непрозрачность устанавливается в 0. Затем запускается анимация смягчения для анимированного значения fadeAnim
, которое будет обновлять все свои зависимые отображения (в данном случае только непрозрачность) на каждом кадре по мере того, как значение анимируется до конечного значения 1.
Это делается оптимизированным способом, что быстрее, чем вызов setState
и повторный рендеринг. Поскольку вся конфигурация является декларативной, мы сможем реализовать дальнейшую оптимизацию, которая сериализует конфигурацию и запускает анимацию на высокоприоритетном потоке.
Настройка анимации¶
Анимация обладает широкими возможностями настройки. Пользовательские и предопределенные функции смягчения, задержки, длительности, коэффициенты затухания, пружинные константы и многое другое можно настраивать в зависимости от типа анимации.
Animated
предоставляет несколько типов анимации, наиболее часто используемым из которых является Animated.timing()
. Он поддерживает анимацию значения во времени с помощью одной из различных предопределенных функций смягчения, или вы можете использовать свои собственные. Функции смягчения обычно используются в анимации для передачи постепенного ускорения и замедления объектов.
По умолчанию timing
будет использовать кривую easeInOut
, которая передает постепенное ускорение до полной скорости и завершается постепенным замедлением до остановки. Вы можете задать другую функцию смягчения, передав параметр easing
. Также поддерживаются пользовательская duration
или даже delay
перед началом анимации.
Например, если мы хотим создать 2-секундную анимацию объекта, который слегка отступает назад, прежде чем переместиться в конечное положение:
1 2 3 4 5 6 |
|
Посмотрите раздел Настройка анимаций справочника API Animated
, чтобы узнать больше обо всех параметрах конфигурации, поддерживаемых встроенными анимациями.
Составление анимации¶
Анимации можно комбинировать и воспроизводить последовательно или параллельно. Последовательные анимации могут воспроизводиться сразу после завершения предыдущей анимации или начинаться после заданной задержки. API Animated
предоставляет несколько методов, таких как sequence()
и delay()
, каждый из которых принимает массив анимаций для выполнения и автоматически вызывает start()
/ stop()
по мере необходимости.
Например, следующая анимация останавливается, а затем возвращается назад, параллельно вращаясь:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
Если одна анимация остановлена или прервана, то все остальные анимации в группе также останавливаются. У Animated.parallel
есть опция stopTogether
, которую можно установить в false
, чтобы отключить это.
Полный список методов композиции можно найти в разделе Composing animations справочника Animated
API.
Объединение анимированных значений¶
Вы можете объединить два анимированных значения с помощью сложения, умножения, деления или по модулю, чтобы получить новое анимированное значение.
Бывают случаи, когда анимированное значение должно инвертировать другое анимированное значение для расчета. Примером может служить инвертирование шкалы (2x → 0,5x):
1 2 3 4 5 6 7 |
|
Интерполяция¶
Каждое свойство может быть сначала пропущено через интерполяцию. Интерполяция сопоставляет входные диапазоны с выходными диапазонами, обычно используя линейную интерполяцию, но также поддерживает функции смягчения. По умолчанию она экстраполирует кривую за пределы заданных диапазонов, но можно также зажать выходное значение.
Основное отображение для преобразования диапазона 0-1 в диапазон 0-100 будет таким:
1 2 3 4 |
|
Например, вы можете считать, что ваше Animated.Value
изменяется от 0
до 1
, но анимировать позицию от 150px
до 0px
и непрозрачность от 0
до 1
. Это можно сделать, изменив style
из примера выше следующим образом:
1 2 3 4 5 6 7 8 9 |
|
interpolate()
также поддерживает несколько сегментов диапазона, что удобно для определения мертвых зон и других удобных трюков. Например, чтобы получить отношение отрицания при -300
, которое переходит в 0
при -100
, затем возвращается к 1
при 0
, а затем снова опускается до нуля при 100
, после чего следует мертвая зона, которая остается на 0
для всего, что находится за пределами этого диапазона, вы можете сделать следующее:
1 2 3 4 |
|
Это будет выглядеть следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
interpolate()
также поддерживает отображение на строки, позволяя вам анимировать цвета, а также значения с единицами измерения. Например, если вы хотите анимировать вращение, вы можете сделать следующее:
1 2 3 4 |
|
interpolate()
также поддерживает произвольные функции смягчения, многие из которых уже реализованы в модуле Easing
. interpolate()
также имеет настраиваемое поведение для экстраполяции outputRange
. Вы можете задать экстраполяцию, установив опции extrapolate
, extrapolateLeft
или extrapolateRight
. По умолчанию используется значение extend
, но вы можете использовать clamp
, чтобы выходное значение не превышало outputRange
.
Отслеживание динамических значений¶
Анимированные значения также могут отслеживать другие значения путем установки toValue
анимации на другое анимированное значение вместо простого числа. Например, анимация "Chat Heads", как в Messenger на Android, может быть реализована с помощью spring()
, привязанной к другому анимированному значению, или с помощью timing()
и duration
, равной 0
, для жесткого отслеживания. Они также могут быть составлены с помощью интерполяций:
1 2 3 4 5 6 7 8 |
|
Анимированные значения leader
и follower
будут реализованы с помощью Animated.ValueXY()
. ValueXY
— это удобный способ работы с 2D-взаимодействиями, такими как панорамирование или перетаскивание. Это базовая обертка, содержащая два экземпляра Animated.Value
и несколько вспомогательных функций, вызывающих их, что делает ValueXY
полноценной заменой Value
во многих случаях. Это позволяет нам отслеживать значения x
и y
в приведенном выше примере.
Отслеживание жестов¶
Жесты, такие как панорамирование или прокрутка, и другие события могут непосредственно отображаться на анимированные значения с помощью Animated.event
. Для этого используется структурированный синтаксис карты, чтобы можно было извлекать значения из сложных объектов событий. Первый уровень — это массив, позволяющий отображать несколько аргументов, и этот массив содержит вложенные объекты.
Например, при работе с жестами горизонтальной прокрутки вы должны сделать следующее, чтобы сопоставить event.nativeEvent.contentOffset.x
с scrollX
(Animated.Value
):
1 2 3 4 5 6 7 8 9 |
|
Следующий пример реализует горизонтальную прокрутку карусели, где индикаторы позиции прокрутки анимируются с помощью Animated.event
, используемого в ScrollView
.
Пример ScrollView с анимированным событием¶
При использовании PanResponder
вы можете использовать следующий код для извлечения позиций x и y из gestureState.dx
и gestureState.dy
. Мы используем null
в первой позиции массива, поскольку нас интересует только второй аргумент, переданный обработчику PanResponder
, которым является gestureState
.
1 2 3 4 5 6 |
|
PanResponder с анимированным событием Пример¶
Реагирование на текущее значение анимации¶
Вы можете заметить, что нет четкого способа прочитать текущее значение во время анимации. Это связано с тем, что значение может быть известно только в нативном времени выполнения из-за оптимизации. Если вам нужно запустить JavaScript в ответ на текущее значение, есть два подхода:
spring.stopAnimation(callback)
остановит анимацию и вызоветcallback
с конечным значением. Это полезно при создании переходов между жестами.spring.addListener(callback)
вызоветcallback
асинхронно во время работы анимации, предоставляя последнее значение. Это полезно для запуска изменений состояния, например, для привязки боббла к новому варианту, когда пользователь подтаскивает его ближе, потому что эти большие изменения состояния менее чувствительны к задержке в несколько кадров по сравнению с непрерывными жестами, такими как панорамирование, которые должны работать со скоростью 60 кадров в секунду.
Animated
спроектирован как полностью сериализуемый, чтобы анимация могла выполняться с высокой производительностью, независимо от обычного цикла событий JavaScript. Это влияет на API, поэтому имейте это в виду, когда кажется, что сделать что-то немного сложнее по сравнению с полностью синхронной системой. Посмотрите на Animated.Value.addListener
как способ обойти некоторые из этих ограничений, но используйте его осторожно, поскольку в будущем это может сказаться на производительности.
Использование родного драйвера¶
API Animated
разработан так, чтобы быть сериализуемым. Используя Native driver, мы отправляем все данные об анимации в native перед началом анимации, что позволяет native-коду выполнять анимацию в потоке UI без необходимости проходить через мост на каждом кадре. После запуска анимации поток JS может быть заблокирован без ущерба для анимации.
Использовать родной драйвер для обычных анимаций можно, установив useNativeDriver: true
в конфигурации анимации при ее запуске. Анимации без свойства useNativeDriver
по умолчанию будут иметь значение false
по унаследованным причинам, но будут выдавать предупреждение (и ошибку проверки типов в TypeScript).
1 2 3 4 5 |
|
Анимированные значения совместимы только с одним драйвером, поэтому если вы используете родной драйвер при запуске анимации значения, убедитесь, что каждая анимация этого значения также использует родной драйвер.
Нативный драйвер также работает с Animated.event
. Это особенно полезно для анимации, которая следует за положением прокрутки, поскольку без нативного драйвера анимация всегда будет выполняться на кадр позже жеста из-за асинхронной природы React Native.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Вы можете увидеть родной драйвер в действии, запустив приложение RNTester app, а затем загрузив пример Native Animated Example. Вы также можете взглянуть на исходный код, чтобы узнать, как были созданы эти примеры.
Предостережения¶
Не все, что можно сделать с помощью Animated
, в настоящее время поддерживается родным драйвером. Основным ограничением является то, что вы можете анимировать только свойства, не относящиеся к макету: такие вещи, как transform
и opacity
будут работать, но Flexbox и свойства позиции — нет. При использовании Animated.event
, он будет работать только с прямыми событиями, но не с событиями пузырьков. Это означает, что он не работает с PanResponder
, но работает с такими вещами, как ScrollView#onScroll
.
Когда запущена анимация, она может помешать компонентам VirtualizedList
отображать больше строк. Если вам нужно запустить длинную или зацикленную анимацию, пока пользователь прокручивает список, вы можете использовать isInteraction: false
в конфигурации анимации, чтобы предотвратить эту проблему.
Имейте в виду¶
При использовании стилей трансформации, таких как rotateY
, rotateX
и других, убедитесь, что стиль трансформации perspective
установлен. В настоящее время некоторые анимации могут не отображаться на Android без него. Пример ниже.
1 2 3 4 5 6 7 8 9 |
|
Дополнительные примеры¶
В приложении RNTester есть различные примеры использования Animated
:
LayoutAnimation
API¶
LayoutAnimation
позволяет вам глобально настроить анимации create
и update
, которые будут использоваться для всех представлений в следующем цикле рендеринга/разметки. Это полезно для обновления макета Flexbox, не утруждая себя измерением или вычислением конкретных свойств, чтобы анимировать их напрямую, и особенно полезно, когда изменения макета могут повлиять на предков, например, расширение "see more", которое также увеличивает размер родителя и сдвигает вниз ряд ниже, что в противном случае потребовало бы явной координации между компонентами, чтобы анимировать их все синхронно.
Обратите внимание, что хотя LayoutAnimation
очень мощная и может быть весьма полезной, она предоставляет гораздо меньше контроля, чем Animated
и другие библиотеки анимации, поэтому вам может понадобиться другой подход, если вы не можете заставить LayoutAnimation
делать то, что вы хотите.
Обратите внимание, что для того, чтобы это работало на Android, необходимо установить следующие флаги через UIManager
:
1 |
|
В данном примере используется предустановленное значение, вы можете настроить анимацию по своему усмотрению, более подробную информацию смотрите в LayoutAnimation.js.
Дополнительные примечания¶
requestAnimationFrame
¶
requestAnimationFrame
— это полифилл из браузера, с которым вы можете быть знакомы. Он принимает функцию в качестве единственного аргумента и вызывает эту функцию перед следующим перерисовкой. Это важный строительный блок для анимации, который лежит в основе всех API анимации на базе JavaScript. В целом, вам не нужно вызывать эту функцию самостоятельно — API анимации будут управлять обновлением кадров за вас.
setNativeProps
¶
Как уже упоминалось в разделе "Прямая манипуляция", setNativeProps
позволяет нам изменять свойства компонентов с поддержкой native (компоненты, которые действительно поддерживаются родными представлениями, в отличие от составных компонентов) напрямую, без необходимости setState
и повторного рендеринга иерархии компонентов.
Мы можем использовать это в примере Rebound для обновления шкалы — это может быть полезно, если обновляемый компонент глубоко вложен и не был оптимизирован с помощью shouldComponentUpdate
.
Если вы обнаружите, что ваши анимации падают (частота кадров ниже 60 в секунду), попробуйте использовать setNativeProps
или shouldComponentUpdate
для их оптимизации. Или вы можете запустить анимацию в потоке UI, а не в потоке JavaScript с опцией useNativeDriver
. Вы также можете отложить любую работу, требующую больших вычислений, до завершения анимации, используя InteractionManager. Вы можете контролировать частоту кадров с помощью инструмента "FPS Monitor" в меню In-App Dev Menu.