heart_fill_view.dart 4.3 KB

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