Flutter自定義控制元件分為三大類:
- 組合控制元件,通過組合其他widget成為一個新的widget。
- 自繪控制元件,通過使用canvas與paint來完全繪製。
- 繼承widget,使用RenderObject來繪製,但最終還是使用canvas來繪製。
本文重點
著重介紹自繪控制元件,因為所有的widget歸根結底都是使用canvas和paint來繪製的,理解了二者,對於其他的widget原理有溯源的功效。
- Canvas:畫布
- Paint:畫筆
怎麼做?
- 繼承CustomPainter
- 重寫paint方法與shouldRepaint方法
- paint提供來canvas和size,canvas用於繪製,size用於確定大小
- shouldRepaint用於確認是否每次都重新繪製
畫矩形canvas.drawRect();
void drawRect(Rect rect, Paint paint)
複製程式碼
rect 矩形的描述
- Rect.fromCenter({ Offset center, double width, double height }),根據中心點和寬高,定義一個矩形。
- Rect.fromCircle({ Offset center, double radius }),根據中心點和半徑定義一個矩形
- Rect.fromLTRB(this.left, this.top, this.right, this.bottom),left左邊框距離左邊的距離,top上邊框距離上邊的距離,right右邊框距離左邊的距離,bottom下邊框距離上邊的距離。根據這四個值定義一個矩形。
- Rect.fromLTWH(double left, double top, double width, double height),根據左上角頂點和寬高定義一個矩形。
- Rect.fromPoints(Offset a, Offset b),根據左上角頂點和右下角頂點定義一個矩形。
示例:
class _MyHomePageState extends State<MyHomePage> {
bool flag = true;
void change(bool value) {
setState(() {
flag = value;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: CustomPaint(
size: Size(380, 560),
painter: MyPainter(),
),
),
);
}
}
class MyPainter extends CustomPainter{
@override
void paint(Canvas canvas, Size size) {
test01(canvas, size);
}
void test00(Canvas canvas, Size size) {
var paint = new Paint()
..color = Colors.orange[200]
..style = PaintingStyle.fill
..isAntiAlias = true;
// 畫矩形
canvas.drawRect(Offset.zero & size, paint);
paint
..color = Colors.green;
canvas.drawRect(Rect.fromCenter(width: 200,height: 200,center: Offset(150, 150)), paint);
paint
..color = Colors.blue;
canvas.drawRect(Rect.fromCircle(radius: 50,center: Offset(150, 150)), paint);
paint
..color = Colors.redAccent;
canvas.drawRect(Rect.fromLTRB(10,10,200,100), paint);
paint
..color = Colors.pink;
canvas.drawRect(Rect.fromLTWH(10,250,100,100), paint);
paint
..color = Colors.grey;
canvas.drawRect(Rect.fromPoints(Offset(250, 250),Offset(350, 300)), paint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
return true;
}
}
複製程式碼
畫圓角矩形canvas.drawRRect
void test01(Canvas canvas, Size size) {
var paint = new Paint()
..color = Colors.orange[200]
..style = PaintingStyle.fill
..isAntiAlias = true;
// 畫矩形
Rect rect = Rect.fromCircle(
center: Offset(200, 200), radius: 150);
RRect rRect = RRect.fromRectAndRadius(rect, Radius.circular(30));
canvas.drawRRect(rRect, paint);
}
複製程式碼
畫環形圓角矩形canvas.drawDRRect
void test011(Canvas canvas, Size size) {
var paint = new Paint()
..color = Colors.orange[200]
..style = PaintingStyle.fill
..isAntiAlias = true;
// 畫矩形
Rect rect1 = Rect.fromCircle(
center: Offset(200, 200), radius: 140);
Rect rect2 = Rect.fromCircle(
center: Offset(200, 200), radius: 160);
RRect rRect1 = RRect.fromRectAndRadius(rect1, Radius.circular(20));
RRect rRect2 = RRect.fromRectAndRadius(rect2, Radius.circular(20));
canvas.drawDRRect(rRect2, rRect1, paint);
}
複製程式碼
畫圓canvas.drawCircle
drawCircle(Offset c, double radius, Paint paint)
複製程式碼
- c 中心點
- radius 圓半徑
- paint 畫筆
示例:
void test02(Canvas canvas, Size size) {
var paint = new Paint()
..color = Colors.orange[200]
..style = PaintingStyle.fill
..isAntiAlias = true;
// 畫矩形
canvas.drawRect(Offset.zero & size, paint);
paint ..color = Colors.blue[200];
canvas.drawCircle(Offset(200, 200), 100, paint);
}
複製程式碼
畫橢圓canvas.drawOval
void test022(Canvas canvas, Size size) {
var paint = new Paint()
..color = Colors.orange[200]
..style = PaintingStyle.fill
..isAntiAlias = true;
// 畫矩形
canvas.drawRect(Offset.zero & size, paint);
paint..color = Colors.blue[200];
canvas.drawOval(
Rect.fromCenter(width: 200, height: 300, center: Offset(150, 250)),
paint);
}
複製程式碼
畫線canvas.drawLine
void drawLine(Offset p1, Offset p2, Paint paint)
複製程式碼
- p1 線的起點
- p2 線的終點
注意:
- 畫筆的style 更改為PaintingStyle.stroke
- 可以調整線的粗細 strokeWidth = 3
示例:
void test03(Canvas canvas, Size size) {
var paint = new Paint()
..color = Colors.orange[200]
..style = PaintingStyle.fill
..isAntiAlias = true;
// 畫矩形
canvas.drawRect(Offset.zero & size, paint);
paint
..color = Colors.black
..strokeWidth = 3
..style = PaintingStyle.stroke;
canvas.drawLine(Offset(100, 150), Offset(250, 150), paint);
}
複製程式碼
畫顏色canvas.drawColor
void drawColor(Color color, BlendMode blendMode)
複製程式碼
- blendMode 是顏色的混合模式
void test04(Canvas canvas, Size size) {
var paint = new Paint()
..color = Colors.orange[200]
..style = PaintingStyle.fill
..isAntiAlias = true;
// 畫矩形
canvas.drawColor(Colors.blue[200], BlendMode.srcIn);
canvas.drawRect(Offset.zero & size/2, paint);
}
複製程式碼
畫點drawPoints
引數PointModel:
- ui.PointMode.points,// 單獨的點
- ui.PointMode.polygon,// 所有的點按給定順序連成線
- ui.PointMode.lines,//畫一條線,起始點為給定陣列的前兩個點
void test06(Canvas canvas, Size size) {
var paint = new Paint()
..color = Colors.orange[200]
..style = PaintingStyle.fill
..isAntiAlias = true;
// 畫矩形
canvas.drawRect(Offset.zero & size, paint);
paint
..style = PaintingStyle.stroke
..strokeWidth = 15
..strokeCap = StrokeCap.round
..color = Colors.black;
canvas.drawPoints(
// ui.PointMode.points,// 單獨的點
// ui.PointMode.polygon,// 所有的點按給定順序連成線
ui.PointMode.lines,//畫一條線,起始點為給定陣列的前兩個點
[Offset(100, 100), Offset(300, 100), Offset(200, 300)], paint);
}
複製程式碼
畫路徑canvas.drawPath
paint ..color = PaintingStyle.fill;
void test07(Canvas canvas, Size size) {
var paint = new Paint()
..color = Colors.orange[200]
..style = PaintingStyle.fill
..isAntiAlias = true;
// 畫矩形
Path path = Path();
path.moveTo(100, 100);
path.lineTo(100, 200);
path.lineTo(200, 100);
path.lineTo(200, 200);
canvas.drawPath(path, paint);
}
複製程式碼
paint ..color = PaintingStyle.stroke;
void test07(Canvas canvas, Size size) {
var paint = new Paint()
..color = Colors.orange[200]
// ..style = PaintingStyle.fill
..style = PaintingStyle.stroke
..strokeWidth = 5
..isAntiAlias = true;
// 畫矩形
Path path = Path();
path.moveTo(100, 100);
path.lineTo(100, 200);
path.lineTo(200, 100);
path.lineTo(200, 200);
canvas.drawPath(path, paint);
}
複製程式碼
path.close()
void test07(Canvas canvas, Size size) {
var paint = new Paint()
..color = Colors.orange[200]
// ..style = PaintingStyle.fill
..style = PaintingStyle.stroke
..strokeWidth = 5
..isAntiAlias = true;
// 畫矩形
Path path = Path();
path.moveTo(100, 100);
path.lineTo(100, 200);
path.lineTo(200, 100);
path.lineTo(200, 200);
path.close();
canvas.drawPath(path, paint);
}
複製程式碼
畫弧型drawArc
void drawArc(Rect rect, double startAngle, double sweepAngle, bool useCenter, Paint paint)
複製程式碼
- rect 弧所屬橢圓的外接矩形,用於定位該弧的位置
- startAngle,起始角度,按弧度制,順時針
- sweepAngle,畫多少弧度,一個圓的弧度是 2PI,也就是2*3.14
- useCenter,是否將弧與中心連線,形成一個扇形
void test08(Canvas canvas, Size size) {
var paint = new Paint()
..color = Colors.orange[200]
// ..style = PaintingStyle.fill
..style = PaintingStyle.stroke
..strokeWidth = 5
..isAntiAlias = true;
Rect rect = Rect.fromCircle(
center: Offset(200, 200), radius: 100);
canvas.drawArc(rect, 0, 3.14, false, paint);
}
複製程式碼
畫陰影drawShadow
void drawShadow(Path path, Color color, double elevation, bool transparentOccluder)
複製程式碼
- path 陰影區域
- color 陰影顏色
- elevation 陰影高度,一般是向右下
- transparentOccluder 如果阻塞物件不是不透明的,則阻塞“transparentoccluder”引數應為true.百度翻譯的,沒明白啥意思
void test09(Canvas canvas, Size size) {
var paint = new Paint()
..color = Colors.orange[200]
..style = PaintingStyle.fill
..strokeWidth = 5
..isAntiAlias = true;
Path path = Path();
path.moveTo(100, 100);
path.lineTo(100, 200);
path.lineTo(200, 100);
path.lineTo(200, 200);
canvas.drawPath(path, paint);
canvas.drawShadow(path, Colors.orange, 10, true);
}
複製程式碼
save(),saveLayer(),restore()
- canvas.save(),儲存之前畫的內容與canvas的狀態
- canvas.saveLayer(rect,paint),儲存之前畫的內容,並新建一個圖層,以後在此新圖層上繪製。引數rect確定圖層的範圍,只能在此範圍內繪製,超出範圍的繪製不顯示。引數paint表示其blendMode與之前的圖層的混合模式。
- canvas.restore(),將該方法與前面最近的一個save() 或saveLayer()之間的操作合併到一起,save與restore,saveLayer與restore是成對兒出現的,只有save沒有restore,是會報錯的。
void test10(Canvas canvas, Size size) {
var paint = new Paint()
..color = Colors.orange[200]
..style = PaintingStyle.fill
..isAntiAlias = true;
canvas.drawPaint(paint);// 整個區域
paint ..color = Colors.blue[200];
canvas.drawRect(Offset.zero&size, paint);// 繪製區域
paint ..color = Colors.green[200];
canvas.drawRect(Rect.fromCircle(center:Offset(200,200),radius:100), paint);
// 儲存之前的繪製內容
canvas.save();
// 接著繪製一個矩形,是在前面的圖層上繼續繪製的
paint.color = Colors.grey;
canvas.drawRect(Rect.fromCircle(center:Offset(250,300),radius:100), paint);
// 儲存
canvas.restore();
// 設定混合模式
paint.blendMode = BlendMode.src;
//儲存之前並新建一個圖層,並指定新圖層的區域與混合模式
canvas.saveLayer(Rect.fromCircle(center:Offset(270,350),radius:100), paint);
paint ..color = Colors.orange;
//在新的圖層上畫一個矩形,設定矩形範圍超過圖層範圍
canvas.drawRect(Rect.fromCircle(center:Offset(270,350),radius:150), paint);
// 儲存
canvas.restore();
}
複製程式碼
可見,在新的圖層上畫一個超出圖層範圍的矩形,超出範圍的內容沒有顯示,只有圖層區域內的顯示了出來
canvas.translate平移
canvas.translate(dx,dy) 指的是整個canvas向xy分別平移dx dy的距離
// 平移
void test11(Canvas canvas, Size size) {
var paint = new Paint()
..color = Colors.orange[200]
..style = PaintingStyle.fill
..isAntiAlias = true;
// 初始位置
canvas.drawRect(Offset.zero&size/2, paint);// 繪製區域
paint ..color = Colors.green[200];
canvas.drawRect(Rect.fromCircle(center:Offset(100,100),radius:50), paint);// 畫一個矩形
// 儲存之前的繪製內容
canvas.save();
canvas.translate(200, 200);//向xy200平移
paint.color = Colors.red[200];
canvas.drawRect(Offset.zero&size/2, paint);// 繪製canvas區域
paint.color = Colors.green[200];
canvas.drawRect(Rect.fromCircle(center:Offset(100,100),radius:50), paint);// 畫一個矩形
// 儲存
canvas.restore();
}
複製程式碼
平移後,canvas繪製的內容顯示在平移後的位置。
縮放canvas.scale(sx,sy)
canvas.scale(0.5,2):x軸縮小為一半,y軸放大為2倍 看效果:
// 縮放
void test12(Canvas canvas, Size size) {
var paint = new Paint()
..color = Colors.orange[200]
..style = PaintingStyle.fill
..isAntiAlias = true;
// 初始位置
canvas.drawRect(Offset.zero&size/2, paint);// 繪製區域
paint ..color = Colors.green[200];
canvas.drawRect(Rect.fromCircle(center:Offset(100,100),radius:50), paint);// 畫一個矩形
// 儲存之前的繪製內容
canvas.save();
canvas.scale(0.5,0.5);// 縮小為原來的一半
paint.color = Colors.red[200];
canvas.drawRect(Offset.zero&size/2, paint);// 繪製canvas區域
paint.color = Colors.green[200];
canvas.drawRect(Rect.fromCircle(center:Offset(100,100),radius:50), paint);// 畫一個矩形
// 儲存
canvas.restore();
}
複製程式碼
旋轉canvas.rotate(value)
- value: 取值pi=3.14,順時針旋轉180讀,取值pi *2 順時針旋轉360度。
- 旋轉的圓心是canvas的左頂點,即(0,0)
看效果: 先平移,再旋轉:
// 旋轉
void test13(Canvas canvas, Size size) {
var paint = new Paint()
..color = Colors.orange[200]
..style = PaintingStyle.fill
..isAntiAlias = true;
// 初始位置
canvas.drawRect(Offset.zero&size/2, paint);// 繪製區域
paint ..color = Colors.green[200];
canvas.drawRect(Rect.fromCircle(center:Offset(100,100),radius:50), paint);// 畫一個矩形
// 儲存之前的繪製內容
canvas.save();
canvas.translate(200, 200);//向xy200平移
canvas.rotate(1);// 旋轉1弧度
paint.color = Colors.red[200];
canvas.drawRect(Offset.zero&size/2, paint);// 繪製canvas區域
paint.color = Colors.green[200];
canvas.drawRect(Rect.fromCircle(center:Offset(100,100),radius:50), paint);// 畫一個矩形
// 儲存
canvas.restore();
}
複製程式碼
斜切canvas.skew(sx,sy)
取值為tan(sx) tan(sy)
// 斜切
void test14(Canvas canvas, Size size) {
var paint = new Paint()
..color = Colors.orange[200]
..style = PaintingStyle.fill
..isAntiAlias = true;
// 初始位置
canvas.drawRect(Offset.zero&size/2, paint);// 繪製區域
paint ..color = Colors.green[200];
canvas.drawRect(Rect.fromCircle(center:Offset(100,100),radius:50), paint);// 畫一個矩形
// 儲存之前的繪製內容
canvas.save();
canvas.skew(pi/4, 0);//
// canvas.skew(tan(45), 0);//
paint.color = Colors.red[200];
canvas.drawRect(Offset.zero&size/2, paint);// 繪製canvas區域
paint.color = Colors.green[200];
canvas.drawRect(Rect.fromCircle(center:Offset(100,100),radius:50), paint);// 畫一個矩形
// 儲存
canvas.restore();
}
複製程式碼