Прикольные анимации для Flutter-приложений

Olga Sayfudinova
NOP::Nuances of Programming
6 min readJun 27, 2020

Я покажу вам 6 прикольных анимационных эффектов, которые можно попробовать в своих приложениях. Добавлять анимацию с Flutter — одно удовольствие. И делать это можно по-разному. Например, можно скачать пакет с dart pub или воспользоваться виджетом AnimatedBuilder, который позволяет настроить каждую деталь анимации.

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

1. AnimationController — задает длительность анимации;

2. Animation — определяет тип и стиль анимации.

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

Готовы начать? Поехали.

Базовый макет

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

Базовый макет

В процессе работы мы будем пользоваться разными изображениями. Вы можете добавить свои изображения или другие виджеты.

Триггером для анимации у нас служит плавающая кнопка снизу. Я буду активировать анимацию через вызов _controller.forward().

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

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

import 'package:flutter/material.dart';

class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin{

//*-----определяем Animation и AnimationController-----*
AnimationController _controller;
Animation _myAnimation;

//*-----запускаем Animation и AnimationController-----*
@override
void initState() {
super.initState();

}

@override
void dispose() {
super.dispose();
//-disposing the animation controller-
_controller.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Flutter Animations"),
),

body: Center(
child:
Container(
width: 250,
height: 250,
decoration: BoxDecoration(
image: new DecorationImage(
image: new AssetImage(
'assets/images/sample-image.png',
)
)
),
)
),

floatingActionButtonLocation:
FloatingActionButtonLocation.centerFloat,

floatingActionButton:
FloatingActionButton(
child: Icon(Icons.play_arrow),
onPressed: (){
//*------включаем анимацию-----*
_controller.forward();
},
),
);
}
}

01. Появление/исчезновение

Fade-эффект

Воспользуйтесь кодом ниже для определения анимации. Лучше всего это делать через метод initState. Теперь он будет запускаться только при отображении StatefulWidget.

AnimationController _controller;
Animation _myAnimation;

void initState() {
// TODO: implement initState
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 1200),

);
_myAnimation = CurvedAnimation(parent: _controller, curve: Curves.easeIn);

}

Во Flutter уже есть встроенный виджет под названием FadeTransition. Таким образом, мы можем обернуть наш виджет-контейнер в FadeTransition, а затем настроить динамические значения для непрозрачности.

body: Center(
child:
FadeTransition(
opacity: _myAnimation,
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
image: new DecorationImage(
image: new AssetImage(
'assets/images/ghost.png',
)
)
),
),
)
),

02. Изменение размера/пульсация

Pulse-анимация

Это тоже простая разновидность анимации. Для начала, зададим нужные параметры.

AnimationController _controller;
Animation<Size> _myAnimation;

@override
void initState() {
// TODO: implement initState
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 1000),

);
_myAnimation = Tween<Size>(
begin: Size(100, 100),
end: Size(120, 120)
).animate(
CurvedAnimation(parent: _controller, curve: Curves.bounceIn)
);

_controller.addStatusListener((AnimationStatus status) {
if (status == AnimationStatus.completed) {
_controller.repeat();
}
});
}

Возможно, сейчас вы заметите что-то новое. Например, мы используем _controller.addStatusListener() каждый раз, когда хотим повторить анимацию. После определения анимации в AnimationController она готова к использованию в виджете-контейнере.

body: Center(
child:
AnimatedBuilder(animation: _myAnimation,
builder: (ctx, ch) => Container(
width: _myAnimation.value.width,
height: _myAnimation.value.height,

decoration: BoxDecoration(
image: new DecorationImage(
image: new AssetImage(
'assets/images/heart.png',
)
)
),
)
)
),

03. Скольжение

Slide-анимация

Slide-анимация — это еще одна встроенная функция Flutter. Вот, как ее можно определить.

AnimationController _controller;
Animation<Offset> _myAnimation;

@override
void initState() {
// TODO: implement initState
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);

_myAnimation = Tween<Offset>(
begin: Offset.zero,
end: const Offset(1.5, 0.0),

).animate(CurvedAnimation(
parent: _controller,
curve: Curves.elasticIn,
));
}

Чтобы воспользоваться анимацией, оберните ее в виджет SlideTransition. Проще простого, не так ли?

body: Center(
child:
SlideTransition(
position: _myAnimation,
child: const Padding(
padding: EdgeInsets.all(8.0),
child: FlutterLogo(size: 150.0),
),
),
),

04. Прыгающая анимация

Bounce-анимация

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

AnimationController _controller;
Animation<double> _slideAnimation;

@override
void initState() {
// TODO: implement initState
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 1000),
);

