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

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

效果图

bezierShow4.gif

bezierShow3.gif

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

循环水波纹

实现分析

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


bezier2.jpeg

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

不封闭的循环波

bezierShow4_2.gif

图3 _waveCount 不够的封闭循环波

bezierShow4_1.gif

关键代码

@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);
}

水波纹进度球

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

没有裁剪画布成圆的图

![]
bezierShow3_1.gif

关键代码

@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);
}

完整代码

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;
  }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容