【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