はじめに
Flutterでは、アニメーションを実装するためにTweenやAnimationControllerなどの便利なクラスが提供されています。今回は、TweenSequenceとuseAnimationControllerを組み合わせて、通知アニメーションを実装してみました。
開発環境
- Flutter: 3.14.0
- Dart: 3.2.0
パッケージのインストール
flutter_hooksとhooks_riverpodというパッケージを利用します。
flutter_hooks: ^0.20.5
hooks_riverpod: ^2.5.1
useAnimationControllerとは
useAnimationControllerは、FlutterHooksパッケージに含まれるフックの一つです。これを使用することで、アニメーションの開始、停止、逆再生など簡単に操作することができます。
useAnimationControllerの使い方
useAnimationControllerの使い方を簡単に説明します。
以下のようにしてuseAnimationControllerを定義することができます。duration
にはアニメーション全体の所要時間を設定します。
final animationController = useAnimationController(duration: const Duration(seconds: 2));
今回は以下2つのメソッドを使用しました。
メソッド | 用途 |
---|---|
animationController.forward(); |
アニメーションの開始 |
animationController.reset() |
アニメーションのリセット |
TweenSequenceとは
TweenSequence
は、Tweenのリストを使用して、時間にそってアニメーションの値(tween.value)を変化させることができるクラスです。今回実装する通知アニメーションのように、複数の段階で変化するアニメーションを実装する場合に活躍します。
TweenSequenceの使い方
TweenSequenceはTweenSequenceItemのリストを持ちます。各TweenSequenceItemにはtween
とweight
を指定します。
- tween (begine: 始点, end: 終点)
- weight (そのTweenがアニメーションの進行に占める時間の割合)
例えば、以下のコードではアニメーション全体の時間が5秒なので
5秒の40% = 2秒, 20% = 1秒より以下の表のように変化します。
経過時間 | tween.value(一次関数的に変化) |
---|---|
0~2秒 | 0→100に変化 |
2~3秒 | 100で一定 |
3~5秒 | 100→0に変化 |
final animationController =
useAnimationController(duration: const Duration(seconds: 5));
final tween = TweenSequence([
TweenSequenceItem(
tween: Tween(
begin: 0,
end: 100,
),
weight: 40,
),
TweenSequenceItem(
tween: ConstantTween(100),
weight: 20,
),
TweenSequenceItem(
tween: Tween(
begin: 100,
end: 0,
),
weight: 40,
),
]).animate(animationController);
Widgetの実装
簡単な通知のWidgetを作成しました。
Widget buildNotificationWidget(context) {
return SafeArea(
child: Material(
type: MaterialType.transparency,
child: Container(
height: 100,
width: MediaQuery.of(context).size.width,
color: Colors.green,
padding: const EdgeInsets.only(top: 50),
child: const Center(
child: Text('お知らせ'),
),
),
),
);
}
}
AnimationBuilder
を使用して、アニメーションが更新されるたびにウィジェットそ再構築し、アニメーションの値に応じてウィジェットを配置します。
return AnimatedBuilder(
animation: animationController,
builder: (context, _) {
return Stack(
children: [
Positioned(
top: tween.value,
child: buildNotificationWidget(context),
),
// 他のウィジェットの配置
],
);
},
);
コード全文
これらを組み合わせることで通知アニメーションのような複雑なアニメーションを構築することが可能になります。以下のコードではボタンが押された時に通知アニメーションが開始するようになっています。
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
class NotificationWidget extends HookConsumerWidget {
const NotificationWidget({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
// ステータスバーの高さ + ウィジェットの高さ + 上マージン + 影を考慮
final movedPosition = -MediaQuery.of(context).padding.top;
final initialPosition = -(MediaQuery.of(context).padding.top + 52 + 8);
final animationController =
useAnimationController(duration: const Duration(seconds: 4));
final tween = TweenSequence([
TweenSequenceItem(
tween: Tween(
begin: initialPosition,
end: movedPosition,
),
weight: 0.5,
),
TweenSequenceItem(
tween: ConstantTween(movedPosition),
weight: 5,
),
TweenSequenceItem(
tween: Tween(
begin: movedPosition,
end: initialPosition,
),
weight: 0.5,
),
]).animate(animationController);
animationController.addStatusListener((status) {
if (status == AnimationStatus.completed) {
animationController.reset();
}
});
return AnimatedBuilder(
animation: animationController,
builder: (context, _) {
return Positioned(
top: tween.value,
child: buildNotificationWidget(context),
);
},
);
}
Widget buildNotificationWidget(context) {
return SafeArea(
child: Material(
type: MaterialType.transparency,
child: Container(
height: 100,
width: MediaQuery.of(context).size.width,
color: Colors.green, // 後で直す
),
),
);
}
}
最後に
TweenSequenceとuseAnimationControllerを使えばどんな複雑なアニメーションでも作れる気がしてきてますね。ワクワクしました。