【Flutter】AnimationController でアニメーション付のHPゲージをつくってみた

Flutter

今回は、以下の記事でつくった HP ゲージに、ダメージが入ると少しずつHPが削られていくようなアニメーションをつけてみました!
そのソースコードと解説、使い方を紹介します!

アニメーション付HPゲージ

ソースコード

以下がアニメーション付のHPゲージを AnimatedHpGauge として独自 Widget 化したソースコードです。
HPが減ると少しずつゲージとHPの表示テキストが減っていくようになっています。

class AnimatedHpGauge extends StatefulWidget {
  const AnimatedHpGauge({
    required this.maxHp,
    Key? key
  }) : super(key: key);

  final int maxHp;

  @override
  State<AnimatedHpGauge> createState() => AnimatedHpGaugeState();
}

class AnimatedHpGaugeState extends State<AnimatedHpGauge> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 1),
    );
    _animation = Tween(begin: 1.0, end: 1.0).animate(CurvedAnimation(
      parent: _controller,
      curve: Curves.easeInOut,
    ));
  }

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

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _controller,
      builder: (context, child) {
        return Row(
          children: [
            Expanded(
              child: ClipRRect(
                borderRadius: const BorderRadius.all(Radius.circular(5)),
                child: LinearProgressIndicator(
                  value: _animation.value,
                  valueColor: AlwaysStoppedAnimation(_getCurrentHpColor(_animation.value)),
                  backgroundColor: Colors.grey,
                  minHeight: 20,
                ),
              ),
            ),
            Text('${_getCurrentHp().toString().padLeft(4, '  ')}/${widget.maxHp}'),
          ],
        );
      },
    );
  }

  void startAnimation(int damage) {
    _animation = Tween<double>(
      begin: _animation.value,
      end: (_getCurrentHp() - damage) / widget.maxHp,
    ).animate(CurvedAnimation(
      parent: _controller,
      curve: Curves.easeInOut,
    ));
    _controller.forward(from: 0.0);
  }

  int _getCurrentHp() {
    return (_animation.value * widget.maxHp).toInt();
  }

  Color _getCurrentHpColor(double hp) {
    if (hp > 1 / 2) {
      return const Color(0xFF00D308);
    }
    if (hp > 1 / 5) {
      return const Color(0xFFFFC107);
    }
    return const Color(0xFFFF0707);
  }
}

使用例

AnimatedHpGauge を使用した例が以下になります。
ボタンを押すと、ボタンに記載の分だけHPが減っていくようにしてみました。

class AnimatedHpGaugePage extends StatefulWidget {
  const AnimatedHpGaugePage({Key? key}) : super(key: key);

  @override
  State<AnimatedHpGaugePage> createState() => _AnimatedHpGaugePageState();
}

class _AnimatedHpGaugePageState extends State<AnimatedHpGaugePage> {
  final int maxHp = 200;

  @override
  Widget build(BuildContext context) {
    final _animatedHpGageKey = GlobalObjectKey<AnimatedHpGaugeState>(context);

    return Scaffold(
        appBar: AppBar(
          title: const Text("Animated HP gauge Sample"),
        ),
        body: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Padding(
              padding: const EdgeInsets.all(30),
              child: AnimatedHpGauge(key: _animatedHpGageKey, maxHp: maxHp),
            ),
            Padding(
              padding: const EdgeInsets.all(30),
              child: ElevatedButton(
                onPressed: (){
                  setState(() {
                    _animatedHpGageKey.currentState?.startAnimation(16);
                  });
                },
                child: const Text('HP -16'),
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(10),
              child: ElevatedButton(
                onPressed: (){
                  setState(() {
                    _animatedHpGageKey.currentState?.startAnimation(40);
                  });
                },
                child: const Text('HP -40'),
              ),
            ),
          ],
        )
    );
  }
}
アニメーション付のHPゲージ

解説

独自 Widget クラスのプロパティ・状態

まず、AnimatedHpGauge のプロパティは、maxHPのみです。このプロパティは、最大HPの値を設定します。
また、AnimatedHpGaugeState で、アニメーションを管理する AnimationController と 具体的なアニメーションを設定する Animation をそれぞれ _controller、_animation で持っています。

アニメーションを付けるための設定

アニメーションを付けるための設定を行なっているのが、主に以下の部分になります。

まず、1行目で with SingleTickerProviderStateMixin とすることにより、アニメーションを付けるために必要な AnimationController を簡単に初期化、管理できるようにしています。
そして、initState 時に、AnimationController の設定を行なっています。(8〜11行目)
ここでは、アニメーション時間を1秒に設定しています。

