【Flutter学习】之自定义组件(绘制Widget)(三)


一,概述

  我们知道,应用开发如果单纯只靠系统提供的控件,对于那些较为绚烂界面效果来说是远远不够的,这就需要开发者自己去自定义绘制Widget。

  当然,自定义Widget讲究灵活性,同一种效果可以由多种实现方案,我们要做的就是找到代价最小、最高效的解决方案。

二,Flutter自定义绘制Widget

从如何使用Canvas draw/paint我们了解到,在Flutter中使用自绘方式自定义Widget,大致需要以下步骤:

    • 继承CustomPainter并重写paint方法和shouldRepaint方法

    • 在写paint方法中绘制内容

    • 使用CustomPaint来构建Widget

    • 示例
        举个栗子,比如下面代码就实现了30*30的网格
       
            
          void main() => runApp(MyApp());

        class MyApp extends StatelessWidget {
          // This widget is the root of your application.
          @override
          Widget build(BuildContext context) {
            return MaterialApp(
              title: 'Flutter Demo',
              theme: ThemeData(
               primarySwatch: Colors.blue,
              ),
              home: BackGrad(),
            );
           }
         }
       
            
      class BackGrid extends StatelessWidget {
        @override
        Widget build(BuildContext context) {
          return Container(
            child: Center(
              // 使用CustomPaint
              child: CustomPaint(
                size: Size(300, 300),
                painter: MyPainter(),
              ),
            ),
          );
        }
      }
      
      class MyPainter extends CustomPainter {
        @override
        void paint(Canvas canvas, Size size) {
          double eWidth = size.width / 30;
          double eHeight = size.height / 30;
      
          //画棋盘背景
          var paint = Paint()
            ..isAntiAlias = true
            ..style = PaintingStyle.fill //填充
            ..color = Color(0x77cdb175); //背景为纸黄色
          canvas.drawRect(Offset.zero & size, paint);
      
          //画棋盘网格
          paint
            ..style = PaintingStyle.stroke //线
            ..color = Color(0xFF888888)
            ..strokeWidth = 1.0;
      
          for (int i = 0; i <= 30; ++i) {
            double dy = eHeight * i;
            canvas.drawLine(Offset(0, dy), Offset(size.width, dy), paint);
          }
      
          for (int i = 0; i <= 30; ++i) {
            double dx = eWidth * i;
            canvas.drawLine(Offset(dx, 0), Offset(dx, size.height), paint);
          }
        }
      
        @override
        bool shouldRepaint(CustomPainter oldDelegate) => false;
      }

     显示效果:

  • Flutter绘制相关知识

    和Android开发中的自定义View类似,Flutter中的绘制也是依靠Canvas和Paint来实现的

    1.1 Canvas

      画布,为开发者提供了点、线、矩形、圆形、嵌套矩形等绘制方法。

    1.2 Paint

      画笔,可以设置抗锯齿,画笔颜色,粗细,填充模式等属性,绘制时可以定义多个画笔以满足不同的绘制需求。

    1.3 Offset

      坐标,可以用来表示某个点在画布中的坐标位置。

    1.4 Rect

      矩形,在图形的绘制中,一般都是分区域绘制的,这个区域一般都是一个矩形,在绘制中通常使用Rect来存储绘制的位置信息。

    1.5 坐标系

      在Flutter中,坐标系原点(0,0)位于左上角,X轴向右变大,Y轴向下变大。

    下面我们看一下Paint的常用基本属性设置

    Paint _paint = new Paint()
    // 画笔颜色
    ..color = Colors.red 
    // 画笔笔触类型
    // round-画笔笔触呈半圆形轮廓开始和结束
    // butt-笔触开始和结束边缘平坦,没有外延
    // square-笔触开始和结束边缘平坦,向外延伸长度为画笔宽度的一半
    ..strokeCap = StrokeCap.round
    //是否启动抗锯齿
    ..isAntiAlias = true
    //绘画风格,默认为填充,有fill和stroke两种
    ..style=PaintingStyle.fill
    ..blendMode=BlendMode.exclusion//颜色混合模式
    ..colorFilter=ColorFilter.mode(Colors.blueAccent, BlendMode.exclusion)//颜色渲染模式
    ..maskFilter=MaskFilter.blur(BlurStyle.inner, 3.0)//模糊遮罩效果
    ..filterQuality=FilterQuality.high//颜色渲染模式的质量
    ..strokeWidth = 15.0;//画笔的宽度复制代码
  • 2、Flutter绘制方法介绍

    接下来我们了解一下Flutter中Canvas的各种绘制方法

    PS:以下各种绘制方法所使用的_paint定义如下:

    var _paint = Paint()
    ..color = Color(0xFFFf0000)
    ..strokeWidth = 4
    ..style = PaintingStyle.stroke
    ..isAntiAlias = true;
    2.1 绘制点
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: BackGrad(),
        );
      }
    }
    
    class BackGrad extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return new Container(
          child: Center(
            child: CustomPaint(//使用CustomPaint
              size: Size(300, 300),
              painter: MyPainter(),
            ),
          ),
        );
      }
    }
    
    class MyPainter extends CustomPainter {
      @override
      ///[画背景]
      void paint(Canvas canvas, Size size) {
        double eWidth = size.width/30;
        double eHeight = size.height/30;
    
        //画棋盘背景
        var paint = Paint()
        ..isAntiAlias = true
        ..style = PaintingStyle.fill//填充
        ..color = Color(0x77cdb175);//背景为纸黄色
        canvas.drawRect(Offset.zero & size, paint);
    
        //画棋盘网格
        paint
          ..style = PaintingStyle.fill //线
          ..color = Color(0xFF888888)
          ..strokeWidth = 1.0;
    
        for(int i = 0; i<=30;++i){
          double dy = eHeight * i;
          canvas.drawLine(Offset(0,dy), Offset(size.width,dy), paint);
        }
    
        for(int i = 0; i <= 30; ++i){
          double dx = eWidth * i;
          canvas.drawLine(Offset(dx, 0), Offset(dx, size.height), paint);
        }
    
        ///[绘制点]
        var _paint = Paint()
        ..color = Color(0xffff0000) //画笔颜色
        ..strokeWidth = 4 //画笔粗细
        ..style = PaintingStyle.stroke
        ..isAntiAlias = true;
    
        List<Offset> points = [
          Offset(0, 0),
          Offset(30, 50),
          Offset(20, 80),
          Offset(100, 40),
          Offset(150, 90),
          Offset(60, 110),
          Offset(260, 160),
        ];
        canvas.drawPoints(prefix0.PointMode.points,points,_paint);
      }
        @override
        bool shouldRepaint(CustomPainter oldDelegate) => false;
    }

    PointMode是一个枚举类:

    enum PointMode {
      points,// 绘制点
      lines,// 绘制点,且数组内隔点相连,如1-2相连,3-4相连。如最后只剩下一个点,则不去绘制该点
      polygon,// 数组内相邻点连接
    }                                                                 

    结果:

  • 2.2 绘制线 
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: BackGrad(),
        );
      }
    }
    
    class BackGrad extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return new Container(
          child: Center(
            child: CustomPaint(//使用CustomPaint
              size: Size(300, 300),
              painter: MyPainter(),
            ),
          ),
        );
      }
    }
    
    class MyPainter extends CustomPainter {
      @override
      ///[画背景]
      void paint(Canvas canvas, Size size) {
        double eWidth = size.width/30;
        double eHeight = size.height/30;
    
        //画棋盘背景
        var paint = Paint()
        ..isAntiAlias = true
        ..style = PaintingStyle.fill//填充
        ..color = Color(0x77cdb175);//背景为纸黄色
        canvas.drawRect(Offset.zero & size, paint);
    
        //画棋盘网格
        paint
          ..style = PaintingStyle.fill //线
          ..color = Color(0xFF888888)
          ..strokeWidth = 1.0;
    
        for(int i = 0; i<=30;++i){
          double dy = eHeight * i;
          canvas.drawLine(Offset(0,dy), Offset(size.width,dy), paint);
        }
    
        for(int i = 0; i <= 30; ++i){
          double dx = eWidth * i;
          canvas.drawLine(Offset(dx, 0), Offset(dx, size.height), paint);
        }
    
        ///[绘制线]
        var _paint = Paint()
        ..color = Color(0xffff0000) //画笔颜色
        ..strokeWidth = 4 //画笔粗细
        ..style = PaintingStyle.stroke
        ..isAntiAlias = true;
    
        var _startPoint = Offset(30, 30);//起始点
        var _endPoint = Offset(100,170);//结束点
        canvas.drawLine(_startPoint, _endPoint, _paint);
        
      }
        @override
        bool shouldRepaint(CustomPainter oldDelegate) => false;
    }

    结果:

 

  • 2.3 绘制矩形
    首先我们了解一下矩形Rect的构建:
    // 利用矩形左边的X坐标、矩形顶部的Y坐标、矩形右边的X坐标、矩形底部的Y坐标确定矩形的大小和位置
    Rect.fromLTRB(double left, double top, double right, double bottom)
    // 利用矩形的左边的X轴、顶边的Y轴位置配合设置矩形的长和宽来确定矩形的大小和位置
    Rect.fromLTWH(double left, double top, double width, double height)
    // 利用矩形的中心点和矩形所在圆的半径来确定矩形的大小和位置,此方法确定的是一个正方形
    Rect.fromCircle({ Offset center, double radius })
    // 利用矩形的左上角和右下角的坐标确定矩形的大小和位置,
    // 需要注意的是两点坐标中,如果X轴相同或者Y轴相同,确定的是一条线,
    // 如果两点XY坐标都是同一个数字,确定的是一个点。
    Rect.fromPoints(Offset a, Offset b) 

    绘制矩形的方法

    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: BackGrad(),
        );
      }
    }
    
    class BackGrad extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return new Container(
          child: Center(
            child: CustomPaint(//使用CustomPaint
              size: Size(300, 300),
              painter: MyPainter(),
            ),
          ),
        );
      }
    }
    
    class MyPainter extends CustomPainter {
      @override
      ///[画背景]
      void paint(Canvas canvas, Size size) {
        double eWidth = size.width/30;
        double eHeight = size.height/30;
    
        //画棋盘背景
        var paint = Paint()
        ..isAntiAlias = true
        ..style = PaintingStyle.fill//填充
        ..color = Color(0x77cdb175);//背景为纸黄色
        canvas.drawRect(Offset.zero & size, paint);
    
        //画棋盘网格
        paint
          ..style = PaintingStyle.fill //线
          ..color = Color(0xFF888888)
          ..strokeWidth = 1.0;
    
        for(int i = 0; i<=30;++i){
          double dy = eHeight * i;
          canvas.drawLine(Offset(0,dy), Offset(size.width,dy), paint);
        }
    
        for(int i = 0; i <= 30; ++i){
          double dx = eWidth * i;
          canvas.drawLine(Offset(dx, 0), Offset(dx, size.height), paint);
        }
    
        ///[绘制矩形]
        var _paint = Paint()
        ..color = Color(0xffff0000) //画笔颜色
        ..strokeWidth = 4 //画笔粗细
        ..style = PaintingStyle.stroke
        ..isAntiAlias = true;
        //1.Rect.fromLTRB(20, 60, 100, 180)
        canvas.drawRect(Rect.fromLTRB(20, 60, 100, 180), _paint);
        //2.Rect.fromLTWH(40, 100, 90, 80)
        canvas.drawRect(Rect.fromLTWH(40, 100, 90, 80), _paint);
        //3.Rect.fromCircle(center:Offset(100, 180),radius: 50)
        canvas.drawRect(Rect.fromCircle(center:Offset(100, 180),radius: 50), _paint);
        //4.Rect.fromPoints(Offset(100, 180), Offset(200, 100))
        canvas.drawRect(Rect.fromPoints(Offset(100, 180), Offset(200, 100)), _paint);
      }
        @override
        bool shouldRepaint(CustomPainter oldDelegate) => false;
    }

    结果:

  • 2.4 绘制圆角矩形

    圆角矩形的构建方式有以下几种:
    // 利用矩形的左边的X坐标、矩形顶部的Y坐标、矩形右边的X坐标、矩形底部的Y坐标
    // 以及可选的四个顶点圆角来确定圆角矩形
    RRect.fromLTRBAndCorners(
        double left,
        double top,
        double right,
        double bottom, {
        Radius topLeft: Radius.zero,
        Radius topRight: Radius.zero,
        Radius bottomRight: Radius.zero,
        Radius bottomLeft: Radius.zero,
    })
    
    // 需要先定义一个Rect,使用方式和fromLTRBAndCorners类似
    RRect.fromRectAndCorners(
        Rect rect,
        {
          Radius topLeft: Radius.zero,
          Radius topRight: Radius.zero,
          Radius bottomRight: Radius.zero,
          Radius bottomLeft: Radius.zero
        }
      )
    
    // 顶点X轴Y轴的圆角设置相同
    const Radius.circular(double radius)
    // 顶点X轴Y轴的圆角设置可以不相同
    const Radius.elliptical(this.x, this.y)

    绘制圆角矩形

    • void drawRRect(RRect rrect, Paint paint)
  •  
       
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: BackGrad(),
        );
      }
    }
    
    class BackGrad extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return new Container(
          child: Center(
            child: CustomPaint(//使用CustomPaint
              size: Size(300, 300),
              painter: MyPainter(),
            ),
          ),
        );
      }
    }
    
    class MyPainter extends CustomPainter {
      @override
      ///[画背景]
      void paint(Canvas canvas, Size size) {
        double eWidth = size.width/30;
        double eHeight = size.height/30;
    
        //画棋盘背景
        var paint = Paint()
        ..isAntiAlias = true
        ..style = PaintingStyle.fill//填充
        ..color = Color(0x77cdb175);//背景为纸黄色
        canvas.drawRect(Offset.zero & size, paint);
    
        //画棋盘网格
        paint
          ..style = PaintingStyle.fill //线
          ..color = Color(0xFF888888)
          ..strokeWidth = 1.0;
    
        for(int i = 0; i<=30;++i){
          double dy = eHeight * i;
          canvas.drawLine(Offset(0,dy), Offset(size.width,dy), paint);
        }
    
        for(int i = 0; i <= 30; ++i){
          double dx = eWidth * i;
          canvas.drawLine(Offset(dx, 0), Offset(dx, size.height), paint);
        }
    
        ///[绘制圆角矩形]
        var _paint = Paint()
        ..color = Color(0xffff0000) //画笔颜色
        ..strokeWidth = 4 //画笔粗细
        ..style = PaintingStyle.stroke
        ..isAntiAlias = true;
        //1.RRect.fromLTRBAndCorners(20,60,110,180,topLeft:Radius.circular(30))
        canvas.drawRRect(RRect.fromLTRBAndCorners(20,60,110,180,topLeft:Radius.circular(30)), _paint);
        //2.RRect.fromLTRBAndCorners(20,60,110,180,topLeft:Radius.elliptical(30,100))
        canvas.drawRRect(RRect.fromLTRBAndCorners(20,60,110,180,topLeft:Radius.elliptical(30,100)), _paint);
       
      }
        @override
        bool shouldRepaint(CustomPainter oldDelegate) => false;
    }

    结果:

    // 利用矩形的左边的X坐标、矩形顶部的Y坐标、矩形右边的X坐标、矩形底部的Y坐标
    // 以及一个Radius来确定圆角矩形
    RRect.fromLTRBR(double left, double top, double right, double bottom, Radius radius)
    
    // 需要先定义一个Rect,使用方式和fromLTRBR类似
    RRect.fromRectAndRadius(Rect rect, Radius radius)

    // 利用矩形的左边的X坐标、矩形顶部的Y坐标、矩形右边的X坐标、矩形底部的Y坐标
    // 以及设置四个顶点的X轴相同圆角,设置四个顶点的Y轴相同圆角来确定圆角矩形
    RRect.fromLTRBXY(double left, double top, double right, double bottom, double radiusX, double radiusY)
    // 需要先定义一个Rect,使用方式和fromLTRBXY类似
    RRect.fromRectXY(Rect rect, double radiusX, double radiusY)


    圆角矩形绘制有一个需要注意的地方,如果设置矩形的半径和圆角的半径相等,则绘制出来的是一个圆
    // 设置矩形的半径和圆角的半径不相等,效果如下面左图
    Rect rect = Rect.fromCircle(center: Offset(100.0, 100.0), radius: 50.0);
    RRect rRect = RRect.fromRectAndRadius(rect, Radius.circular(10.0));
    // 设置矩形的半径和圆角的半径相等,效果如下面右图
    Rect rect = Rect.fromCircle(center: Offset(100.0, 100.0), radius: 50.0);
    RRect rRect = RRect.fromRectAndRadius(rect, Radius.circular(50.0));
  • 2.5 绘制双圆角矩形

    Flutter中提供了绘制双圆角矩形的方法

    void drawDRRect(RRect outer, RRect inner, Paint paint)

    这里需要注意的是,内圆角矩形的半径不能大于外圆角矩形的半径,否则无法绘制。

    // 第一种:外圆角矩形的半径大于内圆角矩形半径,如下面左图
    Rect rectOut = Rect.fromCircle(center: Offset(100.0, 100.0), radius: 80.0);
    Rect rectInner = Rect.fromCircle(center: Offset(100.0, 100.0), radius: 40.0);
    // 第二种:外圆角矩形的半径等于内圆角矩形半径,如下面中图
    Rect rectOut = Rect.fromCircle(center: Offset(100.0, 100.0), radius: 80.0);
    Rect rectInner = Rect.fromCircle(center: Offset(100.0, 100.0), radius: 80.0);
    // 第三种:外圆角矩形的半径小于内圆角矩形半径,无法绘制,如下面右图
    Rect rectOut = Rect.fromCircle(center: Offset(100.0, 100.0), radius: 80.0);
    Rect rectInner = Rect.fromCircle(center: Offset(100.0, 100.0), radius: 81.0);

    接下来绘制上面三种情况下的双圆角矩形

    RRect rRectOut = RRect.fromRectAndRadius(rectOut, Radius.circular(10.0));
    RRect rRectInner = RRect.fromRectAndRadius(rectInner, Radius.circular(30.0));
    
    canvas.drawDRRect(rRectOut, rRectInner, _paint);

  • 2.6 绘制圆
    // 根据圆心坐标和圆的半径确定圆的位置和大小void drawCircle(Offset c, double radius, Paint paint)

    画一个圆

    // 绘制圆,Paint默认填充PaintingStyle.fill,如下面左图canvas.drawCircle(Offset(100.0, 100.0), 50.0, _paint);
    // 绘制圆,Paint设置为不填充,如下面右图_paint.style = PaintingStyle.stroke;canvas.drawCircle(Offset(100.0, 100.0), 50.0, _paint);

  • 2.7 绘制椭圆
    // Rect矩形区域的确定参见前文
    void drawOval(Rect rect, Paint paint)
    // 椭圆的宽度大于高度,如下面左图
    Rect rect= Rect.fromPoints(Offset(50.0, 50.0), Offset(130.0, 100.0));
    
    // 椭圆的宽度小于高度,如下面中图
    Rect rect= Rect.fromPoints(Offset(40.0, 80.0), Offset(80.0, 170.0));
    
    // 椭圆的宽度等于高度,绘制出的是圆,如下面右图
    Rect rect= Rect.fromPoints(Offset(80.0, 70.0), Offset(180.0, 170.0));

    绘制一个椭圆

    canvas.drawOval(rect, _paint);

  • 2.8 绘制圆弧 
    // rect:矩形区域// startAngle:开始的弧度
    // sweepAngle:扫过的弧度,正数顺时针,负数逆时针
    // useCenter:是否使用中心点绘制void drawArc(Rect rect, double startAngle, double sweepAngle, bool useCenter, Paint paint)

    弧度

    一周的弧度数为2πr/r=2π,360°角=2π弧度。

    因此,1弧度约为57.3°,即57°17’44.806’’,1°为π/180弧度,近似值为0.01745弧度。
    周角为2π弧度,平角(即180°角)为π弧度,直角为π/2弧度。

    下表是一些特殊的角度和弧度之间的换算

    角度 弧度
    0
    30° π/6
    45° π/4
    60° π/3
    90° π/2
    120° 2π/3
    180° π
    270° 3π/2
    360°

    使用π需要引入:

    import 'dart:math';

    下面我们尝试绘制一些圆弧

    var rect = Rect.fromCircle(center: Offset(100.0, 100.0), radius: 50.0);
    // 开始度数为90度,顺时针扫度数90度,如下方左图
    canvas.drawArc(rect, -pi / 2,  pi / 2, false, _paint);
    
    // 开始度数为0度,顺时针扫度数60度,如下方中图
    canvas.drawArc(rect, 0,  pi / 3, false, _paint);
    
    // 开始度数为0度,逆时针扫度数180度,使用中心点绘制,如下方右图
    canvas.drawArc(rect, 0,  -pi, true, _paint);复制代码
  • 2.9 绘制路径
    void drawPath(Path path, Paint paint)

    Path方法介绍

    // 将路径的起始点移动到指定的坐标点上
    void moveTo(double x, double y)
    
    // 将起始点和终点连成线段,如果没有显式的指明起始点坐标,默认为(0,0)
    void lineTo(double x, double y)
    // 起始点(30,30),终点(100,100),如下面左图
    Path path = new Path();
    path.moveTo(30, 30);
    path.lineTo(100, 100);
    canvas.drawPath(path, _paint);
    
    // 起始点默认(0,0),终点(100,100),如下面右图
    Path path = new Path();
    path.lineTo(100, 100);
    canvas.drawPath(path, _paint);复制代码

    // 相对于当前位置坐标在X轴上偏移dx,在Y轴上偏移dy
    void relativeMoveTo(double dx, double dy)
    // 起始点(0,0),和(20,20)之间连线,后移动到(30,30),和(70,70)之间连线
    Path path = new Path();
    path.lineTo(20, 20);
    path.moveTo(30, 30);
    path.lineTo(70, 70);
    canvas.drawPath(path, _paint);
    
    // 起始点(0,0),和(20,20)之间连线,
    // 后以当前(20,20)坐标为起始点X轴偏移30,Y轴偏移30,
    // 即偏移后的新坐标(50,50)和(70,70)之间连线
    Path path = new Path();
    path.lineTo(20, 20);
    path.relativeMoveTo(30, 30);
    path.lineTo(70, 70);
    canvas.drawPath(path, _paint);

     

    // 确定相对于当前位置坐标,在X轴上偏移dx,在Y轴上偏移dy后的新坐标,
    // 确定好之后当前位置坐标和新坐标之间连线
    void relativeLineTo(double dx, double dy)
    // (0,0)和(10,10)连线,(10,10)和(20,20)连线,如下面左图
    Path path = new Path();
    path.lineTo(10, 10);
    path.lineTo(20, 20);
    canvas.drawPath(path, _paint);
    
    // (0,0)和(10,10)连线,(10,10)和当前位置偏移后的新坐标(30,30)连线
    Path path = new Path();
    path.lineTo(10, 10);
    path.relativeLineTo(20, 20);
    canvas.drawPath(path, _paint);

     

     

    // 绘制二阶贝塞尔曲线
    // 绘制需要一个起始点、一个终点和一个控制点
    // 此方法的前两个参数是控制点的坐标,后两个参数是终点的坐标
    void quadraticBezierTo(double x1, double y1, double x2, double y2)
    
    // 绘制二阶贝塞尔曲线
    // 绘制需要一个起始点、一个终点和一个控制点
    // 此方法的前两个参数是控制点的坐标,后两个参数是终点的坐标
    // 和quadraticBezierTo不同在于终点坐标的位置是当前起点位置坐标X轴偏移x2,Y轴偏移y2后的坐标
    // 举例(略)
    void relativeQuadraticBezierTo(double x1, double y1, double x2, double y2)
    // 绘制曲线,如下面左图
    Path path = new Path();
    path.moveTo(150, 100);
    path.quadraticBezierTo(250, 40, 250, 200);
    canvas.drawPath(path, _paint);
    // 绘制控制点
    _paint.strokeWidth = 8;
    _paint.color = Colors.blue;
    canvas.drawPoints(PointMode.points, [Offset(250, 40)], _paint);
    // 绘制曲线,如下面中图
    Path path = new Path();
    path.moveTo(150, 100);
    path.quadraticBezierTo(200, 200, 250, 200);
    canvas.drawPath(path, _paint);
    // 绘制控制点
    _paint.strokeWidth = 8;
    _paint.color = Colors.blue;
    canvas.drawPoints(PointMode.points, [Offset(200, 200)], _paint);
    // 绘制曲线,如下面右图
    Path path = new Path();
    path.moveTo(150, 100);
    path.quadraticBezierTo(60, 130, 150, 200);
    canvas.drawPath(path, _paint);
    // 绘制控制点
    _paint.strokeWidth = 8;
    _paint.color = Colors.blue;
    canvas.drawPoints(PointMode.points, [Offset(60, 130)], _paint);

    // 绘制三阶贝塞尔曲线
    // 绘制需要一个起始点、一个终点和两个控制点
    // 此方法的前四个参数分别是两个控制点的XY坐标,后两个参数是终点的坐标
    void cubicTo(double x1, double y1, double x2, double y2, double x3, double y3)
    
    // 绘制三阶贝塞尔曲线
    // 绘制需要一个起始点、一个终点和两个控制点
    // 此方法的前四个参数分别是两个控制点的XY坐标,后两个参数是终点的坐标
    // 和cubicTo不同在于终点坐标的位置是当前起点位置坐标X轴偏移x2,Y轴偏移y2后的坐标
    // 举例(略)
    void relativeCubicTo(double x1, double y1, double x2, double y2, double x3, double y3)

    下面使用cubicTo绘制心形效果

    var width = 300;
    var height = 300;
    
    Path leftPath = new Path();
    // 画笔移动到左边曲线起点
    leftPath.moveTo(width / 2, height / 4 + 20);
    leftPath.cubicTo(
        width / 8, height / 10, 
        width / 13, (height * 2) / 5,
        width / 2, height - height / 4);
    // 绘制心形左边曲线    
    canvas.drawPath(leftPath, _paint);
    
    Path rightPath = new Path();
    // 画笔移动到右边曲线起点
    rightPath.moveTo(width / 2, height / 4 + 20);
    rightPath.cubicTo(
        width - width / 8, height / 10,
        (width * 12) / 13, (height * 2) / 5,
        width / 2, height - height / 4);
    // 绘制心形右边曲线    
    canvas.drawPath(rightPath, _paint);
    // 绘制两个控制点
    _paint.strokeWidth = 8;
    _paint.color = Colors.blue;
    canvas.drawPoints(
        PointMode.points,
        [
            Offset(width / 8, height / 10),
            Offset(width / 13, (height * 2) / 5),
            Offset(width - width / 8, height / 10),
            Offset((width * 12) / 13, (height * 2) / 5)
        ],
        _paint);

     

    // 画弧线
    // rect:矩形区域
    // startAngle:开始的弧度
    // sweepAngle:扫过的弧度,正数顺时针,负数逆时针
    // forceMoveTo:true-画笔从当前位置抬起来,移动到弧线起点
    //              false-画笔从当前位置不抬起来直接连线到弧线起点
    void arcTo(Rect rect, double startAngle, double sweepAngle, bool forceMoveTo)
    // 效果如下面左图,画笔从(80, 100)抬起来,移动到弧线起点
    boo forceMoveTo = true;
    
    // 效果如下面右图,画笔从(80, 100)不抬起来直接连线到弧线起点
    boo forceMoveTo = false;
    
    Path path = new Path();
    path.moveTo(20, 40);
    path.lineTo(80, 100);
    path.arcTo(new Rect.fromLTWH(60, 60, 100, 100), 0, pi / 2, forceMoveTo);
    canvas.drawPath(path, _paint);

    // 封闭当前路径,以上面代码为例,如下图
    void close();

    其他方法

    // 为path添加矩形
    void addRect(Rect rect);
    
    // 为path添加圆角矩形
    void addRRect(RRect rrect);
    
    // 为path添加椭圆
    void addOval(Rect oval)
    
    // 为path添加圆弧
    void addArc(Rect oval, double startAngle, double sweepAngle);
    
    // 为path添加多边形,points-坐标数组,close-是否首位封闭
    void addPolygon(List<Offset> points, bool close);
    
    // 为path添加一个path,偏移offset坐标
    void addPath(Path path, Offset offset, {Float64List matrix4})
    
    // path路径是否包含point坐标点
    bool contains(Offset point)
    
    // 重置路径
    void reset();
    
    // 给路径做matrix4变换
    Path transform(Float64List matrix4)
    下面举一个addPath的例子
    Path path = new Path();
    path.lineTo(20, 20);
    
    Path path1 = new Path();
    path1.lineTo(40, 40);
    
    // 如下图,addPath方法就是在(30,30)坐标位置偏移(40,40)并连线
    path.addPath(path1, Offset(30, 30));
    
    canvas.drawPath(path, _paint);

三,总结

本文列举了Flutter开发中,Canvas绘制流程常用的方法并提供了简单的示例,可以看出,和Android的Canvas还是很相似的,上手也非常的快。要做出酷炫的Widget,最好还是需要配合动画效果,当然,用canvas做些简单的icon也是可以的。

四,转载地址

链接:https://juejin.im/post/5cb56f5ef265da03474df6b5


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM