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('切り替え'),
        ),
      ],
    );
  }
}

よく使われる暗黙的アニメーション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),
          );
        },
      ),
    );
  }
}

スタガーアニメーション

実例:スタガーアニメーション

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('ようこそ'),
                ),
              ),
            ),
          ),
        );
      },
    );
  }
}

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,
          ),
        ),
      ),
    );
  }
}

暗黙的アニメーションは単純なシーンに適しており、明示的アニメーションは複雑な制御に適しています。

Heroアニメーションはページ遷移時の最適な選択肢です。

その他の拡張