| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 |
- import 'dart:math';
- import 'package:flutter/material.dart';
- class ShiningStars extends StatefulWidget {
- final int count; // 星星个数
- final double size; // 星星尺寸
- final int duration; // 动画时长, ms
- const ShiningStars({super.key, this.count = 20, this.size = 100, this.duration = 1200});
- @override
- State<StatefulWidget> createState() => _ShiningStarsState();
- }
- class _ShiningStarsState extends State<ShiningStars> with SingleTickerProviderStateMixin {
- late AnimationController controller;
- Stars? stars;
- @override
- void initState() {
- controller = AnimationController(
- vsync: this,
- value: 0,
- duration: Duration(milliseconds: widget.duration),
- )..forward();
- super.initState();
- }
- @override
- void dispose() {
- controller.dispose();
- super.dispose();
- }
- @override
- Widget build(BuildContext context) {
- return SizedBox(
- width: double.infinity,
- height: double.infinity,
- child: LayoutBuilder(
- builder: (context, constraints) {
- final Size biggest = constraints.biggest;
- stars = Stars(canvasSize: biggest, starSize: widget.size, count: widget.count);
- return CustomPaint(painter: StarPainter(controller, stars!));
- },
- ),
- );
- }
- }
- class StarPainter extends CustomPainter {
- final AnimationController controller;
- final Stars stars;
- StarPainter(this.controller, this.stars) : super(repaint: controller);
- @override
- void paint(Canvas canvas, Size size) {
- if (!controller.isAnimating) return;
- stars.update(controller.value);
- stars.draw(canvas);
- }
- @override
- bool shouldRepaint(CustomPainter oldDelegate) {
- return false;
- }
- }
- class Stars {
- final int count; // 星星数量
- final double starSize; // 星星尺寸大小
- final Size canvasSize; // 画布尺寸
- final List<StarParticle> particles = [];
- Stars({required this.canvasSize, this.starSize = 100, this.count = 10}) {
- final list = List.generate(count, (index) => StarParticle(canvasSize: canvasSize, starSize: starSize));
- particles.addAll(list);
- }
- update(double t) {
- for (var p in particles) {
- p.update(t);
- }
- }
- draw(canvas) {
- for (var p in particles) {
- p.draw(canvas);
- }
- }
- }
- class StarParticle {
- final double starSize; //星星尺寸大小
- final Size canvasSize; // 画布尺寸
- final Path path;
- final Color color;
- late Offset startPos; // 起点
- late Offset endPos; // 终点
- Offset translate; // 当前位置(根据 animation.value 动态计算)
- double scale; // 当前缩放系数(根据 animation.value 动态计算)
- double angle; // 当前旋转角度(根据 animation.value 动态计算)
- late int rotateDirection; // 旋转方向
- StarParticle({required this.canvasSize, this.starSize = 100, this.color = const Color.fromARGB(255, 242, 207, 31)})
- : path = Path(),
- translate = Offset.zero,
- scale = 1.0,
- angle = 0 {
- Offset center = canvasSize.center(Offset.zero);
- Random random = Random();
- // startPos 限定在中心 100x100 的矩形内
- startPos = Offset(center.dx + randomDoubleInRange(-100, 100), center.dy + randomDoubleInRange(-50, 50));
- // endPos 完全随机
- endPos = Offset(
- center.dx + randomDoubleInRange(-canvasSize.width / 2, canvasSize.width / 2),
- center.dy + randomDoubleInRange(-canvasSize.height / 2, canvasSize.height / 2),
- );
- // endPos 固定在边界
- // int rand = random.nextInt(4);
- // if (rand == 0) {
- // endPos = Offset(0.0, random.nextDouble() * canvasSize.height);
- // } else if (rand == 1) {
- // endPos = Offset(canvasSize.width, random.nextDouble() * canvasSize.height);
- // } else if (rand == 2) {
- // endPos = Offset(random.nextDouble() * canvasSize.width, 0.0);
- // } else if (rand == 3) {
- // endPos = Offset(random.nextDouble() * canvasSize.width, canvasSize.height);
- // }
- path
- ..moveTo(startPos.dx, startPos.dy - starSize * 0.8 / 2)
- ..quadraticBezierTo(startPos.dx, startPos.dy, startPos.dx + starSize * 0.75 / 2, startPos.dy)
- ..quadraticBezierTo(startPos.dx, startPos.dy, startPos.dx, startPos.dy + starSize * 0.75 / 2)
- ..quadraticBezierTo(startPos.dx, startPos.dy, startPos.dx - starSize * 0.75 / 2, startPos.dy)
- ..quadraticBezierTo(startPos.dx, startPos.dy, startPos.dx, startPos.dy - starSize * 0.75 / 2);
- rotateDirection = random.nextBool() ? 1 : -1;
- }
- double randomDoubleInRange(double min, double max) {
- final random = Random();
- final range = max - min;
- final randomValue = random.nextDouble() * range + min;
- return randomValue;
- }
- update(double t) {
- scale = Curves.easeOut.transform(1 - t);
- angle = t * pi * rotateDirection / 4;
- translate = Tween(begin: Offset.zero, end: endPos - startPos).transform(t);
- }
- draw(Canvas canvas) {
- canvas.save();
- Matrix4 m = Matrix4.identity()
- ..translate(startPos.dx, startPos.dy)
- ..translate(translate.dx, translate.dy)
- ..scale(scale)
- ..rotateZ(angle)
- ..translate(-startPos.dx, -startPos.dy);
- canvas.transform(m.storage);
- // 绘制光晕
- Paint paint = Paint()
- ..color = color
- ..style = PaintingStyle.fill
- ..shader = shader
- ..maskFilter = MaskFilter.blur(BlurStyle.normal, starSize / 6);
- canvas.drawCircle(startPos, starSize / 2, paint);
- // 绘制四角星
- paint.shader = null;
- paint.maskFilter = null;
- canvas.drawPath(path, paint);
- canvas.restore();
- }
- RadialGradient get gradient =>
- RadialGradient(center: Alignment.center, colors: <Color>[color.withOpacity(0.5), color.withOpacity(0.2)], stops: const <double>[0.2, 1.0]);
- Shader get shader => gradient.createShader(Rect.fromCircle(center: startPos, radius: starSize / 2));
- }
|