_slideAnimation = Tween(begin: 200.0, end: 120.0).animate(
CurvedAnimation(
parent: _controller,
curve: Interval(0.0, 1.0, curve: Curves.elasticIn),
),
)..addStatusListener((AnimationStatus status) {
if (status == AnimationStatus.completed) {
_controller.repeat(reverse: true);
}
});
}

Будьте осторожны — мы играем с огнем. Если я выставлю граничное значение 0.0, то появится ошибка. Для тех, кто уже работал с Flutter, это совсем не новость. Да, перед вами — ошибка переполнения, она же — overflow error. Мы не можем быть уверенными в том, что значения в этих границах будут меняться. Особенно в случае, если эффект анимации (эластичный) выйдет за пределы границ. Запомните, что отступы не могут иметь отрицательного значения.

Теперь, когда мы настроили все необходимые параметры, самое время воспользоваться анимацией.

body:
AnimatedBuilder(animation: _slideAnimation,
builder: (ctx, ch) => Container(
width: 100,
height: 100,
margin: EdgeInsets.only(
top: _slideAnimation.value,
left: 125
),
decoration: BoxDecoration(
image: new DecorationImage(
image: new AssetImage(
'assets/images/bounce-ball.png',
)
)
),
)
),

05. 3D-переворот

Flip-анимация

Flip-анимация — это простой и прикольный эффект, который легко добавить в приложение. (Одна из моих любимых анимаций). Давайте посмотрим, как все реализовать.

AnimationController _controller;
Animation _myAnimation;
AnimationStatus _animationStatus = AnimationStatus.dismissed;

@override
void initState() {

super.initState();
_controller =
AnimationController(vsync: this, duration: Duration(seconds: 1));
_myAnimation = Tween(end: 1.0, begin: 0.0).animate(_controller)
..addListener(() {
setState(() {});
})
..addStatusListener((status) {
_animationStatus = status;
});
}

Здесь появляется кое-что новенькое — AnimationStatus. Им я пользовался для реализации прямых и обратных функций. Этот дополнительный виджет помогает мне получить текущий статус анимации.

Тут я воспользовался простым контейнером, чтобы показать flip-анимацию в действии.

body: Center(
child: Transform(
alignment: FractionalOffset.center,
transform: Matrix4.identity()
..setEntry(3, 2, 0.002)
..rotateX(pi*(_myAnimation.value)),
child: Container(
color: Colors.blueAccent,
width: 200,
height: 200,
child: Icon(
Icons.accessibility_new,
color: Colors.white,
size: 50,
),
),
)
),

06. Вращение с падением

Hinge-анимация

Перейдем к более сложным примерам. Здесь я объединил сразу несколько эффектов. Мы называем это последовательностью анимации. Посмотрите, как можно определить анимацию врещения.

AnimationController _controller;

Animation _rotateAnimation;
Animation<double> _slideAnimation;
Animation<double> _opacityAnimation;

@override
void initState() {
// TODO: implement initState
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 3000),
);

_rotateAnimation = Tween(end: 0.15, begin: 0.0)
.animate(
CurvedAnimation(
parent: _controller,
curve: Interval(0.0, 0.5, curve: Curves.bounceInOut),
),
);

_slideAnimation = Tween(begin: 100.0, end: 600.0).animate(
CurvedAnimation(
parent: _controller,
curve: Interval(0.5, 1.0, curve: Curves.fastOutSlowIn),
),
);

_opacityAnimation = Tween(begin: 1.0, end: 0.0).animate(
CurvedAnimation(
parent: _controller,
curve: Interval(0.5, 1.0, curve: Curves.fastOutSlowIn),
),
);

}

Обратите внимание, что при определении анимации мы пользуемся 3 отдельными виджетами Animation. И для всех них берется один AnimationController. А еще я передавал разные значения для каждого виджета Interval. Именно так и задается последовательность анимации.

Теперь давайте посмотрим, как работает последовательность анимации:

body:
AnimatedBuilder(
animation: _slideAnimation,
builder: (ctx, ch) => Container(
width: 200,
height: 100,
padding: EdgeInsets.all(0),
margin: EdgeInsets.only(
left: 75,
top: _slideAnimation.value,
),
child: RotationTransition(
turns: _rotateAnimation,
child: Center(
child: Text('Animation', style: TextStyle(
fontSize: 40,
fontWeight: FontWeight.bold,
color: Color.fromRGBO(0, 0, 128, _opacityAnimation.value)
),),
),
),
),
),

Все правильно! Нам нужен еще один виджет –AnimatedBuilder. Затем мы можем поменять значения контейнера. В этом примере я изменял отступы (для смены положения объекта) и непрозрачность.

Примеры анимации можно посмотреть на Github

Читайте также:

Читайте нас в телеграмме, vk и Яндекс.Дзен

--

--