和尚今天尝试一下绘制波浪的效果,虽然 pub 仓库中已经有成熟的插件,但和尚还是准备用之前学习的 CanvasAnimation 尝试自定义一个 ACEWave

1. 绘制曲线

绘制波浪首先需要绘制曲线,采用 Canvas 绘制贝塞尔曲线;常用的是数学中通常用的 sin(x) / cos(y) 函数即可;

其中和尚通过 Canvas 绘制时使用了 path.quadraticBezierTo 来绘制从第一个 Point 到另一个 Point 的贝塞尔曲线;

class _ACEWavePainter extends CustomPainter {  @override  void paint(Canvas canvas, Size size) {    Paint paint = Paint()      ..color = Colors.red..strokeCap = StrokeCap.round      ..strokeWidth = 10..style = PaintingStyle.stroke;    Path path = Path()      ..moveTo(0, 500)      ..quadraticBezierTo(size.width / 4, 300, size.width / 2, 500)      ..quadraticBezierTo(size.width / 4 * 3, 700, size.width, 500);    canvas.drawPath(path, paint);  }

  @override  bool shouldRepaint(CustomPainter oldDelegate) => false;}

2. 循环动画

和尚使用最常用的平移动画来让曲线动起来,其中注意的是:

  1. 当第一次动画结束时,通过 controller.repeat() 来实现循环播放;

  2. 动画需要使用 Curves.linear 线性动画,否则在循环播放过程中衔接不顺畅;

  3. 使用动画时均需在生命周期结束时 dispose() 销毁动画;

class _ACEWaveState extends State<ACEWave> with TickerProviderStateMixin {  AnimationController _waveController;  Animation<double> _waveAnimation;  int _duration = 2000;  CurvedAnimation _curvedAnimation;

  @override  Widget build(BuildContext context) {    return Transform.translate(        offset: Offset(MediaQuery.of(context).size.width * _curvedAnimation.value, 0.0),        child: Container(width: MediaQuery.of(context).size.width,            child: CustomPaint(painter: _ACEWavePainter())));  }

  _initAnimations() {    _waveController = AnimationController(duration: Duration(milliseconds: _duration), vsync: this);    _curvedAnimation = CurvedAnimation(parent: _waveController, curve: Curves.linear);    _waveAnimation = Tween(begin: 0.0, end: 1.0).animate(_waveController);    _waveAnimation.addListener(() => setState(() {}));    _waveController.forward();    _waveAnimation.addStatusListener((status) {      switch (status) {        case AnimationStatus.completed:          _waveController.repeat();          break;        case AnimationStatus.dismissed:          _waveController.forward();          break;        default:          break;      }    });  }

  _disposeAnimations() {    _waveController.dispose();  }

  @override  void initState() {    super.initState();    _initAnimations();  }

  @override  void dispose() {    _disposeAnimations();    super.dispose();  }}

3. 增加波浪周期

在执行循环动画之后,发现动画过程中,会有一半是空白的,此时我们增加波浪的周期即可,多绘制一个屏幕的波浪即可,和尚建议前后多绘制两个屏幕的曲线,在循环过程中更流畅;

Path path = Path()  ..moveTo(0 - size.width, 500)  ..quadraticBezierTo(size.width / 4 - size.width, 300, size.width / 2 - size.width, 500)  ..quadraticBezierTo(size.width / 4 * 3 - size.width, 700, size.width - size.width, 500)  ..quadraticBezierTo(size.width / 4, 300, size.width / 2, 500)  ..quadraticBezierTo(size.width / 4 * 3, 700, size.width, 500);

canvas.drawPath(path, paint);

4. 调整波浪起始位置

和尚尝试的曲线是 sin(x) 方式的,起始位置都是 (0.0, 0.0),然而多条波浪时不会都从起点开始;于是和尚提供了一个初始位置,来错开各波浪展示位置;

