写点什么

【Flutter 专题】83 解自定义 ACEWave 波浪 Widget (一)

发布于: 27 分钟前
【Flutter 专题】83 解自定义 ACEWave 波浪 Widget (一)

      小菜今天尝试一下绘制波浪的效果,虽然 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 < _count; 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);}
复制代码




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


来源: 阿策小和尚

发布于: 27 分钟前阅读数: 2
用户头像

还未添加个人签名 2021.05.13 加入

Android / Flutter 小菜鸟~

评论

发布
暂无评论
【Flutter 专题】83 解自定义 ACEWave 波浪 Widget (一)