Advertisement
  1. Code
  2. React Native
Code

Практические примеры анимации в React Native

by
Difficulty:IntermediateLength:LongLanguages:

Russian (Pусский) translation by Masha Kolesnikova (you can also view the original English article)

В этом уроке вы узнаете, как реализовать анимацию, которую вы обычно используете в мобильных приложениях. В частности, вы узнаете, как реализовать анимацию, которая:

  • Предоставляет визуальную обратную связь: например, когда пользователь нажимает кнопку, вы хотите использовать анимацию, чтобы показать пользователю, что кнопка действительно нажата.
  • Показывает текущий статус системы: при выполнении процесса, который не завершается мгновенно (например, при загрузке фотографии или отправке сообщения электронной почты), вы хотите показать анимацию, чтобы пользователь понял, сколько времени займет этот процесс.
  • Визуально соединяет состояния перехода: когда пользователь нажимает кнопку, чтобы что-то перенести в начало экрана, этот переход должен быть анимированным, чтобы пользователь знал, откуда возник элемент.
  • Получает внимание пользователя: когда есть важное уведомление, вы можете использовать анимацию, чтобы привлечь внимание пользователя.

Этот учебник является продолжением моей статьи «Анимация вашего React Native приложения». Поэтому, если вы новичок в анимации в React Native, обязательно сначала посетите ту статью, потому что некоторые из концепций, которые будут использоваться в этом руководстве, более подробно объясняются там.

Кроме того, если хотите, вы можете найти полный исходный код, используемый в этом учебнике, в репозитории на GitHub.

Что мы строим

Мы собираемся создать приложение, которое реализует каждый из различных типов анимации, о котором я упоминал ранее. В частности, мы собираемся создать следующие страницы, каждая из которых будет реализовывать анимации для разных целей.

  • Страница новостей: использует жесты для визуальной обратной связи и отображения текущего состояния системы.
  • Страница кнопок: использует кнопки для визуальной обратной связи и отображения текущего состояния системы.
  • Страница прогресса: использует индикатор выполнения для отображения текущего состояния системы.
  • Страница расширения: визуально связывает переходные состояния с использованием расширяющихся и сокращающихся движений.
  • Страница привлечения внимания: использует захватывающие внимание движения, чтобы привлечь внимание пользователя.

Если вы хотите получить привью каждой из анимаций, ознакомьтесь с этим альбомом Imgur.

Настройка проекта

Начните с создания нового проекта React Native:

После создания проекта перейдите во вновь созданную папку, откройте файл package.json и добавьте следующие dependencies:

Выполните команду npm install для установки этих двух пакетов. react-native-animatable используется для легкого внедрения анимаций, а для создания значков для страницы расширенного использования используются react-native-vector-icons. Если вы не хотите использовать значки, вы можете просто использовать компонент Text. В противном случае следуйте инструкциям по установке react-native-vector-icons на их странице GitHub.

Создание приложения

Откройте файл index.android.js или файл index.ios.js и замените существующее содержимое следующим:

Как только это будет сделано, обязательно создайте соответствующие файлы, чтобы вы не получили никаких ошибок. Все файлы, над которыми мы будем работать, хранятся в каталоге src. Внутри этого каталога находятся следующие папки:

  • components: повторно используемые компоненты, которые будут использоваться другими компонентами или страницами.
  • img: изображения, которые будут использоваться во всем приложении. Вы можете получить изображения из репозитория GitHub.
  • pages: страницы приложения.

Страница новостей

Начнем с страницы новостей.

Во-первых, добавьте компоненты, которые мы будем использовать:

Вы уже должны быть знакомы с большинством из них, за исключением RefreshControl и настраиваемого компонента NewsItem, который мы будем создавать позже. RefreshControl используется для добавления функциональности «pull to refresh» внутри компонента ScrollView или ListView. Таким образом, это на самом деле тот компонент, который будет обрабатывать жесты и анимацию для нас, когда мы тянем вниз. Не нужно реализовывать наши собственные. По мере того как вы приобретете больше опыта использования React Native, вы заметите, что анимации фактически встроены в некоторые компоненты, и нет необходимости использовать класс Animated для реализации своих собственных.

