課題
Flutterでポインター的な点の描画を作っていて、
ふと美しく光り輝かせたくなった。
多分どこかで誰かが同じような物を作ってると思いますが、
探しても(自分に分かるレベルの情報が)見当たらず自分で作ることにしました。
完成品
ソース
FlutterというかDartかつqiitaもよく分かっておらず、不備があったらすみません。
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:light/View/LightSpot.dart';
class LightSpot extends StatefulWidget {
LightSpot({Key? key}) : super(key: key);
@override
_LightSpot createState() => _LightSpot();
}
class _LightSpot extends State<LightSpot> with TickerProviderStateMixin {
final _radius = 10.0; //基本光点のサイズ
final _backRadius = 20.0; //後光のサイズ
late AnimationController _animationController;
late Animation<double> _animationRadius;
@override
void initState() {
//アニメーションコントローラの設定
_animationController = AnimationController(
duration: Duration(milliseconds: 1500), vsync: this);
//アニメーションの設定
_animationRadius =
Tween(begin: 0.0, end: _backRadius).animate(_animationController)
..addListener(() {
setState(() {});
});
//繰り返し、反転無し
_animationController.repeat(reverse: false);
super.initState();
}
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
margin: EdgeInsets.all(50),
child: 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;
}
}
#備考
・1.5秒ごとに輝きます。
・定数「_radius」が基本の点のサイズ設定。
・定数「_backRadius」で1後光のサイズ設定。
・lightLayersのアイテムを増やせば後光の層が増えます。
後光レベル10にしちまった状態。
ここまでやるともう光点とかじゃないですね。
あとlightLayersを連想配列にしてあるのは、色とか大きさも層毎に変えようとした名残です。
今や要素はmaxOpacityだけなので、普通に配列でいいと思います。
苦労したところ
基本円の描画
そもそもFlutterがよく分かっていない初心者なので、
普通に円を表示するだけでも大変でした。
透明度の計算
後光のサイズをアニメーションで徐々に大きくしているわけですが、
それに伴って徐々に透明にしたいと思いました。
でないとアニメーションの最後に後光がパッと消えてしまう感じになり、美しくないので。
サイズが10のとき透明度を0にしたい。
サイズが0のとき透明度を0.5にしたい。
ではサイズに何を掛けたり足したりすれば、適切な透明度になるのか?
ものすごく苦労して色々調べましたが、
最終的には「一次関数」というテクで解決できました。
これは中学校に行けば習得できるそうですが、
僕は知らなかったので即興で勉強しました。
光る頻度を可変にしたい (未解決)
そもそも論ですが、ただ光る点を表示するだけならgifアニメで十分です。
画像ではなくわざわざ処理で実装したかった理由は、
stateの状態によって輝きを早めたり遅くしたりさせたいからでした。
しかしながら、やりかたがぜんぜんわからない。
精進します。
2022/07/19 追記
解決したので下記の記事で説明しています。
まとめ
点を光らせるなんて単純な話だと思っていましたが、
裏ではこんなに色々やらないといけないんですね。
最新テクノロジーのFlutter先生なら楽勝なはずだと、ナメてました。
今回は光る点ですが、
応用次第で光るテキストボックスや光るチェックボックスも作れると思いました。
今後もいろいろなものを光らせていきたいと思います。