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 createState() => _ShiningStarsState(); } class _ShiningStarsState extends State 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 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.withOpacity(0.5), color.withOpacity(0.2)], stops: const [0.2, 1.0]); Shader get shader => gradient.createShader(Rect.fromCircle(center: startPos, radius: starSize / 2)); }