Создайте компонент, который будет содержать всю страницу:

Внутри constructor инициализируйте анимированное значение для сохранения текущей непрозрачности (opacityValue) новостей. Мы хотим, чтобы новостные объекты имели меньшую прозрачность, в то время как новостные статьи обновляются. Это дает пользователю представление о том, что они не могут взаимодействовать со всей страницей во время обновления новостей. Функция is_news_refreshing используется в качестве переключателя для указания того, обновляется ли новостная лента или нет.

Функция opacity() - это функция, которая запускает анимацию для изменения непрозрачности.

Внутри функции render() определите, как изменится значение непрозрачности. Здесь outputRange равен [1, 0, 1], что означает, что он начнется с полной прозрачностью, затем перейдит в нулевую прозрачность и снова вернится к полной непрозрачности. Как определено внутри функции opacity(), этот переход будет выполнен в течение 3500 миллисекунд (3,5 секунды).

Компонент <RefreshControl> добавляется в <ScrollView>. Это вызывает функцию refreshNews() всякий раз, когда делает свайп вниз, пока он находится в верхней части списка (когда scrollY равно 0). Вы можете добавить поддержку colors для настройки цвета анимации обновления.

Внутри <ScrollView> используйте компонент <Animated.View> и установите для style значение opacity:

Функция refreshNews() вызывает функцию opacity() и обновляет значение is_news_refreshing до true. Это позволяет компоненту <RefreshControl> узнать, что анимация обновления должна быть уже показана. После этого используйте функцию setTimeout(), чтобы обновить значение is_news_refreshing до false после 3500 миллисекунд (3,5 секунды). Это скроет анимацию обновления из представления. К тому времени анимация прозрачности также должна быть выполнена, поскольку мы устанавливаем ранее то же самое значение для продолжительности в функции opacity.

RenderNewsItems() принимает массив новостей, которые мы объявили ранее внутри constructor(), и отображает каждую из них с помощью компонента <NewsItem>.

Компонент NewsItem

Компонент NewsItem (src/components/NewsItem.js) отображает заголовок и сайт новостной статьи и обертывает их внутри компонента <Button>, чтобы с ними можно было взаимодействовать.

Компонент кнопки

Компонент Button (src/components/Button.js) использует компонент TouchableHighlight для создания кнопки. Свойство underlayColor используется для указания цвета подкладки при нажатии кнопки. Это встроенный способ React Native, обеспечивающий визуальную обратную связь; Позже в разделе «Страница кнопок» мы рассмотрим другие способы, которыми кнопки могут обеспечивать визуальную обратную связь.

Возвращаясь к компоненту NewsPage, добавьте стиль:

Страница кнопок

На странице кнопок (src/pages/ButtonsPage.js) отображаются три вида кнопок: обычно используемая кнопка, которая подсвечивается, кнопка, которая становится немного больше, и кнопка, отображающая текущее состояние операции. Начните с добавления необходимых компонентов:

Раньше вы видели, как работает компонент Button, поэтому мы просто сосредоточимся на двух других кнопках.

Компонент кнопки масштабирования

Сначала давайте посмотрим на кнопку масштабирования (src/components/ScalingButton.js). В отличие от кнопки, которую мы использовали ранее, для ее создания используется встроенный компонент TouchableWithoutFeedback. Раньше мы использовали компонент TouchableHighlight, который поставляется со всеми необходимыми для кнопки настройками. Вы можете рассматривать TouchableWithoutFeedback как голую кнопку, в которой вы должны указать все, что нужно, когда пользователь нажимает на нее. Это идеально подходит для нашего случая, потому что нам не нужно беспокоиться о поведении кнопки по умолчанию, мешающем анимации, которую мы хотим реализовать.

Подобно компоненту Button, это будет функциональный тип компонента, так как нам действительно не нужно работать с состоянием.

Внутри компонента создайте анимированное значение, которое сохранит текущую шкалу кнопок.

