はじめに
ぐるなびでサーバーサイドのエンジニアとして活動している前田です。
https://developers.gnavi.co.jp/archive/category/%E3%80%90%E5%9F%B7%E7%AD%86%E8%80%85%E3%80%91%E5%89%8D%E7%94%B0
Flutterがバージョン1.0としてメジャーリリースされましたね。
前からFlutterに興味があったのと、アニメーションの実装に興味があったので、
Flutterでアニメーションを実装してみました。
使ったライブラリ
flutter_sequence_animationというライブラリを使います。
https://pub.dartlang.org/packages/flutter_sequence_animation
実装
とりあえずなれるために実装した後、
ログインボタン押下→画面遷移のアニメーションを実装してみます。
クロスプラットフォームということなので、AndroidとiOS両方のシュミレータで実行してみます。
とりあえず実装してみた
import 'package:flutter/material.dart';
import 'package:flutter_sequence_animation/flutter_sequence_animation.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {
AnimationController controller;
SequenceAnimation sequenceAnimation;
@override
void initState() {
super.initState();
controller = new AnimationController(vsync: this);
sequenceAnimation = SequenceAnimationBuilder()
.addAnimatable(
animatable: new EdgeInsetsTween(
begin: const EdgeInsets.only(bottom: 40.0),
end: const EdgeInsets.only(bottom: 200.0),
),
from: Duration.zero,
to: const Duration(milliseconds: 1000),
curve: Curves.fastOutSlowIn,
tag: "padding")
.addAnimatable(
animatable: new Tween<double>(begin: 0.0, end: 1.0),
from: Duration.zero,
to: const Duration(milliseconds: 1000),
curve: Curves.fastOutSlowIn,
tag: "opacity")
.addAnimatable(
animatable: Tween<double>(begin: 10.0, end: 200.0),
from: const Duration(milliseconds: 1000),
to: const Duration(milliseconds: 2000),
curve: Curves.elasticOut,
tag: 'width')
.addAnimatable(
animatable: Tween<double>(begin: 10.0, end: 200.0),
from: const Duration(milliseconds: 2000),
to: const Duration(milliseconds: 3000),
curve: Curves.elasticOut,
tag: 'height')
.addAnimatable(
animatable: ColorTween(begin: Colors.blue, end: Colors.red),
from: const Duration(milliseconds: 3100),
to: const Duration(milliseconds: 5000),
curve: Curves.bounceOut,
tag: 'color')
.addAnimatable(
animatable: new BorderRadiusTween(
begin: new BorderRadius.circular(4.0),
end: new BorderRadius.circular(100.0),
),
from: const Duration(milliseconds: 3100),
to: const Duration(milliseconds: 5000),
curve: Curves.bounceOut,
tag: "borderRadius")
.animate(controller);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
Widget _buildAnimation(BuildContext context, Widget child) {
return new Container(
padding: sequenceAnimation["padding"].value,
alignment: Alignment.bottomCenter,
child: new Opacity(
opacity: sequenceAnimation["opacity"].value,
child: new Container(
width: sequenceAnimation["width"].value,
height: sequenceAnimation["height"].value,
decoration: new BoxDecoration(
borderRadius: sequenceAnimation["borderRadius"].value,
color: sequenceAnimation["color"].value,
),
),
),
);
}
Future<Null> _playAnimation() async {
try {
await controller.forward().orCancel;
await controller.reverse().orCancel;
} on TickerCanceled {
// the animation got canceled, probably because we were disposed
}
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Staggered Animation"),
),
body: new GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
_playAnimation();
},
child: new Center(
child: new Container(
child: new AnimatedBuilder(
animation: controller, builder: _buildAnimation),
),
),
),
);
}
}
![flutter_animation_1.gif](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.amazonaws.com%2F0%2F311078%2Fc3b325e3-6ca7-6810-e71a-e5f8457fdd89.gif?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=457e3751e224c4e1b756f40fe71dd6c8)
ボタン押下時から画面遷移するまでのアニメーション
import 'package:flutter/material.dart';
import 'package:flutter_sequence_animation/flutter_sequence_animation.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {
AnimationController controller;
SequenceAnimation sequenceAnimation;
@override
void initState() {
super.initState();
controller = new AnimationController(vsync: this);
sequenceAnimation = SequenceAnimationBuilder()
.addAnimatable(
animatable: new EdgeInsetsTween(
begin: const EdgeInsets.only(bottom: 80.0),
end: const EdgeInsets.only(bottom: 400.0),
),
from: Duration.zero,
to: const Duration(milliseconds: 3000),
curve: Curves.elasticInOut,
tag: "padding")
.addAnimatable(
animatable: new BorderRadiusTween(
begin: new BorderRadius.circular(4.0),
end: new BorderRadius.circular(100.0),
),
from: Duration.zero,
to: const Duration(milliseconds: 1000),
curve: Curves.ease,
tag: "borderRadius")
.addAnimatable(
animatable: Tween<double>(begin: 200.0, end: 40.0),
from: Duration.zero,
to: const Duration(milliseconds: 1000),
curve: Curves.ease,
tag: 'width')
.addAnimatable(
animatable: ColorTween(begin: Colors.pink, end: Colors.blue),
from: const Duration(milliseconds: 1000),
to: const Duration(milliseconds: 3000),
curve: Curves.easeInOut,
tag: 'color')
.addAnimatable(
animatable: new EdgeInsetsTween(
begin: const EdgeInsets.only(bottom: 400.0),
end: const EdgeInsets.only(bottom: 0.0),
),
from: const Duration(milliseconds: 3000),
to: const Duration(milliseconds: 4000),
curve: Curves.ease,
tag: "padding")
.addAnimatable(
animatable: new Tween<double>(begin: 1.0, end: 0.0),
from: const Duration(milliseconds: 4000),
to: const Duration(milliseconds: 5500),
curve: Curves.ease,
tag: "opacity")
.addAnimatable(
animatable: new BorderRadiusTween(
begin: new BorderRadius.circular(100.0),
end: new BorderRadius.circular(0.0),
),
from: const Duration(milliseconds: 4000),
to: const Duration(milliseconds: 4100),
curve: Curves.ease,
tag: "borderRadius")
.addAnimatable(
animatable: Tween<double>(begin: 40.0, end: 1000.0),
from: const Duration(milliseconds: 4000),
to: const Duration(milliseconds: 5000),
curve: Curves.ease,
tag: 'height')
.addAnimatable(
animatable: Tween<double>(begin: 40.0, end: 1000.0),
from: const Duration(milliseconds: 4000),
to: const Duration(milliseconds: 5000),
curve: Curves.ease,
tag: 'width')
.animate(controller);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
Widget _buildAnimation(BuildContext context, Widget child) {
return new Container(
padding: sequenceAnimation["padding"].value,
alignment: Alignment.bottomCenter,
child: new Opacity(
opacity: sequenceAnimation["opacity"].value,
child: new Container(
width: sequenceAnimation["width"].value,
height: sequenceAnimation["height"].value,
decoration: new BoxDecoration(
borderRadius: sequenceAnimation["borderRadius"].value,
color: sequenceAnimation["color"].value,
),
),
),
);
}
Future<Null> _playAnimation() async {
try {
await controller.forward().orCancel;
} on TickerCanceled {
// the animation got canceled, probably because we were disposed
}
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Flutter Demo"),
),
body: new GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
_playAnimation();
},
child: new Center(
child: new Container(
child: new AnimatedBuilder(
animation: controller, builder: _buildAnimation),
),
),
),
);
}
}
![flutter_animation_2.gif](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.amazonaws.com%2F0%2F311078%2F6098e3d1-92d0-e3fa-237f-01215918c850.gif?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=b3304e77f458e6b5d6af74bbf38b3782)