一,概述
- 畫布(Canvas)
畫布是一個矩形區域,我們可以控制其每一像素來繪制我們想要的內容
Canvas 擁有多種繪制點、線、路徑、矩形、圓形、以及添加圖像等的方法,結合這些方法我們可以繪制出千變萬化的畫面。
Canvas中有多個與繪制相關的方法,如drawLine()、drawRect()、drawOval()、drawOval()、等方法。
- 畫筆(Paint)
雖然,畫布可以畫點,線,路徑,矩形,圓形等,但是決定這些圖形顏色、粗細表現的還是畫筆
Paint非常好理解,就是我們用來畫圖形的工具,我們可以設置畫筆的顏色、粗細、是否抗鋸齒、筆觸形狀以及作畫風格。通過這些屬性我們可以很方便的來定制自己的UI效果,當然我們在“作畫”的過程中可以定義多個畫筆,這樣更方便我們對圖形的繪制
畫筆Paint的屬性:
///[定義畫筆] Paint _paint = Paint() ..color = Colors.blueAccent //畫筆顏色 ..strokeCap = StrokeCap.round//畫筆筆觸類型 ..isAntiAlias = true //是否啟動抗鋸齒 ..blendMode = BlendMode.exclusion//顏色混合模式 ..style = PaintingStyle.fill //繪畫風格,默認為填充 ..colorFilter = ColorFilter.mode(Colors.blueAccent, BlendMode.exclusion)///顏色渲染模式,一般是矩陣效果來改變的,但是flutter中只能使用顏色混合模式 ..maskFilter = MaskFilter.blur(BlurStyle.inner,3.0)//模糊遮罩效果 ..filterQuality = FilterQuality.high //顏色渲染模式質量 ..strokeWidth = 15.0; //畫筆的寬度
二,繪制實例
-
自定義組件
Flutter中如果想要自定義繪制,那么你需要用到 CustomPaint 和 CustomPainter ; CustomPaint是Widget的子類。
-
構造方法
const CustomPaint({ Key key, this.painter, this.foregroundPainter, this.size = Size.zero, this.isComplex = false, this.willChange = false, Widget child, }) :super(key: key, child: child);
我們只需要關心三個參數,painter,foregroundPainter 和 child , 這里需要說明一下,painter 是繪制的 backgroud 層,而child 是在backgroud之上繪制,foregroundPainter 是在 child 之上繪制,所以這里就有了個層級關系,這跟android里面的backgroud與foreground是一個意思,那這兩個painter的應用場景是什么呢?假如你只是單純的想繪制一個圖形,只用painter就可以了,但是如果你想給繪制區域添加一個背景(顏色,圖片,等等),這時候如果使用 painter是會有問題的,painter的繪制會被child 層覆蓋掉,此時你只需要將painter替換成foregroundPainter,然會顏色或者圖片傳遞給child即可。
如果是Android繪制幾何圖形,應該是重寫View的onLayout() 和 onDraw方法,但是Flutter實現繪制,必須繼承CustomPainter並重寫 paint(Canvascanvas, Size size)和 shouldRepaint (CustomPainteroldDelegate) 方法 ,第一個參數canvas就是我們繪制的畫布了(跟Android一模一樣),paint第二個參數Size就是上面CustomPaint構造方法傳入的size, 決定繪制區域的寬高信息
既然Size已經確定了,現在就定義下繪制區域的邊界,一般我做類似的UI,都會定義一個最基本的padding, 一般取值為16 , 因為繪制的內容與坐標軸之間需要找到一個基准線,這樣更容易繪制,而且調試邊距也很靈活
-
工程構建
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: MyHomePage(title: 'Painter繪制直線'), ); } } class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title;
@override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: CustomPaint( size: Size(300, 300), painter:MyPainter() , ), ) ); } } class MyPainter extends CustomPainter { ///[定義畫筆] Paint _paint = Paint() ..color = Colors.blueAccent //畫筆顏色 ..strokeCap = StrokeCap.round//畫筆筆觸類型 ..isAntiAlias = true //是否啟動抗鋸齒 ..style = PaintingStyle.fill //繪畫風格,默認為填充 ..strokeWidth = 5.0; //畫筆的寬度 @override void paint(Canvas canvas, Size size) { canvas.drawLine(Offset(20, 20), Offset(100,100), _paint); } @override bool shouldRepaint(CustomPainter oldDelegate) { return null; } }
-
-
定義畫筆
///[定義畫筆] Paint _paint = Paint() ..color = Colors.blueAccent //畫筆顏色 ..strokeCap = StrokeCap.round//畫筆筆觸類型 ..isAntiAlias = true //是否啟動抗鋸齒 ..style = PaintingStyle.fill //繪畫風格,默認為填充 ..strokeWidth = 5.0; //畫筆的寬度
-
內容繪制
-
繪制直線(drawLine)
使用給定的塗料在給定點之間繪制一條線。 該行被描邊,此調用忽略[Paint.style]的值。p1
和p2
參數為兩個點的坐標 , 在這兩點之間繪制一條直線。
- 系統方法
void drawLine(Offset p1, Offset p2, Paint paint)
-
使用方法
class MyPainter extends CustomPainter {///[定義畫筆]Paint _paint = Paint()..color = Colors.blueAccent //畫筆顏色..strokeCap = StrokeCap.round//畫筆筆觸類型..isAntiAlias = true //是否啟動抗鋸齒..style = PaintingStyle.fill //繪畫風格,默認為填充..strokeWidth = 5.0; //畫筆的寬度
@overridevoid paint(Canvas canvas, Size size) {canvas.drawLine(Offset(20, 20), Offset(100,100), _paint);}
@overridebool shouldRepaint(CustomPainter oldDelegate) {return null;}} -
效果示例
- 系統方法
-
繪制點(drawPoints)
繪制點也是非常的簡單,3個參數分別為: PointMode枚舉,坐標 list 和 paint。PointMode的枚舉類型有三個,points(點),lines(線,隔點連接),polygon(線,相鄰連接)- 系統方法
void drawPoints(PointMode pointMode, List points, Paint paint)
-
使用方法
class MyPainter extends CustomPainter { ///[定義畫筆] Paint _paint = Paint() ..color = Colors.blueAccent //畫筆顏色 ..strokeCap = StrokeCap.round//畫筆筆觸類型 ..isAntiAlias = true //是否啟動抗鋸齒 ..style = PaintingStyle.fill //繪畫風格,默認為填充 ..strokeWidth = 5.0; //畫筆的寬度 @override void paint(Canvas canvas, Size size) { ///PointMode的枚舉類型有三個,points(點),lines(線,隔點連接),polygon(線,相鄰連接) canvas.drawPoints( PointMode.points, [ Offset(20.0, 40.0), Offset(100.0, 120.0), Offset(100.0, 220.0), Offset(200.0, 220.0), Offset(200.0, 120.0), Offset(280.0, 40.0), Offset(20, 40.0), ], _paint ); } @override bool shouldRepaint(CustomPainter oldDelegate) { return null; } }
- 示例
- PointMode改為points
- 示例效果圖
- 示例效果圖
- PointMode改為polygon
PointMode改為polygon,相鄰點互相連接
- 示例效果
- 示例效果
- PointMode改為lines。
PointMode為lines時,兩個點相互連接,也就是說第一個和第二個點連接,第三個跟第四個連接,如果最后只有一個點就舍棄不連接了,在我們的例子中有7個點,所以圖中只有三條連線。
- 示例效果
- 示例效果
- PointMode改為points
- 系統方法
-
繪制圓rawCircle
參數分別為:圓心的坐標、半徑和paint即可。圓形是否填充或描邊(或兩者)由Paint.style
控制。- 系統方法
void drawCircle(Offset c, double radius, Paint paint)
- 使用方法
class MyPainter extends CustomPainter { ///[定義畫筆] Paint _paint = Paint() ..color = Colors.blueAccent //畫筆顏色 ..strokeCap = StrokeCap.round//畫筆筆觸類型 ..isAntiAlias = true //是否啟動抗鋸齒 ..style = PaintingStyle.stroke //繪畫風格,默認為填充 ..strokeWidth = 5.0; //畫筆的寬度 @override void paint(Canvas canvas, Size size) { //繪制圓 參數(圓心,半徑,畫筆) canvas.drawCircle(Offset(140,80), 80,_paint..color = Colors.green); } @override bool shouldRepaint(CustomPainter oldDelegate) { return null; } }
- 示例
- PaintStyle.stroke 不填充
..style = PaintingStyle.stroke //繪畫風格,默認為不填充-
示例效果
-
- PaintStyle.stroke 填充
將畫筆Paint的style改成了stroke,然后我們將畫筆style改成fill (填充) ,fill也是畫筆的style的默認值
- 示例效果
- 示例效果
- PaintStyle.stroke 不填充
- 系統方法
-
繪制橢圓drawOval
繪制一個軸對稱的橢圓形,參數為一個矩形和畫筆paint.
- 系統方法
void drawOval(Rect rect, Paint paint)
-
使用方法
class MyPainter extends CustomPainter { ///[定義畫筆] Paint _paint = Paint() ..color = Colors.blueAccent //畫筆顏色 ..strokeCap = StrokeCap.round//畫筆筆觸類型 ..isAntiAlias = true //是否啟動抗鋸齒 ..style = PaintingStyle.fill //繪畫風格,默認為填充 ..strokeWidth = 5.0; //畫筆的寬度 @override void paint(Canvas canvas, Size size) { //使用左上和右下角坐標來確定矩形的大小和位置,橢圓是在這個矩形之中內切的 Rect rect = Rect.fromPoints(Offset(100.0, 40.0), Offset(220.0, 100.0)); canvas.drawOval(rect, _paint..color=Colors.green); }
}
-
示例
Rect也有多種構建方式- fromLTWH(double left, double top, double width, double height)
使用矩形左邊的X坐標、矩形頂部的Y坐標矩形的寬高來確定矩形的大小和位置
- 使用方法
@override void paint(Canvas canvas, Size size) { //使用矩形左邊的X坐標、矩形頂部的Y坐標矩形的寬高來確定矩形的大小和位置 Rect rect = Rect.fromLTWH(100, 20, 100, 60); canvas.drawOval(rect, _paint..color=Colors.green); }
- 示例效果
- 使用方法
- fromLTRB(double left, double top, double right, double bottom)
使用矩形左邊的X坐標、矩形頂部的Y坐標、矩形右邊的X坐標、矩形底部的Y坐標來確定矩形的大小和位置
- 使用方法
@override void paint(Canvas canvas, Size size) { //使用矩形左邊的X坐標、矩形頂部的Y坐標、矩形右邊的X坐標、矩形底部的Y坐標來確定矩形的大小和位置 Rect rect = Rect.fromLTRB(30, 30, 160, 140); canvas.drawOval(rect, _paint..color=Colors.green); }
- 示例效果
- 使用方法
- fromCircle({ Offset center, double radius })
使用圓的圓心點坐標和半徑和確定外切矩形的大小和位置- 使用方法
@override void paint(Canvas canvas, Size size) { //使用圓的圓心點坐標和半徑和確定外切矩形的大小和位置 Rect rect = Rect.fromCircle(center: Offset(100, 40),radius: 60); canvas.drawOval(rect, _paint..color=Colors.green); }
- 示例效果
- 使用方法
-
fromPoints(Offset a, Offset b)
使用左上和右下角坐標來確定矩形的大小和位置- 使用方法
@override void paint(Canvas canvas, Size size) { //使用左上和右下角坐標來確定矩形的大小和位置,橢圓是在這個矩形之中內切的 Rect rect = Rect.fromPoints(Offset(100.0, 40.0), Offset(220.0, 100.0)); canvas.drawOval(rect, _paint..color=Colors.green); }
-
示例效果
- 使用方法
- fromLTWH(double left, double top, double width, double height)
- 系統方法
-
繪制圓弧drawArc
首先還是需要Rect來確認圓弧的位置,還需要開始的弧度、結束的弧度、是否使用中心點繪制(圓弧是否向中心閉合)、以及paint.
- 系統方法
void drawArc(Rect rect, double startAngle, double sweepAngle, bool useCenter, Paint paint)
-
使用方法
class MyPainter extends CustomPainter { ///[定義畫筆] Paint _paint = Paint() ..color = Colors.blueAccent //畫筆顏色 ..strokeCap = StrokeCap.round//畫筆筆觸類型 ..isAntiAlias = true //是否啟動抗鋸齒 ..style = PaintingStyle.fill //繪畫風格,默認為填充 ..strokeWidth = 5.0; //畫筆的寬度 @override void paint(Canvas canvas, Size size) { // Rect來確認圓弧的位置,還需要開始的弧度、結束的弧度、是否使用中心點繪制、以及paint弧度 Rect rect = Rect.fromCircle(center: Offset(100, 40),radius:80); canvas.drawArc(rect,0.0,0.8,false,_paint); } @override bool shouldRepaint(CustomPainter oldDelegate) { return null; } }
-
拓展
-
弧度
根據定義,一周的弧度數為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π - 使用方法
@override void paint(Canvas canvas, Size size) { // Rect來確認圓弧的位置,還需要開始的弧度、結束的弧度、是否使用中心點繪制、以及paint弧度 const PI = 3.1415926; Rect rect2 = Rect.fromCircle(center: Offset(200.0, 50.0), radius: 80.0); canvas.drawArc(rect2, 0.0, PI / 2, false, _paint); }
-
示例效果
- 使用方法
- 圓弧向中心點閉合
將useCenter改成true,圓弧向中心點閉合了
- 使用方法
@override void paint(Canvas canvas, Size size) { // Rect來確認圓弧的位置,還需要開始的弧度、結束的弧度、是否使用中心點繪制、以及paint弧度 const PI = 3.1415926; Rect rect2 = Rect.fromCircle(center: Offset(100.0, 50.0), radius: 80.0); canvas.drawArc(rect2, 0.0, PI / 2, true, _paint); }
- 示例效果
- 使用方法
-
- 系統方法
-
繪制圓角矩形drawDRRect
使用RRect確定矩形大小及弧度,使用paint來完成繪制。RRect構建起來也非常的方便,直接使用fromRectAndRadius即可- 系統方法
RRect構建
RRect.fromRectAndRadius(rect, radius)
RRect確定矩形大小及弧度,使用paint來完成繪制
void drawRRect(RRect rrect, Paint paint)
- 使用方法
class MyPainter extends CustomPainter { ///[定義畫筆] Paint _paint = Paint() ..color = Colors.blueAccent //畫筆顏色 ..strokeCap = StrokeCap.round//畫筆筆觸類型 ..isAntiAlias = true //是否啟動抗鋸齒 ..style = PaintingStyle.stroke //繪畫風格,默認為填充 ..strokeWidth = 5.0; //畫筆的寬度 @override void paint(Canvas canvas, Size size) { //用Rect構建一個邊長50,中心點坐標為100,100的矩形 Rect rect = Rect.fromCircle(center: Offset(140.0, 50.0), radius: 50.0); //根據上面的矩形,構建一個圓角矩形 RRect rrect = RRect.fromRectAndRadius(rect, Radius.circular(20.0)); canvas.drawRRect(rrect, _paint); } @override bool shouldRepaint(CustomPainter oldDelegate) { return null; } }
- 示例
- rect依然用來表示矩形的位置和大小,radius用來表示圓角的大小
- 使用方法
@override void paint(Canvas canvas, Size size) { //用Rect構建一個邊長50,中心點坐標為100,100的矩形 Rect rect = Rect.fromCircle(center: Offset(140.0, 50.0), radius: 50.0); //根據上面的矩形,構建一個圓角矩形 RRect rrect = RRect.fromRectAndRadius(rect, Radius.circular(20.0)); canvas.drawRRect(rrect, _paint); }
- 示例效果
- 使用方法
- 將圓角的半徑設置為邊長(從20改成50)
- 使用方法
@override void paint(Canvas canvas, Size size) { //用Rect構建一個邊長50,中心點坐標為100,100的矩形 Rect rect = Rect.fromCircle(center: Offset(140.0, 50.0), radius: 50.0); //根據上面的矩形,構建一個圓角矩形 RRect rrect = RRect.fromRectAndRadius(rect, Radius.circular(50.0)); canvas.drawRRect(rrect, _paint); }
-
示例效果
- 使用方法
- rect依然用來表示矩形的位置和大小,radius用來表示圓角的大小
- 系統方法
-
繪制雙圓角矩形drawRRect
和drawRRect類似,使用RRect確定內部、外部矩形大小及弧度,使用paint來完成繪制。
- 系統方法
void drawDRRect(RRect outer, RRect inner, Paint paint)
- 使用方法
使用Rect.fromCircle來創建Rect,使用RRect.fromRectAndRadius來創建RRect
class MyPainter extends CustomPainter { ///[定義畫筆] Paint _paint = Paint() ..color = Colors.blueAccent //畫筆顏色 ..strokeCap = StrokeCap.round//畫筆筆觸類型 ..isAntiAlias = true //是否啟動抗鋸齒 ..style = PaintingStyle.stroke //繪畫風格,默認為填充 ..strokeWidth = 5.0; //畫筆的寬度 @override void paint(Canvas canvas, Size size) { //繪制兩個矩形 Rect rect1 = Rect.fromCircle(center: Offset(100.0, 100.0), radius: 60.0); Rect rect2 = Rect.fromCircle(center: Offset(100.0, 100.0), radius: 40.0); //分別繪制外部圓角矩形和內部的圓角矩形 RRect outer = RRect.fromRectAndRadius(rect1, Radius.circular(10.0)); RRect inner = RRect.fromRectAndRadius(rect2, Radius.circular(10.0)); canvas.drawDRRect(outer, inner, _paint); } @override bool shouldRepaint(CustomPainter oldDelegate) { return null; } }
- 示例
- 示例效果
- 嘗試調整角度的度數大小
- 使用方法
@override void paint(Canvas canvas, Size size) { //繪制兩個矩形 Rect rect1 = Rect.fromCircle(center: Offset(150.0, 40.0), radius: 60.0); Rect rect2 = Rect.fromCircle(center: Offset(150.0, 40.0), radius: 40.0); //分別繪制外部圓角矩形和內部的圓角矩形 RRect outer = RRect.fromRectAndRadius(rect1, Radius.circular(30.0)); RRect inner = RRect.fromRectAndRadius(rect2, Radius.circular(10.0)); canvas.drawDRRect(outer, inner, _paint); }
-
示例效果
- 使用方法
- 示例效果
- 系統方法
-
繪制路徑drawPath
繪制路徑,首先需要一個要繪制的路徑path,然后就是這個paint了。
Path的常用方法:
方法名 作用 moveTo 將路徑起始點移動到指定的位置 relativeMoveTo 相對於當前位置移動到 lineTo 從當前位置連接指定點 relativeLineTo 相對當前位置連接到 arcTo 曲線 conicTo 貝塞爾曲線 add** 添加其他圖形,如addArc,在路徑是添加圓弧 contains 路徑上是否包括某點 transfor 給路徑做matrix4變換 combine 結合兩個路徑 close 關閉路徑,連接路徑的起始點 reset 重置路徑,恢復到默認狀態
-
系統方法
void drawPath(Path path, Paint paint)
-
使用方法
class MyPainter extends CustomPainter { ///[定義畫筆] Paint _paint = Paint() ..color = Colors.blueAccent //畫筆顏色 ..strokeCap = StrokeCap.round//畫筆筆觸類型 ..isAntiAlias = true //是否啟動抗鋸齒 ..style = PaintingStyle.stroke //繪畫風格,默認為填充 ..strokeWidth = 5.0; //畫筆的寬度 @override void paint(Canvas canvas, Size size) { //新建了一個path,然后將路徑起始點移動到坐標(100,100)的位置 Path path = new Path()..moveTo(100.0, 100.0); path.lineTo(200.0, 200.0); canvas.drawPath(path, _paint); } @override bool shouldRepaint(CustomPainter oldDelegate) { return null; } }
-
示例
-
繪制直線
首先新建了一個path,然后將路徑起始點移動到坐標(100,100)的位置,
然后從這個位置連接(200,200)的點.-
使用方法
@override void paint(Canvas canvas, Size size) { //新建了一個path,然后將路徑起始點移動到坐標(100,100)的位置 Path path = new Path()..moveTo(100.0, 100.0); path.lineTo(200.0, 200.0); canvas.drawPath(path, _paint); }
-
效果圖
-
- 繪制多個路徑
- 使用方法
@override void paint(Canvas canvas, Size size) { Path path = new Path()..moveTo(100.0, 100.0); path.lineTo(200.0, 200.0); path.lineTo(100.0, 300.0); path.lineTo(150.0, 350.0); path.lineTo(150.0, 500.0); canvas.drawPath(path, _paint); }
- 示例效果
- 使用方法
-
-
-
-
參看:https://www.cnblogs.com/Free-Thinker/p/10218829.html