アニメーション
Flutterアニメーション完全ガイド
Section titled “Flutterアニメーション完全ガイド”Flutterでは、豊富なアニメーションAPIを提供しており、美しく滑らかなアニメーションを簡単に実装できます。ここでは、基本的なアニメーションから高度な実装まで、実務で使えるパターンを詳しく解説します。
1. 基本的なアニメーション
Section titled “1. 基本的なアニメーション”AnimatedContainer
Section titled “AnimatedContainer”最もシンプルなアニメーションウィジェットです。プロパティの変更が自動的にアニメーションされます。
class AnimatedContainerExample extends StatefulWidget { @override _AnimatedContainerExampleState createState() => _AnimatedContainerExampleState();}
class _AnimatedContainerExampleState extends State<AnimatedContainerExample> { bool _selected = false;
@override Widget build(BuildContext context) { return GestureDetector( onTap: () { setState(() { _selected = !_selected; }); }, child: AnimatedContainer( width: _selected ? 200.0 : 100.0, height: _selected ? 100.0 : 200.0, color: _selected ? Colors.red : Colors.blue, duration: Duration(seconds: 1), curve: Curves.fastOutSlowIn, decoration: BoxDecoration( borderRadius: BorderRadius.circular(_selected ? 20.0 : 0.0), ), ), ); }}AnimatedOpacity
Section titled “AnimatedOpacity”透明度のアニメーションを実装します。
class AnimatedOpacityExample extends StatefulWidget { @override _AnimatedOpacityExampleState createState() => _AnimatedOpacityExampleState();}
class _AnimatedOpacityExampleState extends State<AnimatedOpacityExample> { double _opacity = 1.0;
@override Widget build(BuildContext context) { return Column( children: [ AnimatedOpacity( opacity: _opacity, duration: Duration(seconds: 1), child: Container( width: 200, height: 200, color: Colors.blue, ), ), ElevatedButton( onPressed: () { setState(() { _opacity = _opacity == 1.0 ? 0.0 : 1.0; }); }, child: Text('Toggle Opacity'), ), ], ); }}2. AnimationControllerを使用したアニメーション
Section titled “2. AnimationControllerを使用したアニメーション”より細かい制御が必要な場合は、AnimationControllerを使用します。
基本的なAnimationController
Section titled “基本的なAnimationController”class AnimationControllerExample extends StatefulWidget { @override _AnimationControllerExampleState createState() => _AnimationControllerExampleState();}
class _AnimationControllerExampleState extends State<AnimationControllerExample> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation<double> _animation;
@override void initState() { super.initState(); _controller = AnimationController( duration: Duration(seconds: 2), vsync: this, );
_animation = Tween<double>( begin: 0.0, end: 1.0, ).animate(CurvedAnimation( parent: _controller, curve: Curves.easeInOut, ));
_controller.forward(); }
@override void dispose() { _controller.dispose(); super.dispose(); }
@override Widget build(BuildContext context) { return FadeTransition( opacity: _animation, child: Container( width: 200, height: 200, color: Colors.blue, ), ); }}複数のアニメーションを組み合わせる
Section titled “複数のアニメーションを組み合わせる”class CombinedAnimationExample extends StatefulWidget { @override _CombinedAnimationExampleState createState() => _CombinedAnimationExampleState();}
class _CombinedAnimationExampleState extends State<CombinedAnimationExample> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation<double> _scaleAnimation; late Animation<double> _rotationAnimation;
@override void initState() { super.initState(); _controller = AnimationController( duration: Duration(seconds: 2), vsync: this, );
_scaleAnimation = Tween<double>( begin: 0.5, end: 1.0, ).animate(CurvedAnimation( parent: _controller, curve: Curves.easeInOut, ));
_rotationAnimation = Tween<double>( begin: 0.0, end: 2 * 3.14159, ).animate(CurvedAnimation( parent: _controller, curve: Curves.easeInOut, ));
_controller.repeat(); }
@override void dispose() { _controller.dispose(); super.dispose(); }
@override Widget build(BuildContext context) { return AnimatedBuilder( animation: _controller, builder: (context, child) { return Transform.scale( scale: _scaleAnimation.value, child: Transform.rotate( angle: _rotationAnimation.value, child: Container( width: 100, height: 100, color: Colors.blue, ), ), ); }, ); }}3. 各種アニメーションの実装例
Section titled “3. 各種アニメーションの実装例”フェードアニメーション
Section titled “フェードアニメーション”class FadeAnimationExample extends StatefulWidget { @override _FadeAnimationExampleState createState() => _FadeAnimationExampleState();}
class _FadeAnimationExampleState extends State<FadeAnimationExample> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation<double> _fadeAnimation;
@override void initState() { super.initState(); _controller = AnimationController( duration: Duration(milliseconds: 500), vsync: this, );
_fadeAnimation = Tween<double>( begin: 0.0, end: 1.0, ).animate(CurvedAnimation( parent: _controller, curve: Curves.easeIn, ));
_controller.forward(); }
@override void dispose() { _controller.dispose(); super.dispose(); }
@override Widget build(BuildContext context) { return FadeTransition( opacity: _fadeAnimation, child: Container( width: 200, height: 200, color: Colors.blue, ), ); }}スライドアニメーション
Section titled “スライドアニメーション”class SlideAnimationExample extends StatefulWidget { @override _SlideAnimationExampleState createState() => _SlideAnimationExampleState();}
class _SlideAnimationExampleState extends State<SlideAnimationExample> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation<Offset> _slideAnimation;
@override void initState() { super.initState(); _controller = AnimationController( duration: Duration(milliseconds: 500), vsync: this, );
_slideAnimation = Tween<Offset>( begin: Offset(-1.0, 0.0), end: Offset.zero, ).animate(CurvedAnimation( parent: _controller, curve: Curves.easeOut, ));
_controller.forward(); }
@override void dispose() { _controller.dispose(); super.dispose(); }
@override Widget build(BuildContext context) { return SlideTransition( position: _slideAnimation, child: Container( width: 200, height: 200, color: Colors.blue, ), ); }}回転アニメーション
Section titled “回転アニメーション”class RotationAnimationExample extends StatefulWidget { @override _RotationAnimationExampleState createState() => _RotationAnimationExampleState();}
class _RotationAnimationExampleState extends State<RotationAnimationExample> with SingleTickerProviderStateMixin { late AnimationController _controller;
@override void initState() { super.initState(); _controller = AnimationController( duration: Duration(seconds: 2), vsync: this, )..repeat(); }
@override void dispose() { _controller.dispose(); super.dispose(); }
@override Widget build(BuildContext context) { return RotationTransition( turns: _controller, child: Container( width: 100, height: 100, color: Colors.blue, ), ); }}スケールアニメーション
Section titled “スケールアニメーション”class ScaleAnimationExample extends StatefulWidget { @override _ScaleAnimationExampleState createState() => _ScaleAnimationExampleState();}
class _ScaleAnimationExampleState extends State<ScaleAnimationExample> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation<double> _scaleAnimation;
@override void initState() { super.initState(); _controller = AnimationController( duration: Duration(milliseconds: 500), vsync: this, );
_scaleAnimation = Tween<double>( begin: 0.0, end: 1.0, ).animate(CurvedAnimation( parent: _controller, curve: Curves.elasticOut, ));
_controller.forward(); }
@override void dispose() { _controller.dispose(); super.dispose(); }
@override Widget build(BuildContext context) { return ScaleTransition( scale: _scaleAnimation, child: Container( width: 200, height: 200, color: Colors.blue, ), ); }}4. Heroアニメーション
Section titled “4. Heroアニメーション”画面間でウィジェットを共有する際に使用するアニメーションです。
// 画面1class FirstScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('First Screen')), body: Center( child: Hero( tag: 'hero-image', child: Container( width: 100, height: 100, color: Colors.blue, ), ), ), floatingActionButton: FloatingActionButton( onPressed: () { Navigator.push( context, MaterialPageRoute(builder: (context) => SecondScreen()), ); }, child: Icon(Icons.arrow_forward), ), ); }}
// 画面2class SecondScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Second Screen')), body: Center( child: Hero( tag: 'hero-image', child: Container( width: 300, height: 300, color: Colors.blue, ), ), ), ); }}5. PageRouteBuilderを使用したカスタムページ遷移
Section titled “5. PageRouteBuilderを使用したカスタムページ遷移”class CustomPageRoute extends PageRouteBuilder { final Widget child;
CustomPageRoute({required this.child}) : super( transitionDuration: Duration(milliseconds: 500), pageBuilder: (context, animation, secondaryAnimation) => child, );
@override Widget buildTransitions( BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child, ) { return SlideTransition( position: Tween<Offset>( begin: Offset(1.0, 0.0), end: Offset.zero, ).animate(CurvedAnimation( parent: animation, curve: Curves.easeOut, )), child: child, ); }}
// 使用例Navigator.push( context, CustomPageRoute(child: SecondScreen()),);6. 実務でのアニメーション実装パターン
Section titled “6. 実務でのアニメーション実装パターン”パターン1: ローディングアニメーション
Section titled “パターン1: ローディングアニメーション”class LoadingAnimation extends StatefulWidget { @override _LoadingAnimationState createState() => _LoadingAnimationState();}
class _LoadingAnimationState extends State<LoadingAnimation> with SingleTickerProviderStateMixin { late AnimationController _controller;
@override void initState() { super.initState(); _controller = AnimationController( duration: Duration(seconds: 1), vsync: this, )..repeat(); }
@override void dispose() { _controller.dispose(); super.dispose(); }
@override Widget build(BuildContext context) { return RotationTransition( turns: _controller, child: CircularProgressIndicator(), ); }}パターン2: リストアイテムのアニメーション
Section titled “パターン2: リストアイテムのアニメーション”class AnimatedListExample extends StatelessWidget { final List<String> items = List.generate(10, (index) => 'Item $index');
@override Widget build(BuildContext context) { return ListView.builder( itemCount: items.length, itemBuilder: (context, index) { return TweenAnimationBuilder<double>( tween: Tween(begin: 0.0, end: 1.0), duration: Duration(milliseconds: 300 + (index * 50)), builder: (context, value, child) { return Opacity( opacity: value, child: Transform.translate( offset: Offset(0, 50 * (1 - value)), child: child, ), ); }, child: ListTile( title: Text(items[index]), ), ); }, ); }}パターン3: ボタンのタップアニメーション
Section titled “パターン3: ボタンのタップアニメーション”class AnimatedButton extends StatefulWidget { final VoidCallback onPressed; final Widget child;
AnimatedButton({required this.onPressed, required this.child});
@override _AnimatedButtonState createState() => _AnimatedButtonState();}
class _AnimatedButtonState extends State<AnimatedButton> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation<double> _scaleAnimation;
@override void initState() { super.initState(); _controller = AnimationController( duration: Duration(milliseconds: 100), vsync: this, );
_scaleAnimation = Tween<double>( begin: 1.0, end: 0.95, ).animate(CurvedAnimation( parent: _controller, curve: Curves.easeInOut, )); }
@override void dispose() { _controller.dispose(); super.dispose(); }
@override Widget build(BuildContext context) { return GestureDetector( onTapDown: (_) => _controller.forward(), onTapUp: (_) { _controller.reverse(); widget.onPressed(); }, onTapCancel: () => _controller.reverse(), child: ScaleTransition( scale: _scaleAnimation, child: widget.child, ), ); }}7. パフォーマンス最適化のテクニック
Section titled “7. パフォーマンス最適化のテクニック”AnimatedBuilderの活用
Section titled “AnimatedBuilderの活用”再構築を最小限に抑えるために、AnimatedBuilderを使用します。
class OptimizedAnimation extends StatefulWidget { @override _OptimizedAnimationState createState() => _OptimizedAnimationState();}
class _OptimizedAnimationState extends State<OptimizedAnimation> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation<double> _animation;
@override void initState() { super.initState(); _controller = AnimationController( duration: Duration(seconds: 2), vsync: this, );
_animation = Tween<double>(begin: 0.0, end: 1.0).animate(_controller); _controller.repeat(); }
@override void dispose() { _controller.dispose(); super.dispose(); }
@override Widget build(BuildContext context) { return AnimatedBuilder( animation: _animation, builder: (context, child) { return Transform.rotate( angle: _animation.value * 2 * 3.14159, child: child, ); }, child: Container( width: 100, height: 100, color: Colors.blue, ), ); }}constコンストラクタの使用
Section titled “constコンストラクタの使用”アニメーションしない部分はconstを使用して再構築を防ぎます。
AnimatedBuilder( animation: _controller, builder: (context, child) { return Transform.scale( scale: _animation.value, child: child, // constウィジェットを再利用 ); }, child: const Text('Static Text'), // constで再構築を防ぐ)8. よくある問題と解決策
Section titled “8. よくある問題と解決策”問題1: アニメーションが滑らかでない
Section titled “問題1: アニメーションが滑らかでない”解決策: vsyncを正しく設定し、不要な再構築を避けます。
// 正しい実装class MyWidget extends StatefulWidget { @override _MyWidgetState createState() => _MyWidgetState();}
class _MyWidgetState extends State<MyWidget> with SingleTickerProviderStateMixin { late AnimationController _controller;
@override void initState() { super.initState(); _controller = AnimationController( duration: Duration(seconds: 1), vsync: this, // 重要: vsyncを設定 ); }}問題2: メモリリーク
Section titled “問題2: メモリリーク”解決策: disposeメソッドでコントローラーを確実に破棄します。
@overridevoid dispose() { _controller.dispose(); // 必ず破棄 super.dispose();}問題3: 複数のアニメーションを同時に実行する
Section titled “問題3: 複数のアニメーションを同時に実行する”解決策: TickerProviderStateMixinの代わりにTickerProviderStateMixinを使用します。
class MultipleAnimations extends StatefulWidget { @override _MultipleAnimationsState createState() => _MultipleAnimationsState();}
class _MultipleAnimationsState extends State<MultipleAnimations> with TickerProviderStateMixin { // SingleTickerProviderStateMixinではなく late AnimationController _controller1; late AnimationController _controller2;
@override void initState() { super.initState(); _controller1 = AnimationController( duration: Duration(seconds: 1), vsync: this, ); _controller2 = AnimationController( duration: Duration(seconds: 2), vsync: this, ); }}これで、Flutterでのアニメーション実装の基礎から実践的なパターンまでを理解できるようになりました。