Flutter アニメーション基礎
このセクションでは、Flutterにおけるアニメーションの実装について、暗黙的アニメーションと明示的アニメーションを含めて説明します。
暗黙的アニメーション
暗黙的アニメーションは、Flutterがトランジション効果を自動的に処理するアニメーションWidgetです。
実例:AnimatedContainer
class AnimatedContainerExample extends StatefulWidget {
const AnimatedContainerExample({super.key});
@override
State<AnimatedContainerExample> createState() =>
_AnimatedContainerExampleState();
}
class _AnimatedContainerExampleState
extends State<AnimatedContainerExample> {
bool _isLarge = false;
@override
Widget build(BuildContext context) {
return Column(
children: [
// クリックでサイズを切り替え
GestureDetector(
onTap: () => setState(() => _isLarge = !_isLarge),
child: AnimatedContainer(
duration: const Duration(milliseconds: 500), // アニメーションの継続時間
curve: Curves.easeInOut, // アニメーションカーブ
width: _isLarge ? 200 : 100,
height: _isLarge ? 200 : 100,
color: _isLarge ? Colors.blue : Colors.red,
child: Center(
child: Text(
_isLarge ? '大' : '小',
style: const TextStyle(color: Colors.white, fontSize: 24),
),
),
),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () => setState(() => _isLarge = !_isLarge),
child: const Text('切り替え'),
),
],
);
}
}
const AnimatedContainerExample({super.key});
@override
State<AnimatedContainerExample> createState() =>
_AnimatedContainerExampleState();
}
class _AnimatedContainerExampleState
extends State<AnimatedContainerExample> {
bool _isLarge = false;
@override
Widget build(BuildContext context) {
return Column(
children: [
// クリックでサイズを切り替え
GestureDetector(
onTap: () => setState(() => _isLarge = !_isLarge),
child: AnimatedContainer(
duration: const Duration(milliseconds: 500), // アニメーションの継続時間
curve: Curves.easeInOut, // アニメーションカーブ
width: _isLarge ? 200 : 100,
height: _isLarge ? 200 : 100,
color: _isLarge ? Colors.blue : Colors.red,
child: Center(
child: Text(
_isLarge ? '大' : '小',
style: const TextStyle(color: Colors.white, fontSize: 24),
),
),
),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () => setState(() => _isLarge = !_isLarge),
child: const Text('切り替え'),
),
],
);
}
}
よく使われる暗黙的アニメーションWidget
| Widget | 説明 |
|---|---|
| AnimatedContainer | コンテナのサイズ、色などのプロパティが変化するときのアニメーション |
| AnimatedOpacity | 透明度変化アニメーション |
| AnimatedPadding | パディング変化アニメーション |
| AnimatedAlign | アライメント変化アニメーション |
| AnimatedSwitcher | 子Widgetを切り替えるときのアニメーション |
| AnimatedDefaultTextStyle | テキストスタイル変化アニメーション |
明示的アニメーション
明示的アニメーションは、AnimationControllerを使用してアニメーションを完全に制御します。
実例:回転アニメーション
class RotationAnimation extends StatefulWidget {
const RotationAnimation({super.key});
@override
State<RotationAnimation> createState() => _RotationAnimationState();
}
class _RotationAnimationState extends State<RotationAnimation>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
// アニメーションコントローラーを作成
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
// 回転アニメーションを作成(0から1を0から2πにマッピング)
_animation = Tween<double>(begin: 0, end: 6.28).animate(
CurvedAnimation(parent: _controller, curve: Curves.linear),
);
// アニメーションを開始
_controller.repeat();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Center(
child: AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Transform.rotate(
angle: _animation.value, // 回転角度
child: const Icon(Icons.refresh, size: 100),
);
},
),
);
}
}
const RotationAnimation({super.key});
@override
State<RotationAnimation> createState() => _RotationAnimationState();
}
class _RotationAnimationState extends State<RotationAnimation>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
// アニメーションコントローラーを作成
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
// 回転アニメーションを作成(0から1を0から2πにマッピング)
_animation = Tween<double>(begin: 0, end: 6.28).animate(
CurvedAnimation(parent: _controller, curve: Curves.linear),
);
// アニメーションを開始
_controller.repeat();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Center(
child: AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Transform.rotate(
angle: _animation.value, // 回転角度
child: const Icon(Icons.refresh, size: 100),
);
},
),
);
}
}
スタガーアニメーション
実例:スタガーアニメーション
class StaggeredAnimation extends StatefulWidget {
const StaggeredAnimation({super.key});
@override
State<StaggeredAnimation> createState() => _StaggeredAnimationState();
}
class _StaggeredAnimationState extends State<StaggeredAnimation>
with TickerProviderStateMixin {
late AnimationController _controller;
// 各アニメーション
late Animation<double> _fadeAnimation;
late Animation<double> _scaleAnimation;
late Animation<Offset> _slideAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 1500),
vsync: this,
);
// フェードインアニメーション(0-0.5)
_fadeAnimation = Tween<double>(begin: 0, end: 1).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0, 0.5, curve: Curves.easeOut),
),
);
// スケールアニメーション(0.3-0.7)
_scaleAnimation = Tween<double>(begin: 0.5, end: 1).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.3, 0.7, curve: Curves.elasticOut),
),
);
// スライドインアニメーション(0.5-1.0)
_slideAnimation = Tween<Offset>(
begin: const Offset(0, 0.5),
end: Offset.zero,
).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.5, 1, curve: Curves.easeOutCubic),
),
);
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return FadeTransition(
opacity: _fadeAnimation,
child: ScaleTransition(
scale: _scaleAnimation,
child: SlideTransition(
position: _slideAnimation,
child: const Card(
child: Padding(
padding: EdgeInsets.all(24),
child: Text('ようこそ'),
),
),
),
),
);
},
);
}
}
const StaggeredAnimation({super.key});
@override
State<StaggeredAnimation> createState() => _StaggeredAnimationState();
}
class _StaggeredAnimationState extends State<StaggeredAnimation>
with TickerProviderStateMixin {
late AnimationController _controller;
// 各アニメーション
late Animation<double> _fadeAnimation;
late Animation<double> _scaleAnimation;
late Animation<Offset> _slideAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 1500),
vsync: this,
);
// フェードインアニメーション(0-0.5)
_fadeAnimation = Tween<double>(begin: 0, end: 1).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0, 0.5, curve: Curves.easeOut),
),
);
// スケールアニメーション(0.3-0.7)
_scaleAnimation = Tween<double>(begin: 0.5, end: 1).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.3, 0.7, curve: Curves.elasticOut),
),
);
// スライドインアニメーション(0.5-1.0)
_slideAnimation = Tween<Offset>(
begin: const Offset(0, 0.5),
end: Offset.zero,
).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.5, 1, curve: Curves.easeOutCubic),
),
);
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return FadeTransition(
opacity: _fadeAnimation,
child: ScaleTransition(
scale: _scaleAnimation,
child: SlideTransition(
position: _slideAnimation,
child: const Card(
child: Padding(
padding: EdgeInsets.all(24),
child: Text('ようこそ'),
),
),
),
),
);
},
);
}
}
Heroアニメーション
Heroアニメーションは、ページ遷移時に共有要素のトランジション効果を作成するために使用します。
実例:Heroアニメーション
// ホームページ - Hero付きの画像
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('ホーム')),
body: Center(
child: GestureDetector(
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => const DetailPage()),
),
child: Hero(
tag: 'myHero', // 同じタグ
child: Image.network(
'https://picsum.photos/200',
width: 200,
height: 200,
),
),
),
),
);
}
}
// 詳細ページ - Hero付きの画像
class DetailPage extends StatelessWidget {
const DetailPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('詳細')),
body: Center(
child: Hero(
tag: 'myHero', // 同じタグ
child: Image.network(
'https://picsum.photos/200',
width: 300,
height: 300,
),
),
),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('ホーム')),
body: Center(
child: GestureDetector(
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => const DetailPage()),
),
child: Hero(
tag: 'myHero', // 同じタグ
child: Image.network(
'https://picsum.photos/200',
width: 200,
height: 200,
),
),
),
),
);
}
}
// 詳細ページ - Hero付きの画像
class DetailPage extends StatelessWidget {
const DetailPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('詳細')),
body: Center(
child: Hero(
tag: 'myHero', // 同じタグ
child: Image.network(
'https://picsum.photos/200',
width: 300,
height: 300,
),
),
),
);
}
}
その他の拡張暗黙的アニメーションは単純なシーンに適しており、明示的アニメーションは複雑な制御に適しています。
Heroアニメーションはページ遷移時の最適な選択肢です。