0
0

More than 1 year has passed since last update.

【Flutter】輝く光点のアニメーション周期を可変にする

Last updated at Posted at 2022-07-19

はじめに

前回の記事で光点のアニメーションを作りました。

その際に課題だった「アニメーション周期を可変にする方法」について、
一応解決していたのでまとめておきます。

完成品

スライダーで、アニメーション周期を1000~50msecの範囲で動的に変更できるようにした。
光点周期.gif

ソース

ほとんど前回の記事のまんまですが、
とりあえず全部貼ります。

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

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

  @override
  _LightSpotTest createState() => _LightSpotTest();
}

class _LightSpotTest extends State<LightSpotTest>
    with TickerProviderStateMixin {
  final _radius = 10.0; //基本光点のサイズ
  final _backRadius = 20.0; //後光のサイズ
  late AnimationController _animationController;
  late Animation<double> _animationRadius;
  var _duration = 500.0;

  @override
  void initState() {
    super.initState();
    //アニメーションコントローラの設定
    _animationController = AnimationController(
        duration: Duration(milliseconds: _duration.toInt()), vsync: this);
    //アニメーションの設定
    _animationRadius =
        Tween(begin: 0.0, end: _backRadius).animate(_animationController)
          ..addListener(() {
            setState(() {});
          });
    //初回アニメーション実行
    _animationController.forward();

    _animationController.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        //アニメーションを一旦リセット
        _animationController.reset();
        //アニメーション終了時、周期を再設定
        _animationController.duration =
            Duration(milliseconds: _duration.toInt());
        //アニメーション再実行
        _animationController.forward();
      }
    });
  }

  void dispose() {
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Container(
      margin: EdgeInsets.all(200),
      child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
        Text('周期:' + _duration.toInt().toString() + " msec"),
        Container(
          margin: EdgeInsets.only(bottom: 30),
          child: Slider(
            value: _duration,
            min: 50.0,
            max: 1000.0,
            onChanged: (value) {
              setState(() {
                _duration = value;
              });
            },
          ),
        ),
        CustomPaint(
          painter: _CirclePainter(
            _radius,
            _backRadius,
            _animationRadius.value,
          ),
        ),
      ]),
    ));
  }
}

//美しい光点クラス
class _CirclePainter extends CustomPainter {
  final lightColor = Colors.yellow; //光点の色
  //後光の設定
  final Map<int, Map<String, double>> lightLayers = {
    0: {
      'maxOpacity': 0.5, //後光の濃さ
    },
    1: {
      'maxOpacity': 0.3,
    },
  };

  double _basicRadius; //光点の基本サイズ
  double _maxBackRadius; //後光の最大サイズ
  double _animationRadius; //後光アニメーションサイズ

  _CirclePainter(this._basicRadius, this._maxBackRadius, this._animationRadius);

  @override
  void paint(Canvas canvas, Size size) {
    var c = Offset(0, 0); //光点の表示位置x,y
    //光点の実体を配置
    canvas.drawCircle(
      c,
      _basicRadius,
      Paint()
        ..color = lightColor
        ..style = PaintingStyle.fill, //塗り潰しの円
    );

    var size = _basicRadius;

    //後光配列ループ
    for (var i = 0; i < lightLayers.length; i++) {
      var row = lightLayers[i]!;
      //後光の透明度の算出
      var opacity =
          _animationRadius * (row['maxOpacity']! / _maxBackRadius * -1) +
              row['maxOpacity']!;
      //誤差でmax-min外れないように
      opacity = opacity < 0.0 ? 0.0 : opacity;
      opacity = opacity > 1.0 ? 1.0 : opacity;
      //後光サイズ
      size += _animationRadius;
      //後光描画
      canvas.drawCircle(
        c,
        size,
        Paint()
          ..style = PaintingStyle.fill //塗り潰しの円
          ..color = lightColor.withOpacity(opacity),
      );
    }
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }
}

備考

一応、ポイントを2点説明しておきます。

アニメーションの自動繰り返しをしない

repeatではなくforwardでアニメ実行します。

    //repeatではなくforwardでアニメ実行
    //_animationController.repeat(reverse: false);
    _animationController.forward();

アニメーション終了時に周期を更新してアニメ再実行

AnimationController.addStatusListenerでアニメ状態変化時のイベントを定義して、
一連のアニメ終了時に周期を変更して再実行させます。
これによってアニメが中断されることは無く、きれいに周期変更できます。

周期の変更前にAnimationControllerのreset処理が必須です。

    _animationController.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        //アニメーションを一旦リセット
        _animationController.reset();
        //アニメーション終了時、周期を再設定
        _animationController.duration =
            Duration(milliseconds: _duration.toInt());
        //アニメーション再実行
        _animationController.forward();
      }
    });

さいごに

光るスライダーバー、光るテキストボックスについても解決しているので、
また暇な時にまとめてみます。

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