美文网首页Flutter圈子All in FlutterFlutter
Flutter之 贝塞尔曲线(一)

Flutter之 贝塞尔曲线(一)

作者: 向日花开 | 来源:发表于2019-09-30 08:37 被阅读0次

    贝塞尔曲线简介

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

    直线上所有的值,便可推出公式:

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

    一阶贝塞尔曲线

    一阶贝塞尔曲线.gif

    公式

    B_{1}(t) = (1 - t)P_0 + tP_1,t\in[0,1]

    二阶贝塞尔曲线

    二阶贝塞尔曲线.gif

    对于二阶贝塞尔曲线,其实你可以理解为:在P_0P_1上利用一阶公式求出点P_0^{'}然后在P_1P_2上利用一阶公式求出点P_1^{'}最后在P_0^{'}P_1^{'}上再利用一阶公式就可以求出最终贝塞尔曲线上的点P_0{''}具体推导过程如下:先求出线段上的控制点。P_0^{'} = (1 - t)P_0 +tP_1 P_1^{'} = (1 - t)P_1 + tP_2推导得出:B_{2}(t) = (1 - t)P_0^{'} + tP_1^{'} = (1 - t)((1 - t)P_0 + tP_1) + t((1 - t)P_1 + tP_2) = (1 - t)^2P_0 + 2t(1 - t)P_1 + t^2P_2
    我们常用的画图工具中的曲线,如画图,PS等都是运用贝塞尔曲线计算实现的。

    三阶贝赛尔曲线

    三阶贝塞尔曲线.gif

    公式

    B_{3}(t) = (1 - t)^3P_0 + 3t(1 - t)^2P_1 + 3t^2(1 - t)P_2 + t^3P_3 , t\in[0, 1]

    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之 贝塞尔曲线(一)

        本文链接:https://www.haomeiwen.com/subject/kiuhuctx.html