Flutter CustomPaint自定義繪畫示例詳解
正文
CustomPaint是Flutter中用於自由繪制的一個widget,它與android原生的繪制規則基本一致,以當前Canves(畫佈)的左上角為原點進行繪制。在有些場景中,我們會需要繪制一些高度定制化的組件,比如 UI 設計師給我們出瞭個難題 —— 弄一個奇形怪狀的邊框。這個時候我們就不能直接使用 Flutter 自帶的那些組件瞭,而是需要手動繪制組件,那就會需要用到 CuntomPaint
組件。CustomPaint
組件和前端的 Canvas
差不多,允許我們在一個畫佈上繪制各種元素,包括點、線、矩形、圓弧、文字、圖片等等。
CustomPaint 介紹
CustomPaint
是一個 Widget,其中有三個重要的參數:
CustomPaint( child: childWidget(), foregroundPainter: foregroundPainter(), painter: backgroundPainter(), )
child
:CustomPaint
的子組件;
painter
和 foregroundPainter
:都是 CustomPainter
類,用於定義 canvas
繪制的內容。區別在於,首先是執行 painter
的繪制指令。然後是在背景上渲染 child
子組件。最後,foregroundPainter
的內容會繪制在 child
上一層。
案例展示:
import 'package:demo202112/utils/common_appbar.dart'; import "package:flutter/material.dart"; class MyPaint extends StatelessWidget { const MyPaint({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: getAppBar('CustomPaint'), body: CustomPaint( painter: MyPainer(), child: Container(height: 80,width: 80,child: Text('child測試'),color: Colors.red,), foregroundPainter: MyForeGroundPainer(), ), ); } } class MyPainer extends CustomPainter{ late Paint _paint; @override void paint(Canvas canvas, Size size) { _paint = Paint(); _paint.color = Colors.blue; canvas.drawCircle(Offset(100, 100), 100, _paint); canvas.drawLine(Offset(300, 300), Offset(400, 400), _paint); // TODO: implement paint } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { // TODO: implement shouldRepaint throw UnimplementedError(); } } class MyForeGroundPainer extends CustomPainter{ late Paint _paint; @override void paint(Canvas canvas, Size size) { _paint = Paint(); _paint.color = Colors.green; canvas.drawCircle(Offset(100, 100), 70, _paint); // canvas.drawLine(Offset(300, 300), Offset(400, 400), _paint); // TODO: implement paint } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { // TODO: implement shouldRepaint throw UnimplementedError(); } }
運行效果:
child: 紅色區域,傳入一個子widget,這個widget圖層會在painter在上,在foregroundPainter之下。
painter:藍色區域。
foregroundPainter:綠色區域,它與painter都是CustomPainter類型的。通過名字大概也就知道瞭,它會在painter的上層,也就是說在同樣的位置去繪制,foregroundPainter 會覆蓋painter。
CustomPainter
提供瞭一個paint繪圖方法供我們繪制圖形,該方法攜帶canvas
和size
兩個參數,其中 canvas
是畫佈,size
是畫佈大小。canvas
提供瞭很多繪制圖形的方法,比如繪制路徑、矩形、圓形和線條等等。
//畫圓 drawCircle(Offset c, double radius, Paint paint) → void //畫圖片 drawImage(Image image, Offset p, Paint paint) → void //畫九宮圖 drawImageNine(Image image, Rect center, Rect dst, Paint paint) → void //畫線 drawLine(Offset p1, Offset p2, Paint paint) → void //畫橢圓 drawOval(Rect rect, Paint paint) → void //畫文字 drawParagraph(Paragraph paragraph, Offset offset) → void //畫Rect區域 drawRect(Rect rect, Paint paint) → void //畫陰影 drawShadow(Path path, Color color, double elevation, bool transparentOccluder) → void
繪制點
class MyPoints extends CustomPainter{ Paint _paint = Paint() ..color = Colors.red ..strokeWidth = 15; @override void paint(Canvas canvas, Size size) { // TODO: implement paint var points =[ Offset(0, 0), Offset(size.width/2, size.height/2), Offset(size.width, size.height), ]; canvas.drawPoints(PointMode.points, points, _paint); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { // TODO: implement shouldRepaint throw UnimplementedError(); } }
運行效果:
PointMode3種模式
- points:點
- lines:將2個點繪制為線段,如果點的個數為奇數,最後一個點將會被忽略
- polygon:將整個點繪制為一條線
繪制線 和路徑
class MyGraph extends CustomPainter{ final Paint _paint = Paint() ..color = Colors.red ..strokeWidth = 15; final Paint _paintPath = Paint() ..color = Colors.blue ..strokeWidth = 5 ..style = PaintingStyle.fill; @override void paint(Canvas canvas, Size size) { // TODO: implement paint //繪制線 canvas.drawLine(Offset(0, 30),Offset(size.width-30, size.height), _paint); //繪制路徑 var _path = Path() ..moveTo(0, 0) ..lineTo(size.width, 0) ..lineTo(size.width, size.height) ..close(); canvas.drawPath(_path, _paintPath); //這裡註意Paint.style,還可以設置為PaintingStyle.fill, //繪制圓形 canvas.drawCircle(Offset(size.width/2+50, size.height/2+50), 20, _paint); //繪制橢圓 canvas.drawOval(Rect.fromLTRB(0, 0, size.width, size.height/2), _paint); //繪制弧 canvas.drawArc(Rect.fromLTRB(0, 0, size.width, size.height), 0, pi/2, true, _paint); //繪制圓角矩形 canvas.drawRRect(RRect.fromLTRBR(0, 0, size.width, size.height, Radius.circular(10)), _paint); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { // TODO: implement shouldRepaint throw UnimplementedError(); } }
運行效果:
繪制五子棋
首先繪制背景,淡黃色,再繪制棋盤網格線,隨後繪制黑白子,具體代碼:
class CustomPaintRoute extends StatelessWidget { @override Widget build(BuildContext context) { return Center( child: CustomPaint( size: Size(300, 300), //指定畫佈大小 painter: MyPainter(), ), ); } } class MyPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { double eWidth = size.width / 15; double eHeight = size.height / 15; //畫棋盤背景 var paint = Paint() ..isAntiAlias = true ..style = PaintingStyle.fill //填充 ..color = Color(0x77cdb175); //背景為紙黃色 canvas.drawRect(Offset.zero & size, paint); //畫棋盤網格 paint ..style = PaintingStyle.stroke //線 ..color = Colors.black87 ..strokeWidth = 1.0; for (int i = 0; i <= 15; ++i) { double dy = eHeight * i; canvas.drawLine(Offset(0, dy), Offset(size.width, dy), paint); } for (int i = 0; i <= 15; ++i) { double dx = eWidth * i; canvas.drawLine(Offset(dx, 0), Offset(dx, size.height), paint); } //畫一個黑子 paint ..style = PaintingStyle.fill ..color = Colors.black; canvas.drawCircle( Offset(size.width / 2 - eWidth / 2, size.height / 2 - eHeight / 2), min(eWidth / 2, eHeight / 2) - 2, paint, ); //畫一個白子 paint.color = Colors.white; canvas.drawCircle( Offset(size.width / 2 + eWidth / 2, size.height / 2 - eHeight / 2), min(eWidth / 2, eHeight / 2) - 2, paint, ); } //在實際場景中正確利用此回調可以避免重繪開銷,本示例我們簡單的返回true @override bool shouldRepaint(CustomPainter oldDelegate) => true; }
運行效果:
繪制是比較昂貴的操作,所以我們在實現自繪控件時應該考慮到性能開銷,下面是兩條關於性能優化的建議:
- 盡可能的利用好
shouldRepaint
返回值;在UI樹重新build時,控件在繪制前都會先調用該方法以確定是否有必要重繪;假如我們繪制的UI不依賴外部狀態,那麼就應該始終返回false,因為外部狀態改變導致重新build時不會影響我們的UI外觀;如果繪制依賴外部狀態,那麼我們就應該在shouldRepaint中判斷依賴的狀態是否改變,如果已改變則應返回true
來重繪,反之則應返回false
不需要重繪。 - 繪制盡可能多的分層;在上面五子棋的示例中,我們將棋盤和棋子的繪制放在瞭一起,這樣會有一個問題:由於棋盤始終是不變的,用戶每次落子時變的隻是棋子,但是如果按照上面的代碼來實現,每次繪制棋子時都要重新繪制一次棋盤,這是沒必要的。優化的方法就是將棋盤單獨抽為一個Widget,並設置其
shouldRepaint
回調值為false,然後將棋盤Widget作為背景。然後將棋子的繪制放到另一個Widget中,這樣落子時隻需要繪制棋子。
總結
CustomPaint class提供瞭讓用戶自定義widget的能力,它暴露瞭一個canvas,可以通過這個canvas來繪制widget,CustomPaint會先調用painter繪制背景,然後再繪制child,最後調用foregroundPainter來繪制前景。
canvas–畫佈,真正的繪制是由canvas跟paint來完成的,畫佈提供瞭各種繪制的接口來繪制圖形,除此以外畫佈還提供瞭平移、縮放、旋轉等矩陣變換接口,畫佈都有固定大小跟形狀,還可以使用畫佈提供的裁剪接口來裁剪畫佈的大小形狀等等
Paint—筆畫,是用來設置在畫佈上面繪制圖形時的一些筆畫屬性,如:顏色、線寬、繪制模式、抗鋸齒等等.
自繪控件非常強大,理論上可以實現任何2D圖像外觀,想更深入的瞭解,可以找到其對應的RenderObject對象,如Text Widget最終會通過RenderParagraph對象來通過Canvas實現文本繪制邏輯。瞭解瞭更底層的繪制邏輯,才能更好的在實際項目中靈活應用。
以上就是Flutter CustomPaint自定義繪畫示例詳解的詳細內容,更多關於Flutter CustomPaint 繪畫的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- Android Flutter繪制扇形圖詳解
- 如何使用Flutter實現手寫簽名效果
- Flutter構建自定義Widgets的全過程記錄
- Android Flutter利用CustomPaint繪制基本圖形詳解
- 基於Flutter實現多邊形和多角星組件