Привет! Меня зовут Никита Грибков, я Flutter-разработчик в AGIMA. Расскажу вам про возможности Rive — фреймворка, который использует векторную графику для создания анимации во Flutter-приложениях. Эта статья выросла из небольшого поста на Хабре, в котором я коротко описал опыт работы над кнопкой для Bottom Bar в своем пет-проекте. Здесь же я уже подробно опишу, как анимировать элементы и чем вообще хорош Rive.
Почему Rive
Когда я впервые столкнулся с необходимостью добавить анимацию во Flutter-приложение, выбор, само собой, пал на Lottie. Это вид анимации, который тогда пользовался популярностью. И хотя внедрить его в проект было непросто, кастомный лоадер был установлен. И надо отдать ему должное — он отлично работал.
Однако вскоре алгоритмы Google подкинули мне информацию о RIve. Это еще один формат анимации, который, как оказалось, намного больше подходил под наши бизнес-задачи. И самые убедительные аргументы тому мы нашли прямо в документации Rive:
Размер файла: 181,7 КБ (без сжатия) Память графического процессора: 149–190 МБ Куча JS: 16,9 МБ ЦП: 91,8% | Размер файла: 18 КБ (без сжатия) Память графического процессора: 2,6 МБ Куча JS: 7,3 МБ ЦП: 31,8% |
Как видим, создатели Rive позаботились о том, чтобы анимация была легче и быстрее. Мы с командой прикрутили Rive-лоадер, и вскоре поняли, что управлять анимацией стало в разы проще, а User-flow стал удобнее. Ниже рассказываю про основные инструменты Rive, а в конце объясню, в каких случаях его лучше всего применять.
Преимущества Rive
При разработке Flutter-приложений вообще используют много типов анимации, мы уже писали об этом в отдельной статье. Но на мой взгляд, Rive превосходит большинство из них. Во-первых, мне нравится встроенный UI-интерфейс. Во-вторых, в Rive есть раздел Community, где авторы выкладывают бесплатные анимации.
А в-третьих, — и это главное преимущество — в Rive есть State Machine. Это визуальный способ связать анимацию воедино и определить логику, которая управляет переходами. State Machine позволяет создавать интерактивную графику движения, готовую к внедрению в ваш продукт, приложение, игру или веб-сайт.
State Machine включает несколько уровней:
Graph (График) — это пространство, в котором мы будем добавлять состояния и соединять переходы. Он появляется вместо временной шкалы, когда машина состояний выбрана в списке анимаций.
State — это просто анимации временной шкалы, которые могут воспроизводиться в нашей машине состояний. Как правило, они представляют собой некоторое состояние, в котором находится ваш анимированный контент.
Transaction — переходы представляют собой логическую карту для State Machine. Существует ряд соображений и настраиваемых свойств для переходов.
Inputs — это договор между дизайнерами и разработчиками. Как дизайнеры, мы используем входы как способ управления переходами в нашей машине состояний, назначая их в качестве условий. Разработчики связываются с входами во время выполнения и определяют условия с помощью кода, который может изменить эти входы.
Layers — слой State Machine, который позволяет воспроизводить одну анимацию за раз. По этой причине вы можете создать несколько слоев, если хотите смешать несколько анимаций или добавить дополнительные взаимодействия в State Machine.
Инструменты Rive
Для работы с Rive Animation лучше использовать их UI-интерфейс, в котором и происходит создание и настройка самой анимации. Анимация состоит из нескольких составляющих. Они больше знакомы дизайнерам, но, если кратко, вот некоторые из них.
Artboard: слой, который является холстом анимации — на нем располагаются остальные элементы. В нем можно задать цвет и размер фона. В каждом файле Rive есть хотя бы один такой.
Group: необязательный элемент, но он отлично подходит, чтобы объединить элементы в группы для любого объекта, изменение которого может потребоваться в дальнейшем.
Shape: Rive позволяет создавать, редактировать и анимировать векторную графику, используя процедурные или пользовательские фигуры. Из них чаще всего и состоит весь интерфейс. Также есть крутой инструмент Pen.
Pen: это инструмент, который позволяет делать очень сложные кастомные фигуры.
Три вида операторов, которые используются для триггера анимации
Number
Это каунтер, по достижении которого будет осуществляться анимация. К этому оператору можно привязать, например, анимацию загрузки страницы или количество звезд в отзыве.
Допустим, у нас есть анимация загрузки, которая активируется, когда счетчик достигает определенного значения. Для начала создаем контроллер и каунтер:
RiveAnimationController _controller;
int _counter = 0;
Затем создаем метод, в котором будем обрабатывать состояние в зависимости от значение _counter.
void _incrementCounter() {
setState(() {
_counter++;
if (_counter >= 10) {
_controller = SimpleAnimation('loading');
} else {
_controller = SimpleAnimation('idle');
}
});
}
После этого создаем контейнер с нашим ассетом и добавляем ElevatedButton с методов _incrementCounter.
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 200,
height: 200,
child: RiveAnimation.asset(
'assets/loading_animation.riv',
controllers: [_controller],
fit: BoxFit.cover,
),
),
ElevatedButton(
onPressed: _incrementCounter,
child: Text('Increment Counter: $_counter'),
),
],
),
Bool
Каждый разработчик знает об этом операторе. Он может быть True или False. В зависимости от значения логического оператора обрабатывает логику анимации.
Для Bool-оператора всё тривиально. Думаю, комментарии тут излишни. Так бы выглядел код всё с тем же лоадингом:
RiveAnimationController _controller;
bool _isPlaying = false;
void _toggleAnimation() {
setState(() {
_isPlaying = !_isPlaying;
if (_isPlaying) {
_controller = SimpleAnimation('play');
} else {
_controller = SimpleAnimation('idle');
}
});
}
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 200,
height: 200,
child: RiveAnimation.asset(
'assets/toggle_animation.riv',
controllers: [_controller],
fit: BoxFit.cover,
),
),
ElevatedButton(
onPressed: _toggleAnimation,
child: Text(_isPlaying ? 'Pause' : 'Play'),
),
],
),
Trigger
Триггеры аналогичны логическим значениям, но могут стать истинными только на короткое время.
С триггером мы получаем чуть больше кода, так как триггеры могут быть заданы разные и надо их обрабатывать из самого ассета.
Artboard _artboard;
RiveAnimationController _controller;
void _triggerAnimation() {
if (_controller != null) {
_artboard.artboard.removeController(_controller);
}
_controller = SimpleAnimation('trigger');
_artboard.artboard.addController(_controller);
}
@override
void initState() {
super.initState();
_rootBundle();
}
void _rootBundle() {
rootBundle.load('assets/trigger_animation.riv').then((data) {
final file = RiveFile.import(data);
final artboard = file.mainArtboard..addController(SimpleAnimation('idle'));
setState(() => _artboard = artboard);
});
}
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_artboard != null ?
Rive(
artboard: _artboard,
fit: BoxFit.cover,
) :
SizedBox(),
ElevatedButton(
onPressed: _triggerAnimation,
child: Text('Trigger Animation'),
),
],
),
Вывод
В итоге Rive-анимацией мы закрыли все кейсы, когда в приложении заказчика нужно было проигрывать анимацию. Сейчас Rive в наших проектах используется повсеместно как основной инструмент анимации. Хотя и про Lottie мы иногда вспоминаем.
Rive отлично подходит, если ваша анимация должна быть интерактивной или требует сложной логики и взаимодействия.
Lottie лучше использовать, если вам нужны простые и легкие анимации, которые легко интегрировать в многоплатформенные приложения.
Если у вас остались вопросы про Rive — буду рад ответить в комментариях. Также подписывайтесь на канал нашего руководителя направления мобильной разработки Саши Ворожищева — у него много про Flutter.