import 'dart:math'; import 'package:flutter/material.dart'; import 'package:keyboard/utils/common_expand.dart'; import '../resource/assets.gen.dart'; class HeartFillAnimation extends StatefulWidget { const HeartFillAnimation({ super.key, required this.fillProgress, this.width = 250.0, }); final double fillProgress; final double width; @override State createState() => _HeartFillAnimationState(); } class _HeartFillAnimationState extends State with SingleTickerProviderStateMixin { late AnimationController _waveController; @override void initState() { super.initState(); _waveController = AnimationController( vsync: this, duration: const Duration(milliseconds: 1000), )..repeat(); } @override void dispose() { _waveController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final height = widget.width * 208 / 250; return Stack( alignment: Alignment.center, children: [ AnimatedBuilder( animation: _waveController, builder: (context, child) { return CustomPaint( size: Size(widget.width, height), painter: HeartFillPainter( fillProgress: widget.fillProgress, waveOffset: -_waveController.value * 2 * pi, ), ); }, ), Image( image: Assets.images.bgHeartFill.provider(), width: widget.width, height: height, fit: BoxFit.fill, ), ], ); } } class HeartFillPainter extends CustomPainter { HeartFillPainter({required this.fillProgress, required this.waveOffset}); final double fillProgress; final double waveOffset; static final _originalHeartPath = _createOriginalHeartPath(); static Path _createOriginalHeartPath() { final path = Path(); path.moveTo(182.981, 0); path.cubicTo(158.332, 0, 136.822, 13.4803, 125.435, 33.4817); path.cubicTo(125.07, 34.1387, 124.121, 34.1387, 123.756, 33.4817); path.cubicTo(112.344, 13.4803, 90.8336, 0, 66.1847, 0); path.cubicTo(29.6371, 0, 0, 29.6371, 0, 66.1847); path.cubicTo(0, 134.504, 85.9676, 188.719, 115.078, 205.119); path.cubicTo(121.041, 208.478, 128.123, 208.473, 134.081, 205.106); path.cubicTo(163.184, 188.66, 249.166, 134.307, 249.166, 66.1847); path.cubicTo(249.166, 29.6371, 219.529, 0, 182.981, 0); return path; } @override void paint(Canvas canvas, Size size) { // 应用缩放矩阵 final scaleX = size.width / 250; final scaleY = size.height / 208; final matrix = Matrix4.identity()..scale(scaleX, scaleY); // 创建缩放后的爱心路径 final heartPath = _originalHeartPath.transform(matrix.storage); // 设置裁剪区域 canvas.save(); canvas.clipPath(heartPath); // 计算填充高度 final fillHeight = size.height * (1 - fillProgress); final fillRect = Rect.fromLTRB(0, fillHeight, size.width, size.height); // 绘制次级波浪 final subWavePath = _createWavePath( fillHeight, size, offset: -pi / 5, amplitude: 15, frequency: 0.02, ); canvas.drawPath( subWavePath, Paint() ..shader = LinearGradient( colors: ["#66FFB9D5".color, "#66FF219B".color], ).createShader(fillRect), ); // 绘制主波浪 final wavePath = _createWavePath(fillHeight, size); canvas.drawPath( wavePath, Paint() ..shader = LinearGradient( colors: ["#FF458C".color, "#FF74BC".color], ).createShader(fillRect), ); canvas.restore(); } Path _createWavePath( double fillHeight, Size size, { double offset = 0, double amplitude = 10.0, double frequency = 0.03, }) { final path = Path()..moveTo(0, fillHeight); for (double x = 0; x <= size.width; x++) { final y = fillHeight + sin(x * frequency + waveOffset + offset) * amplitude; path.lineTo(x, y); } path ..lineTo(size.width, size.height) ..lineTo(0, size.height) ..close(); return path; } @override bool shouldRepaint(HeartFillPainter oldDelegate) { return fillProgress != oldDelegate.fillProgress || waveOffset != oldDelegate.waveOffset; } }