Path path = Path()  ..moveTo(0 - size.width - startOffset, 500)  ..quadraticBezierTo(size.width / 4 - size.width - startOffset,      500 - waveHeight, size.width / 2 - size.width - startOffset, 500)  ..quadraticBezierTo(size.width / 4 * 3 - size.width - startOffset,      500 + waveHeight, size.width - size.width - startOffset, 500)  ..quadraticBezierTo(size.width / 4 - startOffset, 500 - waveHeight,      size.width / 2 - startOffset, 500)  ..quadraticBezierTo(size.width / 4 * 3 - startOffset, 500 + waveHeight,      size.width - startOffset, 500)  ..quadraticBezierTo(size.width / 4 + size.width - startOffset,      500 - waveHeight, size.width / 2 + size.width - startOffset, 500)  ..quadraticBezierTo(size.width / 4 * 3 + size.width - startOffset,      500 + waveHeight, size.width + size.width - startOffset, 500);

5. 调整波浪宽度和峰值

和尚调整完波浪起始位置之后对于波浪的宽度和峰值也要进行调整,保证每条波浪效果略有不同;

和尚预先绘制了前中后三个屏幕曲线,在测试过程中,若屏幕并非是曲线周期倍数时,衔接过程中会有空余,如图;

于是和尚计算波浪完整周期倍数与屏幕宽的差值作为移动点 moveTo 的附加宽度即可;

for (int i = 0; i   path..moveTo(waveWidth * i - size.width - startOffset, 500.0)    ..quadraticBezierTo(        _quaterWidth + waveWidth * i - size.width - startOffset,        500 - waveHeight,        _quaterWidth * 2 + waveWidth * i - size.width - startOffset,        500.0)    ..moveTo(        _quaterWidth * 2 + waveWidth * i - size.width - startOffset, 500.0)    ..quadraticBezierTo(        _quaterWidth * 3 + waveWidth * i - size.width - startOffset,        500 + waveHeight,        _quaterWidth * 4 + waveWidth * i - size.width - startOffset,        500.0)    ..moveTo(waveWidth * i + startOffset + (plusWidth), 500.0)    ..quadraticBezierTo(        _quaterWidth + waveWidth * i + startOffset + plusWidth,        500 - waveHeight,        _quaterWidth * 2 + waveWidth * i + startOffset + plusWidth,        500.0)    ..moveTo(        _quaterWidth * 2 + waveWidth * i + startOffset + plusWidth, 500.0)    ..quadraticBezierTo(        _quaterWidth * 3 + waveWidth * i + startOffset + plusWidth,        500 + waveHeight,        _quaterWidth * 4 + waveWidth * i + startOffset + plusWidth,        500.0)    ..moveTo(waveWidth * i - size.width + startOffset, 500.0)    ..quadraticBezierTo(        _quaterWidth + waveWidth * i - size.width + startOffset,        500 - waveHeight,        _quaterWidth * 2 + waveWidth * i - size.width + startOffset,        500.0)    ..moveTo(        _quaterWidth * 2 + waveWidth * i - size.width + startOffset, 500.0)    ..quadraticBezierTo(        _quaterWidth * 3 + waveWidth * i - size.width + startOffset,        500 + waveHeight,        _quaterWidth * 4 + waveWidth * i - size.width + startOffset,        500.0);}


至此,一个基本的波浪模型基本完成,但还有很多优化的方面,和尚在下篇中进一步绘制波浪效果;如有错误,请多多指导!

来源:阿策小和尚