また、具体的なアニメーションの初期設定を12〜15行目で行なっています。
今回は、曲線的な変化でアニメーションを行う Curves.easeInOut でアニメーションするようにしました。

class AnimatedHpGaugeState extends State<AnimatedHpGauge> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 1),
    );
    _animation = Tween(begin: 1.0, end: 1.0).animate(CurvedAnimation(
      parent: _controller,
      curve: Curves.easeInOut,
    ));
  }

  …
}

今回使用した Curves.easeInOut 以外にも、曲線的な変化でアニメーションするものがたくさんあります。詳細は以下をご参考ください。

HPゲージのアニメーション表示

HPゲージの量、色、HPのテキストをアニメーション表示させているのが以下の部分です。
AnimatedBuilder 内の Widget がアニメーション対象になります。

@override
Widget build(BuildContext context) {
  return AnimatedBuilder(
    animation: _controller,
    builder: (context, child) {
      return Row(
        children: [
          Expanded(
            child: ClipRRect(
              borderRadius: const BorderRadius.all(Radius.circular(5)),
              child: LinearProgressIndicator(
                value: _animation.value,
                valueColor: AlwaysStoppedAnimation(_getCurrentHpColor(_animation.value)),
                backgroundColor: Colors.grey,
                minHeight: 20,
              ),
            ),
          ),
          Text('${_getCurrentHp().toString().padLeft(4, '  ')}/${widget.maxHp}'),
        ],
      );
    },
  );
}

HPゲージの値の設定

HPゲージは、LinearProgressIndicator をベースに作成しています。

バーの進捗がアニメーションで動くようにするため、value プロパティには、_animated.value を設定います。_animation.value が、現在のアニメーション値(0.0〜1.0)を表しており、LinearProgressIndicator の value プロパティも0.0〜1.0で進捗を設定するので、_animation.value をそのまま設定しています。

バーの色を設定する valueColor プロパティでは、現在のアニメーション値(進捗)に応じて色が切り替わるように設定しています。
以下の _getCurrentHpColor メソッドを使用して、アニメーション値に応じたバーの色を決めています。

Color _getCurrentHpColor(double hp) {
  if (hp > 1 / 2) {
    return const Color(0xFF00D308);
  }
  if (hp > 1 / 5) {
    return const Color(0xFFFFC107);
  }
  return const Color(0xFFFF0707);
}

HPテキストの値の設定

HPを「現在のHP/最大HP」の形でテキスト表示しているのが、以下の部分です。ここも現在のHPがアニメーションするようにしています。

Text('${_getCurrentHp().toString().padLeft(4, '  ')}/${widget.maxHp}')

現在のHPは、_getCurrentHp メソッドで取得しています。
_animation.value が、現在のアニメーション値(0.0〜1.0)を表しているので、この値に maxHp を掛けることにより、現在のHPを取得しています。
また、toInt() で、int 型への変換も行なっています。
このようにすることで、アニメーションの動きにあわせて、0〜maxHp の範囲でHPが取れます。

int _getCurrentHp() {
  return (_animation.value * widget.maxHp).toInt();
}

アニメーションの実行

アニメーション実行メソッド

アニメーションを実行させる部分が以下の startAnimation メソッドです。

Tween を使用して、アニメーションの開始地点と終了地点を設定しています。
開始地点は、現在HPのアニメーション値なので _animation.value を指定し、
終了地点は、ダメージが引かれた後のHPのアニメーション値なので、(_getCurrentHp() – damage) / widget.maxHp を指定しています。maxHp で除算することで、0.0〜1.0の範囲にしています。

最後に、AnimationController の forward メソッドを使ってアニメーションを開始させています。

void startAnimation(int damage) {
  _animation = Tween<double>(
    begin: _animation.value,
    end: (_getCurrentHp() - damage) / widget.maxHp,
  ).animate(CurvedAnimation(
    parent: _controller,
    curve: Curves.easeInOut,
  ));
  _controller.forward(from: 0.0);
}

親 Widget からアニメーション実行

親 Widget の方で、GlobalObjectKey を使用することで、子 Widget である AnimatedHpGauge の startAnimation メソッドを呼び出して、アニメーション実行が可能です。
このように GlobalObjectKey を使用するため、AnimatedHpGaugeState は、private にしていません。private にすると AnimatedHpGaugeState を参照できなくなるためです。

まとめ

Flutterで、アニメーション付のHPゲージをつくってみて、そのソースコードを解説をしました!
使ってみたい方は、コピペして、アニメーションの動きや時間等変更して使ってみてください。

以上で、【Flutter】AnimationController でアニメーション付のHPゲージをつくってみた は終わりです。

参考

おすすめ書籍

コメント

タイトルとURLをコピーしました