はじめに
前回の記事で光点のアニメーションを作りました。
その際に課題だった「アニメーション周期を可変にする方法」について、
一応解決していたのでまとめておきます。
完成品
スライダーで、アニメーション周期を1000~50msecの範囲で動的に変更できるようにした。
ソース
ほとんど前回の記事のまんまですが、
とりあえず全部貼ります。
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();
}
});
さいごに
光るスライダーバー、光るテキストボックスについても解決しているので、
また暇な時にまとめてみます。