flutter 图解_【Flutter 专题】83 图解自定义 ACEWave 波浪 Widget (一)相关推荐

  1. 双风扇安装图解_汽车灯光标志图解,汽车灯光标志大全图解

    对于新手司机,汽车灯光开关上标志总是有时候会分不清楚,下面我们一起通过汽车灯光标志大全来进行图解. 汽车灯光标志图解,汽车灯光标志大全图解 汽车灯光开关有两种,一种是旋杆式车灯开关,另一种是旋钮式灯光 ...

  2. 纸的大小图解_折纸大全图解基础之如何裁切美元尺寸纸张

    本育儿文章是育儿天堂最新发布的<折纸大全图解基础之如何裁切美元尺寸纸张>的详细页面,觉得有用就收藏了,这里给大家转摘到育儿天堂,为了大家阅读方便. 折纸大全图解中有一类折纸教程是比力奇特的 ...

  3. 各种水龙头拆卸图解_扭力扳手使用方法图解与注意事项、原理、种类

    近年来,我国经济经济发展迅速,随着扭力扳手的使用和要求提升,传统的扭力扳手中也逐渐演变出了设定式棘轮扭力扳手.数显式的扭力扳手和预置式扭力扳手等等.那么这些扭力扳手如何使用?如何确保精度的准确性呢?一 ...

  4. 小提琴1234567位置图解_小提琴1234567位置图解 琴不在身边不然可以直接给你看

    聊到小提琴,我们很多人都知道,有人问小提琴1234567位置图解,另外,还有人问小提琴1234567位置图解,这到底是咋回事?其实小提琴1234567位置图解呢,今天我们就来看看小提琴1234567位 ...

  5. 小提琴1234567位置图解_小提琴1234567位置图解

    说到小提琴,相信大家应该都不会陌生,经常有人问我小提琴1234567位置在哪里,也有人找我要1234567位置图解,这令我非常的纳闷.今天小编为你们整理的小提琴1234567位置图解,应该会对大家有所 ...

  6. 频谱仪使用方法图解_钳形电流表使用方法图解

    钳形电流表的使用方法简单,测量电流时只需要将正在运行的待测导线夹入钳形电流表的钳形铁芯内,然后读取数显屏或指示盘上的读数即可.使用很简单吧,夹住测量导线就行了.不过现在数字钳形电流表的广泛使用,给钳形 ...

  7. 各种水龙头拆卸图解_[各种水龙头拆卸图解]水龙头漏水怎么办

    导语:水龙头用久了都会有漏水的情况,不仅造成水资源的浪费,也让漏水的周围一直处于潮湿的情况,那么水龙头漏水怎么办呢?下面就由jy135小编给大家分享几种解决水龙头漏水的方法,大家一起去看看吧. 水龙头 ...

  8. 15数字华容道解法 图解_数字华容道解法图解 数字华容道玩法介绍

    数字华容道,一款全新的经典的益智类型数字APP.目的是用最少的步数,*短时间将棋盘上的数字方块,按照从左到右,从上到下的顺序重新排列整齐.以往华容道游戏基于三国背景,需将棋子移动到出口.现在以数字为形 ...

  9. 格力机器人图解_格力空调拆机图解,这张图看完立马成专家

    摘要:格力空调室内机拆解 格力空调器室内机是通过电路板及各电器部件的连接实现对制冷循环的控制,因此,学习空调器室内机的维修,首先应掌握空调器室内机的拆卸方法. 如图12-1所示... 格力空调室内机拆 ...

最新文章

  1. android设置tls版本,Android O移除HttpsURLConnection中不安全的TLS版本回退
  2. SpringBoot使用Gradle构建war包
  3. (转)SpringMVC学习(三)——SpringMVC的配置文件
  4. ST CUBEMX 修改MCU型号
  5. PowerDesigner 15.1 安装步骤详细图解及破解
  6. leetcode 1734. 解码异或后的排列(位运算)
  7. UVA10763:Foreign ExchangeUVA10340: All in All(水题)
  8. 玩转Python? 一文总结30种Python的窍门和技巧,不可错过哈!
  9. 邮件发送类_SpringBoot优雅地发送邮件
  10. C#写一个URL编码转换GB23121的方法,然后可以取到天气预报
  11. python打开软件输入消息_菜鸟学Python之七:使用input读取输入信息
  12. matlab 矩阵的数组平方和,MATLAB中的矩阵和数组
  13. 【安全牛学习笔记】Kali Linux基本工具
  14. 截取音乐片段的计算机软件,电脑上剪辑音乐的软件
  15. 海克斯康三坐标模块化c语言编程,海克斯康三坐标编程手册_海克斯康三坐标教程...
  16. 猪猪侠的黑客学习路线
  17. JAVA之bootstrap01
  18. 两篇文章都是翻译了一半就翻不下去了,E文水平有待提高啊
  19. php机器代出价,直通车转化出价工具将升级为:智能出价!
  20. 快速生成树(RSTP)

热门文章

  1. s5pv210——串口通信的基础概念
  2. 高可用性的HDFS—Hadoop分布式文件系统深度实践
  3. LINUX-关机 (系统的关机、重启以及登出 )
  4. JavaWeb无限级分销结构分析
  5. 2.Cannot find config.m4.
  6. [leetcode 70]Climbing Stairs
  7. SQL获取变量类型以及变量最大长度
  8. POJ 3694 Network
  9. 网络流sap需要注意的地方
  10. windows核心编程学习笔记(八)结构化异常处理(Structured Exception Handling)