贝塞尔曲线简介
bezier1.png
由上图可以看出:A,C依据控制点B不断的取点使得AD:AB=BE:BC=DF:DE,构成一个二阶贝塞尔曲线。AD:AB的变化取值范围便是[0,1],
所以要在范围t[0,1]内描述下图
bezier1_1.png
直线上所有的值,便可推出公式:
接下来就可以把X看成矢量坐标上的点P,转换一下便可得出一阶贝塞尔曲线公式。
一阶贝塞尔曲线
一阶贝塞尔曲线.gif
公式
二阶贝塞尔曲线
二阶贝塞尔曲线.gif
对于二阶贝塞尔曲线,其实你可以理解为:在上利用一阶公式求出点然后在上利用一阶公式求出点最后在上再利用一阶公式就可以求出最终贝塞尔曲线上的点具体推导过程如下:先求出线段上的控制点。 推导得出:
我们常用的画图工具中的曲线,如画图,PS等都是运用贝塞尔曲线计算实现的。
三阶贝赛尔曲线
三阶贝塞尔曲线.gif
公式
Flutter中的贝塞尔曲线
/// Adds a quadratic bezier segment that curves from the current /// point to the given point (x2,y2), using the control point /// (x1,y1) 二阶贝塞尔曲线,控制点为(x1,y1),终点为(x2,y2),起点为当前path所在的点 void quadraticBezierTo(double x1, double y1, double x2, double y2) native 'Path_quadraticBezierTo'; /// Adds a cubic bezier segment that curves from the current point /// to the given point (x3,y3), using the control points (x1,y1) and /// (x2,y2) 三阶贝塞尔曲线,控制点为(x1,y1),(x2,y2),终点为(x3,y3),起点为当前path所在的点 void cubicTo(double x1, double y1, double x2, double y2, double x3, double y3) native 'Path_cubicTo';
贝塞尔曲线演示
bezierShow.gif
贝塞尔曲线绘制正余弦函数
需求
在屏幕中间画整段的正余弦波。
效果
bezierShow2.gif
分析
首先算出屏幕中间左侧边和右侧边的两点。
在屏幕中线上距离屏幕两侧等间距的位置确定起始点。
在波峰和波谷找到两个控制点
把点带入方法绘制,即可得到正余弦波
实现
关键代码
///屏幕左上角的坐标顶点对应着(0,0)点 //屏幕中左侧点 Offset offset1 = Offset(0, Screen.screenHeightDp / 2); _path.moveTo(offset1.dx, offset1.dy); ///假设,整个波长=屏幕宽度=M,画的是一条正弦 ///绘制波峰 ///波峰控制点就在(M/4,centerY-波纹高度),终点在屏幕中点 _path.quadraticBezierTo( Screen.screenWidthDp / 4, Screen.screenHeightDp / 2 - this.height, Screen.screenWidthDp / 2, Screen.screenHeightDp / 2); ///绘制波谷,此时画笔的起点已经在屏幕中心 ///波谷控制点就在(3M/4,centerY+波纹高度),终点在屏幕中线终点 _path.quadraticBezierTo( Screen.screenWidthDp / 4 * 3, Screen.screenHeightDp / 2 + this.height, Screen.screenWidthDp, Screen.screenHeightDp / 2); ///绘制,可以把style = PaintingStyle.fill换成stoke看看效果 canvas.drawPath(_path, _paint);
完整代码
Screen类的代码在前面时钟绘制一篇有,要在程序启动的时候调用init初始化。
正余弦代码
///贝塞尔曲线示例一class CustomBezierWidget extends StatefulWidget { @override _BezierWidgetState createState() { // TODO: implement createState return _BezierWidgetState(); }}class _BezierWidgetState extends State<CustomBezierWidget> { Timer timer; int height = 100; bool flag = true; @override void initState() { // TODO: implement initState super.initState(); timer = Timer.periodic(Duration(microseconds: 5000), (timer) { setState(() { if (flag) { height = height - 1; } else { height = height + 1; } if (height == -100) { flag = false; } if (height == 100) flag = true; }); }); } @override void dispose() { // TODO: implement dispose super.dispose(); timer.cancel(); } @override Widget build(BuildContext context) { return CustomPaint(painter: BezierPainter(height)); }}class BezierPainter extends CustomPainter { final int height; //波的高度 BezierPainter(this.height); //路径画笔 Paint _paint = Paint() ..color = Colors.deepOrange ..style = PaintingStyle.fill ..isAntiAlias = true ..strokeWidth = 10; //点画笔 Paint _pointPaint = Paint() ..color = Colors.teal ..strokeWidth = 10 ..isAntiAlias = true ..style = PaintingStyle.fill; //曲线路径 Path _path = Path(); ///屏幕左上脚的坐标顶点对应着(0,0)点 //屏幕中左侧点 Offset offset1 = Offset(0, Screen.screenHeightDp / 2); //屏幕终点 Offset offset2 = Offset(Screen.screenWidthDp / 2, Screen.screenHeightDp / 2); @override void paint(Canvas canvas, Size size) { // TODO: implement paint print('Size.width==>${size.width} Size.height==>${size.height}'); print( 'Screen.width==>${Screen.screenWidthDp} Screen.height==>${Screen.screenHeightDp}'); _path.moveTo(offset1.dx, offset1.dy); ///假设,整个波长=屏幕宽度=M,画的是一条正弦 ///绘制波峰 ///波峰控制点就在(M/4,centerY-波纹高度),终点在屏幕中点 _path.quadraticBezierTo( Screen.screenWidthDp / 4, Screen.screenHeightDp / 2 - this.height, Screen.screenWidthDp / 2, Screen.screenHeightDp / 2); ///绘制波谷,此时画笔的起点已经在屏幕中心 ///波谷控制点就在(3M/4,centerY+波纹高度),终点在屏幕中线终点 _path.quadraticBezierTo( Screen.screenWidthDp / 4 * 3, Screen.screenHeightDp / 2 + this.height, Screen.screenWidthDp, Screen.screenHeightDp / 2); ///绘制,可以把style = PaintingStyle.fill换成stoke看看效果 canvas.drawPath(_path, _paint); //描绘辅助控制点 canvas.drawPoints( PointMode.points, [ Offset(0, Screen.screenHeightDp / 2), Offset(Screen.screenWidthDp / 4, Screen.screenHeightDp / 2 - this.height), Offset(Screen.screenWidthDp / 2, Screen.screenHeightDp / 2) ], _pointPaint); canvas.drawPoints( PointMode.points, [ Offset(Screen.screenWidthDp / 2, Screen.screenHeightDp / 2), Offset(Screen.screenWidthDp / 4 * 3, Screen.screenHeightDp / 2 + this.height), Offset(Screen.screenWidthDp, Screen.screenHeightDp / 2) ], _pointPaint); } @override bool shouldRepaint(CustomPainter oldDelegate) { // TODO: implement shouldRepaint return true; }}
贝塞尔曲线演示代码
写的比较粗糙,类稍微有点多
import 'package:flutter/material.dart';import 'package:flutter_weekly/common/utils/screen.dart';import 'dart:math' as math;class BezierGestureWidget extends StatefulWidget { @override _BezierGestureWidgetState createState() => _BezierGestureWidgetState();}class _BezierGestureWidgetState extends State<BezierGestureWidget> { int _bezierType = 3; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('贝塞尔曲线演示视图'), ), body: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ RaisedButton( child: Text("一阶贝塞尔"), onPressed: () { setState(() { _bezierType = 1; }); }, ), RaisedButton( child: Text("二阶贝塞尔"), onPressed: () { setState(() { _bezierType = 2; }); }, ), RaisedButton( child: Text("三阶贝塞尔"), onPressed: () { setState(() { _bezierType = 3; }); }, ), ], ), Expanded( child: Container( child: GestureWidget(_bezierType), ), flex: 1, ) ], ), ); }}///演示控件class GestureWidget extends StatefulWidget { final int _type; GestureWidget(this._type); @override _GestureWidgetState createState() => _GestureWidgetState();}class _GestureWidgetState extends State<GestureWidget> { Offset moveOffset=Offset(0, 0); ///贝塞尔曲线控制点,这里只演示三阶曲线 Offset _ctrlOffset0;//控制点1 Offset _ctrlOffset1; _GestureWidgetState(){ if(_ctrlOffset0==null){ print("_ctrlOffset0==null"); _ctrlOffset0= Offset(90, Screen.screenHeightDp/3-60); } if(_ctrlOffset1==null){ print("_ctrlOffset1==null"); _ctrlOffset1=Offset(Screen.screenWidthDp-90, Screen.screenHeightDp/3-60); } } //控制点2 @override Widget build(BuildContext context) { return Stack( children: <Widget>[ GestureDetector( onPanUpdate: (DragUpdateDetails details) { setState(() { moveOffset = new Offset(details.globalPosition.dx, details.globalPosition.dy); ///这里可以看做是初始化的两个控制点 //计算控制点距离控制点1,2的距离,以此判断手势想要移动哪个控制点 double ctrl0Length=math.sqrt((math.pow((moveOffset.dx-_ctrlOffset0.dx).abs(), 2)+(math.pow((moveOffset.dy-_ctrlOffset0.dy).abs(), 2))));//勾股定理 double ctrl1Length=math.sqrt((math.pow((moveOffset.dx-_ctrlOffset1.dx).abs(), 2)+(math.pow((moveOffset.dy-_ctrlOffset1.dy).abs(), 2))));//勾股定理 if(ctrl0Length>ctrl1Length){ ///控制的是 _ctrlOffset1 这个点 _ctrlOffset1=moveOffset; }else{ _ctrlOffset0=moveOffset; } }); }, onTapDown: (TapDownDetails details){ setState(() { moveOffset = new Offset(details.globalPosition.dx, details.globalPosition.dy); //计算控制点距离控制点1,2的距离,以此判断手势想要移动哪个控制点 double ctrl0Length=math.sqrt((math.pow((moveOffset.dx-_ctrlOffset0.dx).abs(), 2)+(math.pow((moveOffset.dy-_ctrlOffset0.dy).abs(), 2))));//勾股定理 double ctrl1Length=math.sqrt((math.pow((moveOffset.dx-_ctrlOffset1.dx).abs(), 2)+(math.pow((moveOffset.dy-_ctrlOffset1.dy).abs(), 2))));//勾股定理 if(ctrl0Length>ctrl1Length){ ///控制的是 _ctrlOffset1 这个点 _ctrlOffset1=moveOffset; }else{ _ctrlOffset0=moveOffset; } }); }, ), BezierWidget(this.widget._type, moveOffset,_ctrlOffset0,_ctrlOffset1), ], ); }}class BezierWidget extends StatefulWidget { final int _type; final Offset _moveOffset; final Offset _ctrlOffset0;//控制点1 final Offset _ctrlOffset1;//控制点2 BezierWidget(this._type, this._moveOffset, this._ctrlOffset0, this._ctrlOffset1); @override _BezierWidgetState createState() => _BezierWidgetState();}class _BezierWidgetState extends State<BezierWidget> { @override Widget build(BuildContext context) { return CustomPaint( painter: BezierExamplePainter(this.widget._type, this.widget._moveOffset,this.widget._ctrlOffset0,this.widget._ctrlOffset1), ); }}class BezierExamplePainter extends CustomPainter { final int _type; final Offset _moveOffset; final Offset _ctrlOffset0;//控制点1 final Offset _ctrlOffset1;//控制点2 BezierExamplePainter(this._type, this._moveOffset, this._ctrlOffset0, this._ctrlOffset1); double _r=5;//点半径 ///起始点和终止点设置不变 Offset _startOffset;//起始点 Offset _endOffset;//结束点 ///设置画笔 Paint _pathPaint=Paint()..isAntiAlias=true..strokeWidth=4..color=Colors.deepOrange..style=PaintingStyle.stroke; Paint _pointPaint=Paint()..isAntiAlias=true..strokeWidth=4..style=PaintingStyle.fill..color=Colors.green; Paint _ctrPaint=Paint()..isAntiAlias=true..strokeWidth=4..style=PaintingStyle.fill..color=Colors.blue; //路径 Path _linePath=Path(); @override void paint(Canvas canvas, Size size) { // TODO: implement paint _startOffset=Offset(size.width+60, Screen.screenHeightDp/3); _endOffset=Offset(Screen.screenWidthDp-60, Screen.screenHeightDp/3); print("_startOffset: ${_startOffset.toString()}"); canvas.drawCircle(_startOffset, _r, _pointPaint); canvas.save(); canvas.restore(); _linePath.reset(); switch(_type){ case 1:{ canvas.drawLine(_startOffset, this._moveOffset, _pathPaint); canvas.drawCircle(_moveOffset, _r, _pointPaint); break; } case 2:{ _linePath.moveTo(_startOffset.dx, _startOffset.dy); _linePath.quadraticBezierTo(this._moveOffset.dx, this._moveOffset.dy, _endOffset.dx, _endOffset.dy); canvas.drawCircle(_moveOffset, _r, _ctrPaint); canvas.drawCircle(_endOffset, _r, _pointPaint); canvas.drawPath(_linePath, _pathPaint); break; } case 3: { _linePath.moveTo(_startOffset.dx, _startOffset.dy); _linePath.cubicTo(_ctrlOffset0.dx, _ctrlOffset0.dy, _ctrlOffset1.dx, _ctrlOffset1.dy, _endOffset.dx, _endOffset.dy); canvas.drawCircle(_endOffset, _r, _pointPaint); canvas.drawCircle(_ctrlOffset0, _r, _ctrPaint); canvas.drawCircle(_ctrlOffset1, _r, _ctrPaint); canvas.drawPath(_linePath, _pathPaint); break; } default: } } @override bool shouldRepaint(CustomPainter oldDelegate) { // TODO: implement shouldRepaint return true; }}
本文分享自微信公众号 - Flutter学习簿(gh_d739155d3b2c)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。