0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Flutter】2回目以降アニメーションが動かない?AnimationControllerの仕組みと正しいリセット方法

0
Posted at

はじめに

Flutterで明示的アニメーション(Explicit Animations)を実装している際、「1回目は綺麗に動くのに、2回目以降ボタンを押してもアニメーションせずに一瞬で色が切り替わってしまう」という現象に遭遇しました。

今回は、DecoratedBoxTransition を例に、この現象が発生する原因と正しい解決方法を記事にします。


発生する問題

以下は、ボタンを押すたびにコンテナの色がランダムに変化するアニメーションを意図したコードです。

しかし、このコードでは最初の1回しかアニメーションが実行されず、2回目以降は色が一瞬でパッと切り替わってしまいます。

import 'dart:math';
import 'package:flutter/material.dart';

class ColorPage extends StatefulWidget {
  const ColorPage({super.key});

  @override
  State<ColorPage> createState() => _ColorPageState();
}

class _ColorPageState extends State<ColorPage>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  final List<Color> _colors = [
    Colors.blue,
    Colors.red,
    Colors.green,
    Colors.yellow,
  ];
  Color _currentColor = Colors.blue;
  Color _nextColor = Colors.blue;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 1),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Color Animation')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            DecoratedBoxTransition(
              decoration: _controller.drive(
                DecorationTween(
                  begin: BoxDecoration(color: _currentColor),
                  end: BoxDecoration(color: _nextColor),
                ),
              ),
              child: const SizedBox(width: 100, height: 100),
            ),
            const SizedBox(height: 16),
            ElevatedButton(
              onPressed: () {
                final randomIndex = Random().nextInt(_colors.length);
                final randomColor = _colors[randomIndex];
                setState(() {
                  _currentColor = _nextColor;
                  _nextColor = randomColor;
                });
                _controller.forward(); // ⚠️ 2回目以降、ここでアニメーションしない
              },
              child: const Text('Animate Color'),
            ),
          ],
        ),
      ),
    );
  }
}


原因:AnimationControllerは「1.0」で止まっている

なぜ2回目以降、アニメーションが即座に切り替わってしまうのでしょうか?

原因は、Flutterのアニメーション進捗の管理方法にあります。

  • Flutterのアニメーションの進行具合は、内部的に 0.0 から 1.0 の double 型の値で管理されています。
  • _controller.forward() を呼び出すと、値は 0.0 から 1.0 に向かって進みます。

最初の1回目のアニメーションが終わった時点で、_controller の値はすでに最大値(1.0)に達しています。
そのため、2回目にボタンを押して再び _controller.forward() を呼び出しても、
「すでに1.0(終了状態)なので、これ以上進まない」状態になってしまいます。

DecoratedBoxTransitionAnimationController の値を使って描画を行うため、コントローラーの値が 1.0 のままだと、新しい DecorationTween を設定しても即座に終了状態として描画されてしまうのです。


解決策:アニメーションをリセットする

2回目以降もボタンを押すたびに最初からアニメーションを再生するには、onPressed の中でコントローラーの値を初期状態(0.0)に戻す必要があります。

修正方法は以下の2通りがあります。

修正パターン1:reset() してから forward() する

forward() を呼ぶ前に _controller.reset() を挟むことで、アニメーションの進捗を明示的に 0.0 に戻します。

ElevatedButton(
  onPressed: () {
    final randomIndex = Random().nextInt(_colors.length);
    final randomColor = _colors[randomIndex];
    setState(() {
      _currentColor = _nextColor;
      _nextColor = randomColor;
    });
    
    _controller.reset();   // 1. コントローラーを初期状態(0.0)に戻す
    _controller.forward(); // 2. 再度アニメーションを開始する
  },
  child: const Text('Animate Color'),
),

修正パターン2:forward(from: 0.0) を使う

forward メソッドの引数 from を指定すると、リセットと再生を同時に行うことができます。コードをスッキリ書きたいときはこちらが便利です。

ElevatedButton(
  onPressed: () {
    final randomIndex = Random().nextInt(_colors.length);
    final randomColor = _colors[randomIndex];
    setState(() {
      _currentColor = _nextColor;
      _nextColor = randomColor;
    });
    
    // 0.0(最初)からアニメーションを強制的に再生する
    _controller.forward(from: 0.0);
  },
  child: const Text('Animate Color'),
),


まとめ

  • AnimationController.forward() は、現在の値から 1.0 に向かって進む
  • アニメーションが終了した後は、値が 1.0 のまま保持される
  • 繰り返しアニメーションを再生したい場合は、_controller.reset() を呼ぶか、_controller.forward(from: 0.0) を使用して進捗をリセットする必要がある
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?