一,概述
我們知道,應用開發如果單純只靠系統提供的控件,對於那些較為絢爛界面效果來說是遠遠不夠的,這就需要開發者自己去自定義繪制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.@overrideWidget 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° 0 30° π/6 45° π/4 60° π/3 90° π/2 120° 2π/3 180° π 270° 3π/2 360° 2π 使用π需要引入:
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也是可以的。