Flutter贝塞尔曲线之水波纹与球形进度(二)

Stella981
• 阅读 899

续上篇,再用贝塞尔曲线绘制一个循环水波纹,一个水波纹进度球,先看效果,以下效果的实现用的都是二阶贝塞尔曲线。

效果图

Flutter贝塞尔曲线之水波纹与球形进度(二)

bezierShow4.gif

Flutter贝塞尔曲线之水波纹与球形进度(二)

bezierShow3.gif

我们先实现简单的循环水波纹绘制,我们可以经常见到当手持长绳或上下或左右挥舞绳子,都会产生波形路径,此时如果我们有黑幕遮住
绳子两端只留下绳子中间的波形部分,就可得到一段无限循环的波形图,前提是一直在抖动绳子。。。

循环水波纹

实现分析

先看一下下图,下图也表述了上述场景。

Flutter贝塞尔曲线之水波纹与球形进度(二)

bezier2.jpeg

综上所述,只要屏幕内的波形够多,只要控制住offset的偏移量,无限循环,就能得到我们所想要的波形。

不封闭的循环波

Flutter贝塞尔曲线之水波纹与球形进度(二)

bezierShow4_2.gif

图3 _waveCount 不够的封闭循环波

Flutter贝塞尔曲线之水波纹与球形进度(二)

bezierShow4_1.gif

关键代码