Добавьте функцию, которая запустит анимацию масштаба. Мы не хотим, чтобы приложение выглядело медленным, поэтому сделайте duration максимально маленьким, но одновременно достаточно высоким, чтобы пользователь мог понять, что происходит. 300 миллисекунд - хорошая отправная точка, но не стесняйтесь поиграть с этим значением.

Определите то, как кнопка будет масштабироваться (outputRange) в зависимости от текущего значения (inputRange). Мы не хотим, чтобы она стала слишком большой, поэтому мы придерживаемся 1.1 как самого высокого значения. Это означает, что она будет на 0,1 больше, чем ее первоначальный размер на полпути (0.5) всей анимации.

Функция onPress() выполняет анимацию масштаба сначала перед вызовом метода, переданного пользователем через свойства.

Функция getContent() выводит дочерние компоненты, если они доступны. Если нет, отображается компонент Text, содержащий свойства label.

Добавьте стили и экспортируйте кнопку:

Компонент кнопки с состоянием

Далее находится кнопка состояния (src/components/StatefulButton.js). При нажатии эта кнопка меняет цвет фона и показывает загрузочное изображение до тех пор, пока выполняемая операция не будет выполнена.

Загружаемое изображение, которое мы будем использовать, является анимированным gif. По умолчанию React Native на Android не поддерживает анимированные gif. Чтобы это заработало, вам нужно отредактировать файл android/app/build.gradle и добавить compile 'com.facebook.fresco: animated-gif: 0.12.0' под массивом dependencies следующим образом :

Если вы работаете в iOS, анимированные gif будут работать по умолчанию.

Возвращаясь к компоненту кнопки состояния, как и кнопка масштабирования, для создания кнопки используется компонент TouchableWithoutFeedback, так как он также будет реализовывать свою собственную анимацию.

В отличие от кнопки масштабирования, этот компонент будет полноценным компонентом класса, поскольку он управляет своим собственным состоянием.

Внутри constructor() создайте анимированное значение для сохранения текущего цвета фона. После этого инициализируйте состояние, которое действует как переключатель для сохранения текущего состояния кнопки. По умолчанию установлено значение false. Как только пользователь нажал на кнопку, оно будет обновлено true и будет снова установлено в значение false снова после выполнения воображаемого процесса.

Внутри функции render() укажите различные цвета фона, которые будут использоваться на основе текущего значения анимированного значения.

Затем оберните все внутри компонента TouchableWithoutFeedback, а внутри <Animated.View> находится анимированный цвет фона. Мы также отображаем изображение загрузчика, если текущее значение is_loading равно true. Метка кнопки также изменяется в зависимости от этого значения.

Когда кнопка нажата, она сначала выполняет функцию, которая была передана через свойства перед выполнением анимации.

Функция changeColor() отвечает за обновление состояния и анимирование цвета фона кнопки. Здесь мы предполагаем, что процесс займет 3000 миллисекунд (3 секунды). Но в реальном сценарии вы не всегда можете знать, сколько времени займет процесс. Что вы можете сделать, так это выполнить анимацию в течение более короткого периода времени, а затем вызвать функцию changeColor() рекурсивно до тех пор, пока процесс не будет выполнен.

Добавьте стили:

Вернемся к странице Кнопки: создайте компонент, отрисуйте три вида кнопок и добавьте их стили.

Страница прогресса

Страница прогресса (src/pages/ProgressPage.js) показывает анимацию прогресса пользователю во время продолжительного процесса. Мы будем использовать наш собственный компонент, вместо встроенного, потому что у React Native нет единого способа реализации анимации индикатора выполнения. Если вам интересно, вот ссылки на два встроенных компонента индикатора выполнения:

Чтобы создать страницу «Прогресс», начните с импорта необходимых нам компонентов:

Мы используем Dimensions, чтобы получить ширину устройства. В итоге мы можем рассчитать ширину, доступную для индикатора выполнения. Чтобы сделать это, мы вычтем сумму левого и правого paddings, которые мы добавим в контейнер, а также левую и правую границы, которые мы добавим в контейнер индикаторов выполнения.

