heart_fill_view.dart 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. import 'dart:math';
  2. import 'package:flutter/material.dart';
  3. import 'package:keyboard/utils/common_expand.dart';
  4. import '../resource/assets.gen.dart';
  5. class HeartFillAnimation extends StatefulWidget {
  6. const HeartFillAnimation({
  7. super.key,
  8. required this.fillProgress,
  9. this.width = 250.0,
  10. this.onControllerCreated,
  11. });
  12. final double fillProgress;
  13. final double width;
  14. @override
  15. State createState() => _HeartFillAnimationState();
  16. final void Function(HeartFillController controller)? onControllerCreated;
  17. }
  18. class HeartFillController {
  19. late void Function() start;
  20. late void Function() stop;
  21. }
  22. class _HeartFillAnimationState extends State<HeartFillAnimation>
  23. with SingleTickerProviderStateMixin {
  24. late AnimationController _waveController;
  25. @override
  26. void initState() {
  27. super.initState();
  28. _waveController = AnimationController(
  29. vsync: this,
  30. duration: const Duration(milliseconds: 1000),
  31. )..repeat();
  32. if (widget.onControllerCreated != null) {
  33. final controller = HeartFillController();
  34. controller.start = () => _waveController.repeat();
  35. controller.stop = () => _waveController.stop();
  36. widget.onControllerCreated!(controller);
  37. }
  38. }
  39. @override
  40. void dispose() {
  41. _waveController.dispose();
  42. super.dispose();
  43. }
  44. void start() {
  45. _waveController.repeat();
  46. }
  47. void stop() {
  48. _waveController.stop();
  49. }
  50. @override
  51. Widget build(BuildContext context) {
  52. final height = widget.width * 208 / 250;
  53. return Stack(
  54. alignment: Alignment.center,
  55. children: [
  56. AnimatedBuilder(
  57. animation: _waveController,
  58. builder: (context, child) {
  59. return CustomPaint(
  60. size: Size(widget.width, height),
  61. painter: HeartFillPainter(
  62. fillProgress: widget.fillProgress,
  63. waveOffset: -_waveController.value * 2 * pi,
  64. ),
  65. );
  66. },
  67. ),
  68. Image(
  69. image: Assets.images.bgHeartFill.provider(),
  70. width: widget.width,
  71. height: height,
  72. fit: BoxFit.fill,
  73. ),
  74. ],
  75. );
  76. }
  77. }
  78. class HeartFillPainter extends CustomPainter {
  79. HeartFillPainter({required this.fillProgress, required this.waveOffset});
  80. final double fillProgress;
  81. final double waveOffset;
  82. static final _originalHeartPath = _createOriginalHeartPath();
  83. static Path _createOriginalHeartPath() {
  84. final path = Path();
  85. path.moveTo(182.981, 0);
  86. path.cubicTo(158.332, 0, 136.822, 13.4803, 125.435, 33.4817);
  87. path.cubicTo(125.07, 34.1387, 124.121, 34.1387, 123.756, 33.4817);
  88. path.cubicTo(112.344, 13.4803, 90.8336, 0, 66.1847, 0);
  89. path.cubicTo(29.6371, 0, 0, 29.6371, 0, 66.1847);
  90. path.cubicTo(0, 134.504, 85.9676, 188.719, 115.078, 205.119);
  91. path.cubicTo(121.041, 208.478, 128.123, 208.473, 134.081, 205.106);
  92. path.cubicTo(163.184, 188.66, 249.166, 134.307, 249.166, 66.1847);
  93. path.cubicTo(249.166, 29.6371, 219.529, 0, 182.981, 0);
  94. return path;
  95. }
  96. @override
  97. void paint(Canvas canvas, Size size) {
  98. // 应用缩放矩阵
  99. final scaleX = size.width / 250;
  100. final scaleY = size.height / 208;
  101. final matrix = Matrix4.identity()..scale(scaleX, scaleY);
  102. // 创建缩放后的爱心路径
  103. final heartPath = _originalHeartPath.transform(matrix.storage);
  104. // 设置裁剪区域
  105. canvas.save();
  106. canvas.clipPath(heartPath);
  107. // 计算填充高度
  108. final fillHeight = size.height * (1 - fillProgress);
  109. final fillRect = Rect.fromLTRB(0, fillHeight, size.width, size.height);
  110. // 绘制次级波浪
  111. final subWavePath = _createWavePath(
  112. fillHeight,
  113. size,
  114. offset: -pi / 5,
  115. amplitude: 15,
  116. frequency: 0.02,
  117. );
  118. canvas.drawPath(
  119. subWavePath,
  120. Paint()
  121. ..shader = LinearGradient(
  122. colors: ["#66FFB9D5".color, "#66FF219B".color],
  123. ).createShader(fillRect),
  124. );
  125. // 绘制主波浪
  126. final wavePath = _createWavePath(fillHeight, size);
  127. canvas.drawPath(
  128. wavePath,
  129. Paint()
  130. ..shader = LinearGradient(
  131. colors: ["#FF458C".color, "#FF74BC".color],
  132. ).createShader(fillRect),
  133. );
  134. canvas.restore();
  135. }
  136. Path _createWavePath(
  137. double fillHeight,
  138. Size size, {
  139. double offset = 0,
  140. double amplitude = 10.0,
  141. double frequency = 0.03,
  142. }) {
  143. final path = Path()..moveTo(0, fillHeight);
  144. for (double x = 0; x <= size.width; x++) {
  145. final y =
  146. fillHeight + sin(x * frequency + waveOffset + offset) * amplitude;
  147. path.lineTo(x, y);
  148. }
  149. path
  150. ..lineTo(size.width, size.height)
  151. ..lineTo(0, size.height)
  152. ..close();
  153. return path;
  154. }
  155. @override
  156. bool shouldRepaint(HeartFillPainter oldDelegate) {
  157. return fillProgress != oldDelegate.fillProgress ||
  158. waveOffset != oldDelegate.waveOffset;
  159. }
  160. }