star.dart 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. import 'dart:math';
  2. import 'package:flutter/material.dart';
  3. class ShiningStars extends StatefulWidget {
  4. final int count; // 星星个数
  5. final double size; // 星星尺寸
  6. final int duration; // 动画时长, ms
  7. const ShiningStars({super.key, this.count = 20, this.size = 100, this.duration = 1200});
  8. @override
  9. State<StatefulWidget> createState() => _ShiningStarsState();
  10. }
  11. class _ShiningStarsState extends State<ShiningStars> with SingleTickerProviderStateMixin {
  12. late AnimationController controller;
  13. Stars? stars;
  14. @override
  15. void initState() {
  16. controller = AnimationController(
  17. vsync: this,
  18. value: 0,
  19. duration: Duration(milliseconds: widget.duration),
  20. )..forward();
  21. super.initState();
  22. }
  23. @override
  24. void dispose() {
  25. controller.dispose();
  26. super.dispose();
  27. }
  28. @override
  29. Widget build(BuildContext context) {
  30. return SizedBox(
  31. width: double.infinity,
  32. height: double.infinity,
  33. child: LayoutBuilder(
  34. builder: (context, constraints) {
  35. final Size biggest = constraints.biggest;
  36. stars = Stars(canvasSize: biggest, starSize: widget.size, count: widget.count);
  37. return CustomPaint(painter: StarPainter(controller, stars!));
  38. },
  39. ),
  40. );
  41. }
  42. }
  43. class StarPainter extends CustomPainter {
  44. final AnimationController controller;
  45. final Stars stars;
  46. StarPainter(this.controller, this.stars) : super(repaint: controller);
  47. @override
  48. void paint(Canvas canvas, Size size) {
  49. if (!controller.isAnimating) return;
  50. stars.update(controller.value);
  51. stars.draw(canvas);
  52. }
  53. @override
  54. bool shouldRepaint(CustomPainter oldDelegate) {
  55. return false;
  56. }
  57. }
  58. class Stars {
  59. final int count; // 星星数量
  60. final double starSize; // 星星尺寸大小
  61. final Size canvasSize; // 画布尺寸
  62. final List<StarParticle> particles = [];
  63. Stars({required this.canvasSize, this.starSize = 100, this.count = 10}) {
  64. final list = List.generate(count, (index) => StarParticle(canvasSize: canvasSize, starSize: starSize));
  65. particles.addAll(list);
  66. }
  67. update(double t) {
  68. for (var p in particles) {
  69. p.update(t);
  70. }
  71. }
  72. draw(canvas) {
  73. for (var p in particles) {
  74. p.draw(canvas);
  75. }
  76. }
  77. }
  78. class StarParticle {
  79. final double starSize; //星星尺寸大小
  80. final Size canvasSize; // 画布尺寸
  81. final Path path;
  82. final Color color;
  83. late Offset startPos; // 起点
  84. late Offset endPos; // 终点
  85. Offset translate; // 当前位置(根据 animation.value 动态计算)
  86. double scale; // 当前缩放系数(根据 animation.value 动态计算)
  87. double angle; // 当前旋转角度(根据 animation.value 动态计算)
  88. late int rotateDirection; // 旋转方向
  89. StarParticle({required this.canvasSize, this.starSize = 100, this.color = const Color.fromARGB(255, 242, 207, 31)})
  90. : path = Path(),
  91. translate = Offset.zero,
  92. scale = 1.0,
  93. angle = 0 {
  94. Offset center = canvasSize.center(Offset.zero);
  95. Random random = Random();
  96. // startPos 限定在中心 100x100 的矩形内
  97. startPos = Offset(center.dx + randomDoubleInRange(-100, 100), center.dy + randomDoubleInRange(-50, 50));
  98. // endPos 完全随机
  99. endPos = Offset(
  100. center.dx + randomDoubleInRange(-canvasSize.width / 2, canvasSize.width / 2),
  101. center.dy + randomDoubleInRange(-canvasSize.height / 2, canvasSize.height / 2),
  102. );
  103. // endPos 固定在边界
  104. // int rand = random.nextInt(4);
  105. // if (rand == 0) {
  106. // endPos = Offset(0.0, random.nextDouble() * canvasSize.height);
  107. // } else if (rand == 1) {
  108. // endPos = Offset(canvasSize.width, random.nextDouble() * canvasSize.height);
  109. // } else if (rand == 2) {
  110. // endPos = Offset(random.nextDouble() * canvasSize.width, 0.0);
  111. // } else if (rand == 3) {
  112. // endPos = Offset(random.nextDouble() * canvasSize.width, canvasSize.height);
  113. // }
  114. path
  115. ..moveTo(startPos.dx, startPos.dy - starSize * 0.8 / 2)
  116. ..quadraticBezierTo(startPos.dx, startPos.dy, startPos.dx + starSize * 0.75 / 2, startPos.dy)
  117. ..quadraticBezierTo(startPos.dx, startPos.dy, startPos.dx, startPos.dy + starSize * 0.75 / 2)
  118. ..quadraticBezierTo(startPos.dx, startPos.dy, startPos.dx - starSize * 0.75 / 2, startPos.dy)
  119. ..quadraticBezierTo(startPos.dx, startPos.dy, startPos.dx, startPos.dy - starSize * 0.75 / 2);
  120. rotateDirection = random.nextBool() ? 1 : -1;
  121. }
  122. double randomDoubleInRange(double min, double max) {
  123. final random = Random();
  124. final range = max - min;
  125. final randomValue = random.nextDouble() * range + min;
  126. return randomValue;
  127. }
  128. update(double t) {
  129. scale = Curves.easeOut.transform(1 - t);
  130. angle = t * pi * rotateDirection / 4;
  131. translate = Tween(begin: Offset.zero, end: endPos - startPos).transform(t);
  132. }
  133. draw(Canvas canvas) {
  134. canvas.save();
  135. Matrix4 m = Matrix4.identity()
  136. ..translate(startPos.dx, startPos.dy)
  137. ..translate(translate.dx, translate.dy)
  138. ..scale(scale)
  139. ..rotateZ(angle)
  140. ..translate(-startPos.dx, -startPos.dy);
  141. canvas.transform(m.storage);
  142. // 绘制光晕
  143. Paint paint = Paint()
  144. ..color = color
  145. ..style = PaintingStyle.fill
  146. ..shader = shader
  147. ..maskFilter = MaskFilter.blur(BlurStyle.normal, starSize / 6);
  148. canvas.drawCircle(startPos, starSize / 2, paint);
  149. // 绘制四角星
  150. paint.shader = null;
  151. paint.maskFilter = null;
  152. canvas.drawPath(path, paint);
  153. canvas.restore();
  154. }
  155. RadialGradient get gradient =>
  156. RadialGradient(center: Alignment.center, colors: <Color>[color.withOpacity(0.5), color.withOpacity(0.2)], stops: const <double>[0.2, 1.0]);
  157. Shader get shader => gradient.createShader(Rect.fromCircle(center: startPos, radius: starSize / 2));
  158. }