Flutter 中如何手动绘制一个水滴球(波浪球)(水滴图)
效果预览
本组件实现了一个具有动态波浪效果的液体填充球,支持以下特性:
- 🌊 可调节的波浪动画速度
- 🎨 自定义波浪颜色或渐变色
- 🔢 精确的百分比控制(0.0-1.0)
- 📏 灵活调整容器尺寸和边距
- 🖼️ 可定制的圆形边框样式
Pub.dev 发布地址:https://pub.dev/packages/liquid_ball
Github预览图:https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=https%3A%2F%2Fraw.githubusercontent.com%2FAzad-Zhang%2Fmy_image%2Frefs%2Fheads%2Fmain%2Fliquid_ball.gif&pos_id=img-ZU8IuFBI-1747406058835)
快速开始
1. 基础代码结构(核心代码解析)
// 组件核心结构
LiquidBallWidget(
waveColor: Colors.blue, // 单色模式
percentage: 0.75, // 填充比例(重要参数)
)
代码说明:
- 使用
LiquidBallWidget作为容器 waveColor控制波浪颜色(与waveGradient二选一)percentage是关键控制参数(0.0=空,1.0=满)
2. 完整组件树示例
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: LiquidBallWidget(
waveGradient: LinearGradient(
colors: [Colors.purple[300]!, Colors.blue[200]!],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
containerSize: 150,
percentage: 0.6,
containerBorder: Border.all(
color: Colors.amber,
width: 2,
style: BorderStyle.solid,
),
),
),
);
}
核心参数深度解析
波浪控制参数
// 在 _LiquidWavePainter 类中
void paint(Canvas canvas, Size size) {
final baseHeight = size.height * (1 - percentage); // 核心计算公式
// ...
}
实现原理:
percentage通过1 - percentage转换为绘制基准线- 示例:当 percentage=0.7 时:
- 计算基准线:100px 容器 → 100*(1-0.7)=30px 从底部开始
- 波浪在容器上部 70% 区域波动
动画控制逻辑
// 在 _LiquidBallState 类中
void initState() {
_waveController = AnimationController(
vsync: this,
duration: widget.animationDuration,
)..repeat(); // 关键动画控制方法
_waveAnimation = _waveController.drive(
Tween(begin: 0.0, end: 1.0), // 生成0→1的连续值
);
}
动画流程:
repeat()方法启动无限循环动画- 每帧通过
_waveAnimation.value获取当前进度值(0.0-1.0) - 该值传递给
_LiquidWavePainter驱动波浪运动
高级定制指南
1. 波形特征调整
// 修改 _LiquidWavePainter 类中的常量
const waveAmplitude = 20.0; // 波浪高度(默认16)
const waveFrequency = 4.0; // 波浪密度(默认3.5)
// 在_createWavePath方法中:
final radians = (x / size.width * waveFrequency * pi) + (phase * 2 * pi);
final y = baseHeight + waveAmplitude * sin(radians); // 正弦波公式
参数效果:
waveAmplitude增大 → 波浪起伏更剧烈waveFrequency增大 → 波浪密度更高
2. 动态水位控制
// 示例:实现水位渐变动画
AnimatedLiquidBallWidget({Key? key, required double targetPercentage})
: super(key: key) {
// 使用显式动画控制
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
_animation = Tween(begin: 0.0, end: targetPercentage)
.animate(_controller);
}
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (context, _) {
return LiquidBallWidget(
percentage: _animation.value,
// ...其他参数
);
},
);
}
实现原理图解
绘制流程分解
-
容器裁剪
canvas.clipPath(Path()..addOval(rect)); // 圆形裁剪区域- 创建圆形绘制区域
- 限制波浪绘制范围
-
波浪路径生成
for (x in -50...width+50) { y = baseHeight + 16 * sin(x/width*3.5π + phase*2π) }- 使用正弦函数生成波浪曲线
- 添加水平填充防止边缘空隙
-
颜色填充
Paint() ..shader = gradient.createShader(rect) // 渐变着色器 ..color = waveColor // 单色模式- 根据参数选择着色方式
- 支持线性/径向渐变
参数详解
核心参数
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
percentage | double | ✔️ | 0.5 | 液体填充比例 (0.0-1.0) |
waveColor | Color | ✖️ | null | 波浪单色(与渐变色二选一) |
waveGradient | Gradient | ✖️ | null | 波浪渐变色(与单色二选一) |
样式参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
containerSize | double | 100 | 容器直径 |
containerPadding | double | 10 | 内边距 |
containerBorder | Border | 红色边框 | 容器边框样式 |
动画参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
animationDuration | Duration | 2秒 | 完整波浪周期时长 |
注意事项
1. 参数互斥规则
// 正确用法
waveColor: Colors.blue // 或单独使用 waveGradient
// 错误用法 ❌
waveColor: Colors.blue,
waveGradient: myGradient // 同时设置会抛出异常
2. 性能优化建议
- 避免在动画过程中频繁重建组件
- 当需要高频更新时,考虑将动画控制器提升到上层
- 对于复杂渐变,建议预创建 Gradient 对象
完整示例代码
import 'package:flutter/material.dart';
class DemoPage extends StatefulWidget {
_DemoPageState createState() => _DemoPageState();
}
class _DemoPageState extends State<DemoPage> {
double _currentLevel = 0.3;
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Liquid Ball Demo')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
LiquidBallWidget(
waveGradient: LinearGradient(
colors: [Colors.teal, Colors.cyan],
stops: [0.3, 1.0],
),
percentage: _currentLevel,
containerSize: 180,
containerBorder: Border.all(
color: Colors.indigo,
width: 3,
),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () => setState(() {
_currentLevel = (_currentLevel + 0.1).clamp(0.0, 1.0);
}),
child: Text('Add 10%'),
)
],
),
),
);
}
}
常见问题解答
Q:如何实现垂直方向上的波浪效果?
A:修改绘制逻辑,将正弦波应用在Y轴方向,调整坐标计算方式
Q:能否支持不规则容器形状?
A:修改 clipPath 的裁剪路径即可支持任意形状
Q:动画出现卡顿怎么优化?
A:尝试以下方法:
- 减少波浪频率值
- 降低动画帧率
- 使用性能更好的渐变类型
Q:如何添加水面反光效果?
A:可在现有绘制逻辑基础上叠加半透明高光层,使用二次贝塞尔曲线创建反光形状
本组件已在 Flutter 3.13 版本测试通过,如有任何使用问题或改进建议,欢迎在评论区留言讨论。
3486

被折叠的 条评论
为什么被折叠?



