heart_fill_view.dart 4.4 KB

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