Flutter CustomPaint自定義繪畫示例詳解
正文
CustomPaint是Flutter中用于自由繪制的一個widget,它與android原生的繪制規(guī)則基本一致,以當(dāng)前Canves(畫布)的左上角為原點進(jìn)行繪制。在有些場景中,我們會需要繪制一些高度定制化的組件,比如 UI 設(shè)計師給我們出了個難題 —— 弄一個奇形怪狀的邊框。這個時候我們就不能直接使用 Flutter 自帶的那些組件了,而是需要手動繪制組件,那就會需要用到 CuntomPaint 組件。CustomPaint 組件和前端的 Canvas差不多,允許我們在一個畫布上繪制各種元素,包括點、線、矩形、圓弧、文字、圖片等等。
CustomPaint 介紹
CustomPaint是一個 Widget,其中有三個重要的參數(shù):
CustomPaint( child: childWidget(), foregroundPainter: foregroundPainter(), painter: backgroundPainter(), )
child:CustomPaint的子組件;
painter和 foregroundPainter:都是 CustomPainter 類,用于定義 canvas 繪制的內(nèi)容。區(qū)別在于,首先是執(zhí)行 painter 的繪制指令。然后是在背景上渲染 child 子組件。最后,foregroundPainter 的內(nèi)容會繪制在 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: 紅色區(qū)域,傳入一個子widget,這個widget圖層會在painter在上,在foregroundPainter之下。
painter:藍(lán)色區(qū)域。
foregroundPainter:綠色區(qū)域,它與painter都是CustomPainter類型的。通過名字大概也就知道了,它會在painter的上層,也就是說在同樣的位置去繪制,foregroundPainter 會覆蓋painter。
CustomPainter提供了一個paint繪圖方法供我們繪制圖形,該方法攜帶canvas 和size兩個參數(shù),其中 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區(qū)域 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個點繪制為線段,如果點的個數(shù)為奇數(shù),最后一個點將會被忽略
- 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,還可以設(shè)置為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();
}
}
運行效果:

繪制五子棋
首先繪制背景,淡黃色,再繪制棋盤網(wǎng)格線,隨后繪制黑白子,具體代碼:
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);
//畫棋盤網(wǎng)格
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,
);
}
//在實際場景中正確利用此回調(diào)可以避免重繪開銷,本示例我們簡單的返回true
@override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}
運行效果:

繪制是比較昂貴的操作,所以我們在實現(xiàn)自繪控件時應(yīng)該考慮到性能開銷,下面是兩條關(guān)于性能優(yōu)化的建議:
- 盡可能的利用好
shouldRepaint返回值;在UI樹重新build時,控件在繪制前都會先調(diào)用該方法以確定是否有必要重繪;假如我們繪制的UI不依賴外部狀態(tài),那么就應(yīng)該始終返回false,因為外部狀態(tài)改變導(dǎo)致重新build時不會影響我們的UI外觀;如果繪制依賴外部狀態(tài),那么我們就應(yīng)該在shouldRepaint中判斷依賴的狀態(tài)是否改變,如果已改變則應(yīng)返回true來重繪,反之則應(yīng)返回false不需要重繪。 - 繪制盡可能多的分層;在上面五子棋的示例中,我們將棋盤和棋子的繪制放在了一起,這樣會有一個問題:由于棋盤始終是不變的,用戶每次落子時變的只是棋子,但是如果按照上面的代碼來實現(xiàn),每次繪制棋子時都要重新繪制一次棋盤,這是沒必要的。優(yōu)化的方法就是將棋盤單獨抽為一個Widget,并設(shè)置其
shouldRepaint回調(diào)值為false,然后將棋盤Widget作為背景。然后將棋子的繪制放到另一個Widget中,這樣落子時只需要繪制棋子。
總結(jié)
CustomPaint class提供了讓用戶自定義widget的能力,它暴露了一個canvas,可以通過這個canvas來繪制widget,CustomPaint會先調(diào)用painter繪制背景,然后再繪制child,最后調(diào)用foregroundPainter來繪制前景。
canvas--畫布,真正的繪制是由canvas跟paint來完成的,畫布提供了各種繪制的接口來繪制圖形,除此以外畫布還提供了平移、縮放、旋轉(zhuǎn)等矩陣變換接口,畫布都有固定大小跟形狀,還可以使用畫布提供的裁剪接口來裁剪畫布的大小形狀等等
Paint---筆畫,是用來設(shè)置在畫布上面繪制圖形時的一些筆畫屬性,如:顏色、線寬、繪制模式、抗鋸齒等等.
自繪控件非常強大,理論上可以實現(xiàn)任何2D圖像外觀,想更深入的了解,可以找到其對應(yīng)的RenderObject對象,如Text Widget最終會通過RenderParagraph對象來通過Canvas實現(xiàn)文本繪制邏輯。了解了更底層的繪制邏輯,才能更好的在實際項目中靈活應(yīng)用。
以上就是Flutter CustomPaint自定義繪畫示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Flutter CustomPaint 繪畫的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
iOS開發(fā)狀態(tài)欄及設(shè)置功能全面詳解
這篇文章主要為大家介紹了iOS開發(fā)狀態(tài)欄及設(shè)置功能全面詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06
iOS推送增加右側(cè)顯示圖Service Extension
這篇文章主要為大家介紹了iOS推送增加右側(cè)顯示圖Service Extension,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10

