当前位置:
首页
文章
前端
详情

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

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

效果图

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源创计划”,欢迎正在阅读的你也加入,一起分享。

免责申明:本站发布的内容(图片、视频和文字)以转载和分享为主,文章观点不代表本站立场,如涉及侵权请联系站长邮箱:xbc-online@qq.com进行反馈,一经查实,将立刻删除涉嫌侵权内容。