【Flutter】Animationの基礎から応用まで ~②複数効果の適用とTweenSequence~
こちらの記事はアニメーションに関するシリーズ記事のvol2となります。
vol1ではアニメーションの基本に触れましたが、次はもう少し複雑なアニメーションを実現してみましょう
TweenSequence
複数効果の適用と複雑なアニメーションには以下の様なものが考えられます
- 単体のWidgetに複数のアニメーション効果を同時に充てる
- 単体のWidgetに同一のアニメーションを複数回充てる
- 単体のWidgetに異なるアニメーションをそれぞれのタイミングで充てる
- 複数のWidgetを連鎖的にアニメーションさせる
- 単体 or 複数のWidgetを別々にアニメーションさせる
1つ1つ見ていきましょう
単体のWidgetに複数のアニメーション効果を同時に充てる
vol1.では単体のWidgetのAlignmentだけをアニメーションさせていましたが、例えば回転をさせながらAlignmentも変えたい場合はどうしたら良いでしょうか?
結論:1つのAnimationControllerから複数のアニメーションを生成し、単体のWidgetに充てる
いきなり結論から言ってしまいましたが、このユースケースはシンプルですね
加えたいアニメーション効果(位置、回転、色、サイズなど)の分、Tween
を用意し、そのTween
とAnimationController
を使ってAnimation
を生成します
以下ではWidgetに位置(Alignment)と回転(rotation)の2つのアニメーション効果を掛けています
サンプルコード
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
late AnimationController controller;
late Tween<Alignment> alignmentTween; // <<< 1つ目のアニメーションのTween
late Tween<double> rotateTween; // <<< 2つ目のアニメーションのTween
late Animation<Alignment> alignmentAnimation; // <<< 1つ目のアニメーション
late Animation<double> rotateAnimation; // <<< 2つ目のアニメーション
void initState() {
controller = AnimationController(duration: Duration(seconds: 3),vsync: this);
alignmentTween = Tween(begin: Alignment.topCenter,end: Alignment.bottomCenter); // <<< 位置のアニメーションの始点と終点を定義
rotateTween = Tween(begin:0, end: 8 * pi); // <<< 回転のアニメーションの始点と終点を定義
alignmentAnimation = controller.drive(alignmentTween); // <<< 位置のアニメーションを生成
rotateAnimation = controller.drive(rotateTween); // <<< 回転のアニメーションを生成
super.initState();
}
void dispose() {
controller.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter app'),
),
body: AnimatedBuilder(
animation: controller,
builder: (context, _){
return Align(
alignment: alignmentAnimation.value, // <<< 位置のアニメーション変化を適用
child: Transform.rotate(
angle: rotateAnimation.value, // <<< 回転のアニメーション変化を適用
child:Text('Hello world!'),
),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
controller.forward();
},
backgroundColor: Colors.yellow[700],
child: Icon(
Icons.bolt,
color: Colors.black,
),
),
);
}
}
TweenSequence
-
単体のWidgetに同一のアニメーションを複数回充てる -次のユースケースでは同じアニメーション効果を複数回変化させながらWidgetに充てていきます
例えば以下の様にで四角の形にWidgetを動かしたい場合、位置を変化させるアニメーションを最初は右、次に下、その次に左、そして上と言うように変化させながら複数回充てる必要があります
このようなケースで使えるのがTweenSequence
です
TweenSequeceクラス
TweenSequence
はその名の通り、変化のSequence(順序)を定義したTween
です
TweenSequence
ではTweenSequenceItem
というどのくらいの間、どういった変化を加えるかを定義したクラスを配列で渡す事で複数回の変化を充てる事が出来ます
tweenSequnece = TweenSequence<T>([
TweenSequenceItem(tween: Tween(),weight:1),
TweenSequenceITem(tween: Tween(),weight:1),
TweenSequenceITem(tween: Tween(),weight:1),
]);
TweenSequence
はTween
と同じ抽象クラスであるAnimatable
クラスを継承しており、要はTween
と同じように扱えます
なので定義したTweenSequence
とAnimationController
を使ってAnimation
を生成する事が出来、このAnimationwをWidgetに適用する事で複数回変化するアニメーションをWidgetにさせる事が出来ます
animation = controller.drive(TweenSequence([]))
TweenSequenceItemクラス
TweenSequence
に複数渡すことが出来るTweenSequenceItem
では、どういった変化をtween
に、どのくらいの間をweight
に定義します
TweenSequenceItem(
tween: Tween(
begin: const Alignment(-1, 3),
end: Alignment.topLeft,
),
weight: 2,
)
weight
は時間の比率を表します。AnimationController
に定義された時間(Duration
)の間、どれくらいの間でtween
の変化を充てるかを決めます。
Flexible
クラスのflex
と同じ様に、渡されているTweenSequenceItem
のweight
の合計でDuration
を割った分の時間がそのTweenSequenceItem
の持ち時間になります
サンプルコード
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
late AnimationController controller;
late TweenSequence<Alignment> tweenSequence; // <<< Tweenではなく、TweenSequenceとして定義
late Animation<Alignment> animation;
void initState() {
controller = AnimationController(duration: Duration(seconds: 4),vsync: this);
tweenSequence = TweenSequence<Alignment>([ // <<< 複数のTweenSequenceItemを受け取る
TweenSequenceItem(
tween: Tween(begin: Alignment.topLeft,end: Alignment.topRight),
weight: 1 // <<< この例ではDurationが4秒なので、weight:1 = 1秒となる
),
TweenSequenceItem(
tween: Tween(begin: Alignment.topRight,end: Alignment.bottomRight),
weight: 1
),
TweenSequenceItem(
tween: Tween(begin: Alignment.bottomRight,end: Alignment.bottomLeft),
weight: 1
),
TweenSequenceItem(
tween: Tween(begin: Alignment.bottomLeft,end: Alignment.topLeft),
weight: 1
),
]);
animation = controller.drive(tweenSequence); // <<< TweenとAnimationControllerでAnimationを作るのと同じ様にしてAnimationを生成
super.initState();
}
void dispose() {
controller.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter app'),
),
body: AnimatedBuilder(
animation: controller,
builder: (context, _){
return Align(
alignment: animation.value, // <<< animationを適用するだけ
child: Text('Hello world!')
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
controller.forward();
},
backgroundColor: Colors.yellow[700],
child: Icon(
Icons.bolt,
color: Colors.black,
),
),
);
}
}
サンプルコード
続く
残りの3つのユースケースは次の記事に続きます
- 単体のWidgetに異なるアニメーションをそれぞれのタイミングで充てる
- 複数のWidgetを連鎖的にアニメーションさせる
- 単体 or 複数のWidgetを別々にアニメーションさせる
Discussion