flutter_hooksでアニメーションを作ってみた!
HookWidgetを使ってみたい!
Flutterは簡単にアニメーションをつけることができる。簡単と言っても知識は必要。前回アニメーションの勉強をする記事を書いて、それを参考に記事を書いたのと、StatefulWidget
とHookWidget
では使える機能が異なるので、そこの解説をしたいと思いました。
今回はこんなものを作ってみた!
これがサンプルコード
今回は作り方の解説より、アニメーションやってみたよ〜って記事ですので、全部内容は書きません。サンプルコードを参考にしてみてください。
前回書いたアニメーションの記事:
Flutter Genを使って画像とフォントの設定はしてます
補足情報
StatefulWidget
でないとTickerProviderStateMixin
クラスが使えないので、HookWidget
で同じような機能を使うときは、useSingleTickerProvider
を使います。他に違いがあるとしたら、flutter_hooks
だと状態の破棄は勝手にやってくれるので、dispose
メソッドを書かないところでしょうか...
これが、flutter_hooksで使えるアニメーションの機能たち。リファレンス見てもさっぱりわからん???
作ったもの
ふわ〜っと文字が出てきて、ボタンに変わって次のページは画面遷移するアニメーションのDEMOアプリです。
🔚NextPageクラス
これは次のページで表示する画像とテキストをラップしてるコンテナがくるくる回るアニメーションです。今日までこれ仕組みが理解できなかった😅
これが良くある方法:
StatefulWidgetの場合
import 'package:flutter/material.dart';
import 'dart:math' as math;
class AnimatedBuilderExample extends StatefulWidget {
const AnimatedBuilderExample({super.key});
State<AnimatedBuilderExample> createState() => _AnimatedBuilderExampleState();
}
///AnimationController は `vsync:this` で作成できます。
/// TickerProviderStateMixin.
class _AnimatedBuilderExampleState extends State<AnimatedBuilderExample>
with TickerProviderStateMixin {
// lateをAnimationControllerにつけると、初期化を遅らせることができる。..repeat()で繰り返しアニメーションを行う。
late final AnimationController _controller = AnimationController(
duration: const Duration(seconds: 10),
vsync: this,
)..repeat();
// AnimationControllerを破棄する必要があるので、dispose()をオーバーライドする。
void dispose() {
_controller.dispose();
super.dispose();
}
Widget build(BuildContext context) {
// AnimatedBuilderは、アニメーションの値を受け取り、ウィジェットを構築する。
return Scaffold(
appBar: AppBar(
title: const Text('AnimatedBuilder Example'),
),
body: Center(
child: AnimatedBuilder(
animation: _controller, // AnimationControllerを渡す。
child: Container(
width: 200.0,
height: 200.0,
color: Colors.green,
child: const Center(
child: Text('くるくる回っちゃうもんね〜'),
),
),
builder: (BuildContext context, Widget? child) {
// Transform.rotateで回転させる。
return Transform.rotate(
angle: _controller.value * 2.0 * math.pi,
child: child,
);
},
),
),
);
}
}
HookWidgetの場合:
Hookの場合
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_animaition/gen/assets.gen.dart';
class NextPage extends HookWidget {
const NextPage({super.key});
Widget build(BuildContext context) {
// 画像がくるくる回るアニメーションを制御するためのuseAnimationController
final controller = useAnimationController(
duration: const Duration(seconds: 3),
vsync: useSingleTickerProvider(),
)..repeat();
return Scaffold(
appBar: AppBar(),
body: Center(
child: AnimatedBuilder(
animation: controller, // AnimationControllerを渡す。
child: Container(
width: 200.0,
height: 200.0,
color: Colors.green,
child: Center(
child: Column(
children: [
Assets.images.orechan.image(
width: 100,
height: 100,
),
const Text('くるくる回っちゃうもんね〜'),
],
),
),
),
builder: (BuildContext context, Widget? child) {
// Transform.rotateで回転させる。
return Transform.rotate(
angle: controller.value * 2.0 * math.pi,
child: child,
);
},
),
),
);
}
}
🔜FirstPageクラス
最初のページは、画面が呼ばれるとおしゃれな文字ってわけではないですがアニメーションが表示されて、次のページへ画面遷移するボタンに変わります。StatefulWidget
のときは、initState
とsetState
を使ってますが、HookWidget
のときは、useState
で状態の管理をして、ページが呼ばれたら、useEffect
でアニメーションを実行するメソッドを実行しています。
StatefulWidgetの場合
import 'package:flutter/material.dart';
class AnimatedCrossFadeExample extends StatefulWidget {
const AnimatedCrossFadeExample({Key? key}) : super(key: key);
_AnimatedCrossFadeExampleState createState() =>
_AnimatedCrossFadeExampleState();
}
class _AnimatedCrossFadeExampleState extends State<AnimatedCrossFadeExample> {
// 初期値がtrueなので、最初はfirstChildが表示される
bool _first = true;
void initState() {
super.initState();
_loadAnimation();
}
Future<void> _loadAnimation() async {
// 画面が呼ばれたときにアニメーションを開始
Future.delayed(Duration.zero, () {
setState(() {
_first = !_first;
});
});
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('AnimatedCrossFade Example'),
),
body: Center(
child: AnimatedCrossFade(
duration: const Duration(seconds: 3), // 3秒かけてアニメーションする
// firstChildは最初に表示されるWidget
firstChild: const FlutterLogo(
style: FlutterLogoStyle.horizontal, size: 100.0),
// secondChildはfirstChildが消えた後に表示されるWidget
secondChild:
const FlutterLogo(style: FlutterLogoStyle.stacked, size: 100.0),
crossFadeState:
_first ? CrossFadeState.showFirst : CrossFadeState.showSecond,
),
),
);
}
}
Hookの場合
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_animaition/gen/fonts.gen.dart';
import 'package:hooks_animaition/next_page.dart';
class FirstPage extends HookWidget {
const FirstPage({super.key});
Widget build(BuildContext context) {
// bool型の値を保持する変数を宣言
final firstBool = useState<bool>(true);
// 3秒後にfirstBoolの値を反転させる関数
Future<void> loadAnimation() async {
await Future.delayed(Duration.zero, () {
firstBool.value = !firstBool.value;
});
firstBool.value = false;
}
// 画面が表示された時にloadAnimationを実行する
useEffect(() {
loadAnimation();
return null;
}, []);
return Scaffold(
appBar: AppBar(
title: const Text(
'First Page!',
style: TextStyle(
fontFamily: FontFamily.rubikDoodleShadow,
fontSize: 25,
color: Colors.deepPurple,
),
),
),
body: Center(
child: AnimatedCrossFade(
duration: const Duration(seconds: 3), // 3秒かけてアニメーションする
// firstChildは最初に表示されるWidget
firstChild: const Text(
'Welcome!',
style: TextStyle(
fontFamily: FontFamily.rubikDoodleShadow,
fontSize: 25,
color: Colors.deepPurple,
),
),
// secondChildはfirstChildが消えた後に表示されるWidget
secondChild: ElevatedButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) {
return const NextPage();
},
),
);
},
// ignore: sort_child_properties_last
child: Ink(
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Colors.red, Colors.purple, Colors.blue],
),
borderRadius: BorderRadius.circular(4),
),
child: Container(
padding:
const EdgeInsets.symmetric(vertical: 10, horizontal: 15),
child:
const Text('次のページへ', style: TextStyle(color: Colors.white)),
),
),
style: ElevatedButton.styleFrom(
padding: EdgeInsets.zero,
),
),
crossFadeState: firstBool.value
? CrossFadeState.showFirst
: CrossFadeState.showSecond,
),
),
);
}
}
まとめ
アニメーション機能は時間や位置を制御したり、状態の破棄をするライフサイクルがあるので、難しくて奥が深いな〜と思いました😅
極めたら楽しいんだろうな〜と思ってこれからは、アニメーションを使ったアプリも作りたいですね。
Discussion