Для того чтобы приведенная выше формула имела смысл, перейдем к стилям:

container имеет padding по 20 с каждой стороны, поэтому мы вычитаем 40 из available_width. У progress_container есть граница шириной 6 с каждой стороны, поэтому мы просто удваиваем ее и вычитаем 12 из ширины прогресс бара.

Создайте компонент и внутри конструктора создайте анимированное значение для сохранения текущих значений анимации для индикатора выполнения.

Я сказал «значения», потому что на этот раз мы будем использовать это одиночное анимированное значение для анимации как ширины, так и цвета фона индикатора выполнения. Все это в действии вы увидите чуть позже.

Помимо этого, вам также необходимо инициализировать текущий прогресс в состоянии.

Внутри функции render(), progress_container действует как контейнер для индикатора выполнения, а <Animated.View> внутри него -как фактический индикатор выполнения, ширина и цвет фона которого будут меняться в зависимости от текущего прогресса. Ниже мы также показываем текущий прогресс в текстовой форме (от 0% до 100%).

Стили для прогресс бара возвращаются функцией getProgressStyles(). Здесь мы используем анимированное значение ранее, чтобы рассчитать ширину и цвет фона. Мы делаем так вместо создания отдельного анимированного значения для каждой анимации, как как мы все равно интерполируем одно и то же значение. Если бы мы использовали два отдельных значения, нам нужно было бы иметь две анимации параллельно, что менее эффективно.

Анимация будет немедленно выполнена сразу после монтирования компонента. Начните с установки начального значения прогресса, а затем добавьте слушателя к текущему значению прогресса. Это позволяет нам обновлять состояние каждый раз, когда изменяется значение прогресса. Мы используем parseInt(), поэтому значение прогресса преобразуется в целое число. После этого мы запускаем анимацию продолжительностью 7000 миллисекунд (7 секунд). Как только это будет сделано, мы изменим текст прогресса на done!

Страница расширения

Страница расширения (src/pages/ExpandPage.js) показывает, как визуально связывать переходные состояния с помощью расширяющихся и сокращающихся движений. Важно показать пользователю, как появился конкретный элемент. Он отвечает на вопросы о том, откуда взялся этот элемент и какова его роль в текущем контексте. Как всегда, начните с импорта необходимых нам вещей:

Внутри constructor() создайте анимированное значение, которое будет сохранять y-позицию в меню. Идея состоит в том, чтобы получить большой бокс, достаточный для того, чтобы содержать все пункты меню.

Первоначально поле будет иметь отрицательное значение для позиции bottom. Это означает, что по умолчанию будет отображаться только верхушка всего окна. Как только пользователь нажимает на меню, весь бокс будет выглядеть так, как если бы он был расширен, когда на самом деле мы только меняем bottom позицию.

Вам может быть интересно, почему мы используем этот подход вместо того, чтобы просто масштабировать поле для размещения всех его дочерних элементов. Это потому, что нам нужно только масштабировать атрибут высоты. Подумайте, что происходит с изображениями, когда вы просто корректируете их высоту или ширину - они выглядят растянутыми. То же самое произойдет с элементами внутри бокса.

Возвращаясь к constructor(), мы также добавляем флаг состояния, который указывает, будет ли в настоящее время меню расширено или нет. Нам это нужно, так как нужно скрыть кнопку для расширения меню, если меню итак уже расширено.

Внутри функции render() укажите, как будет изменена bottom позиция. inputRange равен 0 и 1, а outputRange равен 0 и -300. Поэтому, если y_translate значение 0, ничего не произойдет, потому что эквивалент outputRange равен 0. Но если значение становится равным 1, bottom положение меню переводится на -300 из своего исходного положения.

Обратите внимание на отрицательный знак, потому что, если это будет просто 300, то бокс расширится еще дальше. А если это отрицательное число, произойдет обратное.

Чтобы это имело больше смысла, давайте перейдем к стилям:

