续上篇,再用贝塞尔曲线绘制一个循环水波纹,一个水波纹进度球,先看效果,以下效果的实现用的都是二阶贝塞尔曲线。
效果图
bezierShow4.gif
bezierShow3.gif
我们先实现简单的循环水波纹绘制,我们可以经常见到当手持长绳或上下或左右挥舞绳子,都会产生波形路径,此时如果我们有黑幕遮住
绳子两端只留下绳子中间的波形部分,就可得到一段无限循环的波形图,前提是一直在抖动绳子。。。
循环水波纹
实现分析
先看一下下图,下图也表述了上述场景。
bezier2.jpeg
综上所述,只要屏幕内的波形够多,只要控制住offset的偏移量,无限循环,就能得到我们所想要的波形。
不封闭的循环波
bezierShow4_2.gif
图3 _waveCount 不够的封闭循环波
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;还有就是之前是方形的,现在变成圆形,方形变圆形只要
把整个画布裁剪成圆的即可。
没有裁剪画布成圆的图
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源创计划”,欢迎正在阅读的你也加入,一起分享。