@overridevoid paint(Canvas canvas, Size size) {  // TODO: implement paint  _screenHeight = size.height; //屏幕高,这里不是一直成立,像当有Center 父控件的时候就不成立  _screenWidth = size.width; //屏幕宽  _waveCount = (_screenWidth / waveLength)      .round()+2; //根据波长算出波的个数,这里的波浪个数多一点,这样可以在屏幕内看到更完整的波浪  _centerY = _screenHeight / 2; //中心高度的值  _path.moveTo(      -waveLength + this.myOffsetX, _centerY); //把画笔的起点移到屏幕外的一个波长处,Y轴在屏幕中间  //this.myOffsetX水平方向的偏移量  for (int i = 0; i < _waveCount; i++) {    canvas.save();    canvas.restore();    //绘制波谷,控制点在(-3M/4,_centerY),结束点在(-waveLength / 2,_centerY)    //此处可以先把(waveLength * i) + this.myOffsetX 这段代码移除,所画的就是一个波谷,屏幕外的波谷    _path.quadraticBezierTo(        -waveLength / 4 * 3 + (waveLength * i) + this.myOffsetX,        _centerY + _waveHeight,        -waveLength / 2 + (waveLength * i) + this.myOffsetX,        _centerY);    //绘制波峰,控制点在(-M/4,_centerY),结束点在(0,_centerY)    //此处可以先把(waveLength * i) + this.myOffsetX 这段代码移除,所画的就是一个波峰,屏幕外的波峰    _path.quadraticBezierTo(        -waveLength / 4 + (waveLength * i) + this.myOffsetX,        _centerY - _waveHeight,        0 + waveLength * i + this.myOffsetX,        _centerY);    canvas.drawPath(_path, _whitePaint); //绘制  }  ///封闭绘制区域,构成“深水面”  _path.lineTo(_screenWidth, _screenHeight); //将画笔从当前位置画到屏幕最右下角  _path.lineTo(0, _screenHeight); //画到屏幕左下角  _path.close();  canvas.drawPath(_path, _pathPaint);}

水波纹进度球

在上面的基础上,在加了一个竖直方向变化的偏移量_progressY;还有就是之前是方形的,现在变成圆形,方形变圆形只要
把整个画布裁剪成圆的即可。

没有裁剪画布成圆的图

Flutter贝塞尔曲线之水波纹与球形进度(二)

bezierShow3_1.gif

关键代码

@overridevoid paint(Canvas canvas, Size size) {  // TODO: implement paint  //centerOffset圆心  _path.addArc(Rect.fromCircle(center: centerOffset, radius: r), 0, 360);  canvas.clipPath(_path); //把画布裁剪成一个圆形,这样怎么画都是圆。  canvas.drawCircle(centerOffset, r, _pointPaint); //画圆  _path.reset(); //重置路径  //this.progress的范围是0-100。  _progressY = centerOffset.dy + (r - r / 50 * this.progress); //算出Y点的坐标  //将画笔移动至屏幕外  _path.moveTo(-waveLength + moveX, _progressY);  //这里的波峰波谷稍微多点,所以waveCount*2  for (int i = 0; i < waveCount * 2; i++) {    canvas.save();    canvas.restore();    //绘制波谷,同上    _path.quadraticBezierTo(        -waveLength / 4 * 3 + (waveLength * i) + moveX,        _progressY + waveHeight,        -waveLength / 2 + (waveLength * i) + moveX,        _progressY);    //绘制波峰,同上    _path.quadraticBezierTo(-waveLength / 4 + (waveLength * i) + moveX,        _progressY - waveHeight, 0 + waveLength * i + moveX, _progressY);  }  print("_moveX=${moveX.toString()}");  //封闭圆  _path.moveTo(centerOffset.dx + r, _progressY);  _path.lineTo(centerOffset.dx + r, centerOffset.dy + r);  _path.lineTo(centerOffset.dx - r, centerOffset.dy + r);  _path.lineTo(centerOffset.dx - r, _progressY);  _path.close();  canvas.drawPath(_path, _pathPaint);}

完整代码

Screen类的代码在前面时钟绘制一篇有,要在程序启动的时候调用init初始化。

///贝塞尔曲线示例二class CustomBezierWidget1 extends StatefulWidget {  @override  _CustomBezierWidget1State createState() => _CustomBezierWidget1State();}class _CustomBezierWidget1State extends State<CustomBezierWidget1>    with SingleTickerProviderStateMixin {  AnimationController animationController;  Animation<double> animation;  final double _waveLength = 300; //波浪长  @override  void initState() {    // TODO: implement initState    super.initState();    animationController = new AnimationController(        vsync: this, duration: Duration(milliseconds: 600));    animation = Tween<double>(begin: 0, end: 300).animate(animationController)      ..addListener(() {        setState(() {});      });    animationController.repeat();  }  @override  void deactivate() {    // TODO: implement deactivate    super.deactivate();    animationController.stop();  }  @override  void dispose() {    // TODO: implement dispose    super.dispose();    animationController.dispose();  }  @override  Widget build(BuildContext context) {    return CustomPaint(      painter: BezierPainter1(animation.value, _waveLength),    );  }}class BezierPainter1 extends CustomPainter {  final double myOffsetX; //平移量  final int _waveHeight = 30; //波浪高  final double waveLength; //一个波浪的长度  Paint _pointPaint; //点画笔  Paint _pathPaint; //线画笔  Paint _whitePaint; //空白画笔  double _screenHeight; //屏幕高  double _screenWidth; //屏幕宽  double _centerY; //屏幕中间Y坐标  int _waveCount; //波浪个数  Path _path = Path(); //路径  BezierPainter1(this.myOffsetX, this.waveLength) {    _pointPaint = Paint()      ..color = Colors.teal      ..strokeWidth = 4      ..isAntiAlias = true      ..style = PaintingStyle.fill;    _pathPaint = Paint()      ..color = Colors.deepOrange      ..style = PaintingStyle.fill      ..isAntiAlias = true      ..strokeWidth = 1;    _whitePaint = Paint()      ..color = Colors.white      ..style = PaintingStyle.fill      ..isAntiAlias = true      ..strokeWidth = 1;  }  @override  void paint(Canvas canvas, Size size) {    // TODO: implement paint    _screenHeight = size.height; //屏幕高,这里不是一直成立,像当有Center 父控件的时候就不成立    _screenWidth = size.width; //屏幕宽    _waveCount = (_screenWidth / waveLength)        .round()+2; //根据波长算出波的个数,这里的波浪个数多一点,这样可以在屏幕内看到更完整的波浪    _centerY = _screenHeight / 2; //中心高度的值    _path.moveTo(        -waveLength + this.myOffsetX, _centerY); //把画笔的起点移到屏幕外的一个波长处,Y轴在屏幕中间    //this.myOffsetX水平方向的偏移量    for (int i = 0; i < _waveCount; i++) {      canvas.save();      canvas.restore();      //绘制波谷,控制点在(-3M/4,_centerY),结束点在(-waveLength / 2,_centerY)      //此处可以先把(waveLength * i) + this.myOffsetX 这段代码移除,所画的就是一个波谷,屏幕外的波谷      _path.quadraticBezierTo(          -waveLength / 4 * 3 + (waveLength * i) + this.myOffsetX,          _centerY + _waveHeight,          -waveLength / 2 + (waveLength * i) + this.myOffsetX,          _centerY);      //绘制波峰,控制点在(-M/4,_centerY),结束点在(0,_centerY)      //此处可以先把(waveLength * i) + this.myOffsetX 这段代码移除,所画的就是一个波峰,屏幕外的波峰      _path.quadraticBezierTo(          -waveLength / 4 + (waveLength * i) + this.myOffsetX,          _centerY - _waveHeight,          0 + waveLength * i + this.myOffsetX,          _centerY);      canvas.drawPath(_path, _whitePaint); //绘制    }    ///封闭绘制区域,构成“深水面”    _path.lineTo(_screenWidth, _screenHeight); //将画笔从当前位置画到屏幕最右下角    _path.lineTo(0, _screenHeight); //画到屏幕左下角    _path.close();    canvas.drawPath(_path, _pathPaint);  }  @override  bool shouldRepaint(CustomPainter oldDelegate) {    // TODO: implement shouldRepaint    return true;  }}/// 贝塞尔曲线示例三class WidgetCircleProgressWidget extends StatefulWidget {  @override  _WidgetCircleProgressWidgetState createState() =>      _WidgetCircleProgressWidgetState();}class _WidgetCircleProgressWidgetState    extends State<WidgetCircleProgressWidget> {  Timer timer;  double progress = 0;  bool flag = false;  @override  void initState() {    // TODO: implement initState    super.initState();    timer = Timer.periodic(Duration(milliseconds: 500), (timer) {      setState(() {        if (flag) {          progress = progress - 1;        } else {          progress = progress + 1;        }        if (progress == 0) {          flag = false;        }        if (progress == 100) flag = true;      });    });  }  @override  void dispose() {    // TODO: implement dispose    super.dispose();    timer.cancel();  }  @override  Widget build(BuildContext context) {    return CustomBezierWidget2(progress);  }}class CustomBezierWidget2 extends StatefulWidget {  final double progress;  CustomBezierWidget2(this.progress);  @override  _CustomBezierWidget2State createState() => _CustomBezierWidget2State();}class _CustomBezierWidget2State extends State<CustomBezierWidget2>    with SingleTickerProviderStateMixin {  AnimationController _animationController;  Animation<double> _animationTranslate;  double _moveX; //移动的X,此处变化一个波长  double _r; //半径  double waveLength; //波长  double _waveCount = 2;  @override  void initState() {    // TODO: implement initState    super.initState();    _r = Screen.screenWidthDp / 3;    waveLength = 2 * _r / _waveCount;    _animationController =        new AnimationController(vsync: this, duration: Duration(seconds: 1));    _animationTranslate =        Tween<double>(begin: 0, end: waveLength).animate(_animationController)          ..addListener(() {            setState(() {});          });    _animationController.repeat();  }  @override  void deactivate() {    // TODO: implement deactivate    super.deactivate();    _animationController.stop();  }  @override  void dispose() {    // TODO: implement dispose    super.dispose();    _animationController.dispose();  }  @override  Widget build(BuildContext context) {    return CustomPaint(      painter: BezierPainter2(          progress: this.widget.progress,          waveHeight: 15,          moveX: _animationTranslate.value,          r: _r,          waveLength: waveLength),    );  }}class BezierPainter2 extends CustomPainter {  final double progress; //进度  final double waveHeight; //波浪高  final double moveX; //移动的X,此处变化一个波长  final double r; //半径  final double waveLength; //一个波浪的长度  final waveCount = 2; //波浪个数  double _progressY; //移动中Y的坐标  Paint _pointPaint; //点画笔  Paint _pathPaint; //线画笔  Paint _whitePaint; //空白画笔  Path _path = Path(); //路径  Offset centerOffset; //圆心  BezierPainter2(      {this.progress, this.waveHeight, this.moveX, this.r, this.waveLength}) {    _pointPaint = Paint()      ..color = Colors.teal      ..strokeWidth = 4      ..isAntiAlias = true      ..style = PaintingStyle.stroke;    _pathPaint = Paint()      ..color = Colors.deepOrange      ..style = PaintingStyle.fill      ..isAntiAlias = true      ..strokeWidth = 1;    _whitePaint = Paint()      ..color = Colors.white      ..style = PaintingStyle.stroke      ..isAntiAlias = true      ..strokeWidth = 1;    centerOffset = Offset(Screen.screenWidthDp / 2, Screen.screenHeightDp / 2);  }  @override  void paint(Canvas canvas, Size size) {    // TODO: implement paint    //centerOffset圆心    _path.addArc(Rect.fromCircle(center: centerOffset, radius: r), 0, 360);    canvas.clipPath(_path); //把画布裁剪成一个圆形,这样怎么画都是圆。    canvas.drawCircle(centerOffset, r, _pointPaint); //画圆    _path.reset(); //重置路径    //this.progress的范围是0-100。    _progressY = centerOffset.dy + (r - r / 50 * this.progress); //算出Y点的坐标    //将画笔移动至屏幕外    _path.moveTo(-waveLength + moveX, _progressY);    //这里的波峰波谷稍微多点,所以waveCount*2    for (int i = 0; i < waveCount * 2; i++) {      canvas.save();      canvas.restore();      //绘制波谷,同上      _path.quadraticBezierTo(          -waveLength / 4 * 3 + (waveLength * i) + moveX,          _progressY + waveHeight,          -waveLength / 2 + (waveLength * i) + moveX,          _progressY);      //绘制波峰,同上      _path.quadraticBezierTo(-waveLength / 4 + (waveLength * i) + moveX,          _progressY - waveHeight, 0 + waveLength * i + moveX, _progressY);    }    print("_moveX=${moveX.toString()}");    //封闭圆    _path.moveTo(centerOffset.dx + r, _progressY);    _path.lineTo(centerOffset.dx + r, centerOffset.dy + r);    _path.lineTo(centerOffset.dx - r, centerOffset.dy + r);    _path.lineTo(centerOffset.dx - r, _progressY);    _path.close();    canvas.drawPath(_path, _pathPaint);  }  @override  bool shouldRepaint(CustomPainter oldDelegate) {    // TODO: implement shouldRepaint    return true;  }}

本文分享自微信公众号 - Flutter学习簿(gh_d739155d3b2c)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
晴空闲云 晴空闲云
3年前
svg中path贝塞尔曲线和圆弧图文详解
最近研究了一下svg的path标签,功能非常强大,理论上来讲path标签可以画出任意图形。自己记性不太好,记录一下path的使用语法和自己的理解。path介绍path用d属性来描述路径,语法格式大概如下:html其中路径描述包含如下命令:Mmoveto移动到某点。Llineto画一条直线到某点。Hhorizontallineto
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Stella981 Stella981
3年前
Android 贝塞尔曲线实战之网易云音乐鲸云特效
作者:哈哈将个推Android高级开发工程师前言APP开发市场已经告别“野蛮生长”时代,人们不再满足于APP外形创新,而将目光转向全方面的用户体验上。在这过程中,动效化作为移动互联网产品的新趋势,如何实现酷炫丝滑的动画效果已然成为开发者们的新课题。实现方式其实很简单。本文将为你剖析理论基础以及具体应用。咱们日常使用的APP的时候,
Stella981 Stella981
3年前
Flutter 贝塞尔曲线切割
现在人们对于网站的美感要求是越来越高了,所以很多布局需要优美的曲线设计。当然最简单的办法是作一个PNG的透明图片,然后外边放一个Container.但其内容如果本身就不是图片,只是容器,这种放入图片的做法会让包体变大。其实我们完全可以使用贝塞尔曲线进行切割。!(https://oscimg.oschina.net/oscnet/4e9a304ba
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Stella981 Stella981
3年前
Flutter 之贝塞尔曲线(一)
贝塞尔曲线简介!(https://oscimg.oschina.net/oscnet/863784996212c918a1feef7a916bce28f31.png"bezier1.png")bezier1.png由上图可以看出:A,C依据控制点B不断的取点使得AD:ABBE:BCDF:DE,构成一个二阶贝塞尔曲线。AD:
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这