Обратите внимание на стиль footer_menu. Его общая height установлена равной 350, а bottom позиция -300, что означает, что по умолчанию отображается только верхняя часть 50. Когда анимация трансляции выполняется для расширения меню, bottom позиция заканчивается значением 0. Почему так? Потому что, если вы все еще помните правила при вычитании отрицательных чисел, два минусовых знака становятся положительными. Таким образом (-300) - (-300) становится (-300) + 300.

Мы все знаем, что происходит при добавлении положительных и отрицательных чисел: они отменяют друг друга. Таким образом, bottom позиция становится равной 0, и отображается все меню.

Возвращаясь к функции render(), у нас есть основное содержимое (body) и меню нижнего колонтитула, которое будет расширяться и сокращаться. Преобразование translateY используется для перевода своего положения по оси Y. Поскольку весь container имеет flex: 10, и body также имеет flex: 10, то  отправная точка находится в самом низу экрана.

Внутри <Animated.View> находятся tip_menu и полное меню. Если меню расширено, мы не хотим, чтобы отображалось меню подсказок, поэтому мы отображаем его только в том случае, если для параметра menu_expanded установлено значение false.

С другой стороны, мы хотим отобразить полное меню, если для параметра menu_expanded будет установлено значение true. Каждая из кнопок вернет меню обратно в исходное положение.

При открытии меню сначала необходимо обновить состояние, чтобы отображались скрытые меню. Только после этого можно выполнить анимацию перевода. Это использует Animated.spring, а не Animated.timing, чтобы добавить немного анимации. Чем выше значение, которое вы придаете friction, тем меньше будет отскок. Помните, что не переусердствуйте с анимацией, потому что вместо того, чтобы помогать пользователю, они могут оказаться раздражающими.

HideMenu() делает противоположное showMenu(), поэтому мы просто отменяем то, что он делает:

Страница привлечения внимания

Последней, но не менее важной является страница привлечения внимания (src/pages/AttentionSeekerPage.js). Я знаю, что этот учебник уже довольно длинный, поэтому, чтобы немного сократить, давайте использовать пакет react-native-animatable для реализации анимаций для этой страницы.

Создайте массив, содержащий тип анимации и цвет фона, который будет использоваться для каждого окна:

Создайте компонент:

Функция render() использует функцию renderBoxes() для создания трех строк, каждая из которых будет отображать по три окна.

Функция renderBoxes() отображает анимированные поля. Она использует начальный индекс, предоставленный в качестве аргумента, для извлечения определенной части массива и визуализации их по отдельности.

Здесь мы используем <Animatable.View> вместо <Animated.View>. Он принимает animation и iterationCount в качестве свойств. animation определяет тип анимации, которую вы хотите выполнить, и iterationCount указывает, сколько раз вы хотите выполнить анимацию. В этом случае мы просто хотим отключить пользователя, пока не нажмем на него.

stopAnimation() просто прекращает анимацию на боксе. Она использует «refs», чтобы однозначно идентифицировать каждый бокс, чтобы их можно было остановить индивидуально.

Наконец, добавьте стили:

Заключение

В этом уроке вы узнали, как реализовать некоторые анимации, обычно используемые в мобильных приложениях. В частности, вы узнали, как реализовать анимацию, обеспечивающую визуальную обратную связь, отображение текущего состояния системы, визуальное связывание состояний перехода и привлечение внимания пользователя.

Как всегда, еще многое предстоит узнать, когда дело касается анимации. Например, мы до сих пор не затронули следующие области:

  • Как выполнять анимацию для конкретных жестов пользователя, таких как перетаскивание, щелканье, сужение и расширение. Например, когда пользователь использует жест расширения, вы должны использовать анимацию масштаба, чтобы показать, как этот элемент становится больше.
  • Как анимировать переход нескольких элементов из одного состояния в другое. Например, при отображении списка фотографий вы можете захотеть выполнить анимацию шага, чтобы задержать показ всех фотографий.
  • Intro-анимация для первых пользователей приложения. В качестве альтернативы можно использовать видео, но это также хорошее место для реализации анимаций.

Возможно, я расскажу о некоторых из этих тем в следующем учебнике. Тем временем, ознакомьтесь с некоторыми из наших других курсов и учебников по React Native!


Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.