Flutter 之贝塞尔曲线(一)

Stella981
• 阅读 743

贝塞尔曲线简介

Flutter 之贝塞尔曲线(一)

bezier1.png

由上图可以看出:A,C依据控制点B不断的取点使得AD:AB=BE:BC=DF:DE,构成一个二阶贝塞尔曲线。AD:AB的变化取值范围便是[0,1],
所以要在范围t[0,1]内描述下图

Flutter 之贝塞尔曲线(一)

bezier1_1.png

Flutter 之贝塞尔曲线(一)
直线上所有的值,便可推出公式:

接下来就可以把X看成矢量坐标上的点P,转换一下便可得出一阶贝塞尔曲线公式。

一阶贝塞尔曲线

Flutter 之贝塞尔曲线(一)

一阶贝塞尔曲线.gif

公式

Flutter 之贝塞尔曲线(一)

二阶贝塞尔曲线

Flutter 之贝塞尔曲线(一)

二阶贝塞尔曲线.gif

对于二阶贝塞尔曲线,其实你可以理解为:在Flutter 之贝塞尔曲线(一) 上利用一阶公式求出点Flutter 之贝塞尔曲线(一) 然后在Flutter 之贝塞尔曲线(一) 上利用一阶公式求出点Flutter 之贝塞尔曲线(一) 最后在Flutter 之贝塞尔曲线(一) 上再利用一阶公式就可以求出最终贝塞尔曲线上的点Flutter 之贝塞尔曲线(一) 具体推导过程如下:先求出线段上的控制点。Flutter 之贝塞尔曲线(一) Flutter 之贝塞尔曲线(一) 推导得出:

我们常用的画图工具中的曲线,如画图,PS等都是运用贝塞尔曲线计算实现的。

三阶贝赛尔曲线

Flutter 之贝塞尔曲线(一)

三阶贝塞尔曲线.gif

公式

Flutter 之贝塞尔曲线(一)

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';

贝塞尔曲线演示

Flutter 之贝塞尔曲线(一)

bezierShow.gif

贝塞尔曲线绘制正余弦函数

需求

在屏幕中间画整段的正余弦波。

效果

Flutter 之贝塞尔曲线(一)

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

点赞
收藏
评论区
推荐文章
晴空闲云 晴空闲云
2年前
svg中path贝塞尔曲线和圆弧图文详解
最近研究了一下svg的path标签,功能非常强大,理论上来讲path标签可以画出任意图形。自己记性不太好,记录一下path的使用语法和自己的理解。path介绍path用d属性来描述路径,语法格式大概如下:html其中路径描述包含如下命令:Mmoveto移动到某点。Llineto画一条直线到某点。Hhorizontallineto
Easter79 Easter79
2年前
swing 聊天窗体,支持图文模式
!(https://oscimg.oschina.net/oscnet/7bffe1e627c0dcd84aee04edb6b80f00c94.jpg)!(https://oscimg.oschina.net/oscnet/3cf62b9e5426e6a259ad3c137f795a5ba7c.jpg)packagecom..tes
Stella981 Stella981
2年前
Android 贝塞尔曲线实战之网易云音乐鲸云特效
作者:哈哈将个推Android高级开发工程师前言APP开发市场已经告别“野蛮生长”时代,人们不再满足于APP外形创新,而将目光转向全方面的用户体验上。在这过程中,动效化作为移动互联网产品的新趋势,如何实现酷炫丝滑的动画效果已然成为开发者们的新课题。实现方式其实很简单。本文将为你剖析理论基础以及具体应用。咱们日常使用的APP的时候,
Stella981 Stella981
2年前
Spring boot源码分析之Spring循环依赖揭秘
!(https://oscimg.oschina.net/oscnet/be79581de12c41704c44e976d329ad35ad1.gif)若你是一个有经验的程序员,那你在开发中必然碰到过这种现象:事务不生效。或许刚说到这,有的小伙伴就会大惊失色了。Spring不是解决了循环依赖问题吗,它是怎么又会发生循环依赖的呢?,接下来就
Stella981 Stella981
2年前
Discuz X3.2源码解析 discuz_application类(转自百度)
1.discuz\_application在/source/class/discuz/discuz\_application.php中。!DiscuzX3.2源码解析discuz_application类(https://oscimg.oschina.net/oscnet/99b35d79caf70b7c74ad0838d6
Stella981 Stella981
2年前
Flutter 贝塞尔曲线切割
现在人们对于网站的美感要求是越来越高了,所以很多布局需要优美的曲线设计。当然最简单的办法是作一个PNG的透明图片,然后外边放一个Container.但其内容如果本身就不是图片,只是容器,这种放入图片的做法会让包体变大。其实我们完全可以使用贝塞尔曲线进行切割。!(https://oscimg.oschina.net/oscnet/4e9a304ba
Stella981 Stella981
2年前
Spring Boot如何利用AOP巧妙记录操作日志?
!(https://oscimg.oschina.net/oscnet/7f1d6247ad65413fbca3b77b0bb9433c.png)点击上方蓝字关注我们!(https://oscimg.oschina.net/oscnet/3f5a1c2360f9430c93a00b4715527ed9.jpg)本篇
Stella981 Stella981
2年前
Flutter贝塞尔曲线之水波纹与球形进度(二)
续上篇,再用贝塞尔曲线绘制一个循环水波纹,一个水波纹进度球,先看效果,以下效果的实现用的都是二阶贝塞尔曲线。效果图!(https://oscimg.oschina.net/oscnet/db689ecfebdfa10dbc59b588184913f7c6f.gif"bezierShow4.gif")bezierShow4.
Wesley13 Wesley13
2年前
C# 曲线上的点(二) 获取距离最近的点
如何在一条曲线上,获取到距离指定点最近的点位置?!(https://oscimg.oschina.net/oscnet/6ce94b2cd799359221f22a5742193a09296.png)与上一篇C曲线上的点(一)获取指定横坐标对应的纵坐标值(https://www.oschina.net/action/GoToLink?
Stella981 Stella981
2年前
Js使用面向对象和面向过程的方法实现拖拽物体的效果
1.面向过程的拖拽实现代码:!(https://oscimg.oschina.net/oscnet/d680c759957babef2fec0902676eaa35ad9.gif)<!DOCTYPEhtml<html<head<titledragDiv</title