Flutter 34: 圖解自定義 View 之 Canvas (一)
小菜最近在學習自定義 View ,剛瞭解了一下 Paint 畫筆的神奇之處,現在學習一下 Canvas 畫布的神祕之處。 Flutter 提供了眾多的繪製方法,小菜接觸不深,儘量都嘗試一下。
Canvas 畫布

drawColor 繪製背景色
drawColor需要傳入兩個引數,第一個為色值,第二個為混合模式,有眾多混合模式供選擇,但注意使用混合模式後會與繪製其上的其他 View 顏色混合畫素。
canvas.drawColor(Colors.pinkAccent, BlendMode.srcIn);
drawPoints 繪製點/線
drawPoints不僅可以繪製點,還可以繪製點與點的連線; PointMode 包括 points 點 / lines 線 / polygon 多邊形;注意 lines 為每兩點之間的連線,若為奇數個點,最後一個沒有與之相連的點。
// 繪製點 canvas.drawPoints( PointMode.points, [ Offset(30.0, 30.0), Offset(60.0, 30.0), Offset(90.0, 30.0), Offset(90.0, 60.0), Offset(60.0, 60.0), Offset(30.0, 60.0) ], Paint()..strokeWidth = 4.0); canvas.drawPoints( PointMode.points, [ Offset(160.0, 30.0), Offset(190.0, 30.0), Offset(220.0, 30.0), Offset(220.0, 60.0), Offset(190.0, 60.0), Offset(160.0, 60.0) ], Paint()..strokeWidth = 4.0..strokeCap = StrokeCap.round); // 繪製線 canvas.drawPoints( PointMode.lines, [ Offset(30.0, 100.0), Offset(60.0, 100.0), Offset(90.0, 100.0), Offset(90.0, 130.0), Offset(60.0, 130.0), Offset(30.0, 130.0) ], Paint()..strokeWidth = 4.0..strokeCap = StrokeCap.round); // 繪製多邊形 canvas.drawPoints( PointMode.polygon, [ Offset(160.0, 100.0), Offset(190.0, 100.0), Offset(220.0, 100.0), Offset(220.0, 130.0), Offset(190.0, 130.0), Offset(160.0, 130.0) ], Paint()..strokeWidth = 4.0..strokeCap = StrokeCap.round);

drawLine 繪製線
canvas.drawLine(Offset(30.0, 90.0), Offset(Screen.width - 30.0, 90.0), Paint()..strokeWidth = 4.0); canvas.drawLine(Offset(30.0, 120.0), Offset(Screen.width - 30.0, 120.0), Paint()..strokeWidth = 4.0..strokeCap = StrokeCap.round); canvas.drawLine(Offset(30.0, 150.0), Offset(Screen.width - 30.0, 150.0), Paint()..strokeWidth = 4.0..strokeCap = StrokeCap.square);
drawArc 繪製弧/餅
drawArc可以用來繪製圓弧甚至配合 Paint 繪製餅狀圖; drawArc 的第一個引數為矩形範圍,即圓弧所在的圓的範圍,若非正方形則圓弧所在的圓會拉伸;第二個引數為起始角度, 0.0 為座標系 x 軸正向方形;第三個引數為終止角度,若超過 2 PI ,則為一個圓;第四個引數為是否由中心出發, false * 時只繪製圓弧, true 時繪製圓餅;第五個引數即 Paint 畫筆,可通過 PaintingStyle 屬性繪製是否填充等;
const PI = 3.1415926; canvas.drawArc(Rect.fromCircle(center: Offset(60.0, 60.0), radius: 80.0), 0.0, PI / 2, false, Paint()..color = Colors.white..strokeCap = StrokeCap.round..strokeWidth = 4.0..style = PaintingStyle.stroke); canvas.drawArc(Rect.fromCircle(center: Offset(200.0, 60.0), radius: 80.0), 0.0, PI / 2, false, Paint()..color = Colors.white..strokeWidth = 4.0..style = PaintingStyle.fill); canvas.drawArc(Rect.fromCircle(center: Offset(90.0, 160.0), radius: 80.0), 0.0, PI * 2 / 3, true, Paint()..color = Colors.white..strokeWidth = 4.0..style = PaintingStyle.stroke); canvas.drawArc(Rect.fromCircle(center: Offset(250.0, 160.0), radius: 80.0), 0.0, PI * 2 / 3, true, Paint()..color = Colors.white..strokeWidth = 4.0..style = PaintingStyle.fill); canvas.drawArc(Rect.fromLTWH(30.0, 300.0, 200.0, 100.0), 0.0, 5.0, true, Paint()..color = Colors.white..style = PaintingStyle.fill); canvas.drawArc(Rect.fromPoints(Offset(260.0, 260.0), Offset(320.0, 420.0)), 0.0, 5.0, true, Paint()..color = Colors.white..style = PaintingStyle.fill);

drawRect 繪製矩形
drawRect用來繪製矩形, Flutter 提供了多種繪製矩形方法:
- Rect.fromPoints 根據兩個點(左上角點/右下角點)來繪製;
- Rect.fromLTRB 根據以螢幕左上角為座標系圓點,分別設定上下左右四個方向距離;
- Rect.fromLTWH 根據設定左上角的點與矩形寬高來繪製;
- Rect.fromCircle 最特殊,根據圓形繪製正方形;
canvas.drawRect(Rect.fromPoints(Offset(30.0, 30.0), Offset(150.0, 100.0)), Paint()..color = Colors.white..strokeWidth = 4.0..style = PaintingStyle.stroke); canvas.drawRect(Rect.fromPoints(Offset(210.0, 30.0), Offset(330.0, 100.0)), Paint()..color = Colors.white..style = PaintingStyle.fill); canvas.drawRect(Rect.fromLTRB(30.0, 140.0, 150.0, 210.0), Paint()..color = Colors.white); canvas.drawRect(Rect.fromLTWH(210.0, 140.0, 120.0, 70.0), Paint()..color = Colors.white); canvas.drawRect(Rect.fromCircle(center: Offset(90.0, 300.0), radius: 60.0), Paint()..color = Colors.white..strokeWidth = 4.0..style = PaintingStyle.stroke);

drawRRect 繪製圓角矩形
drawRRect繪製圓角矩形, Flutter 提供了多種繪製方法:
- RRect.fromLTRBXY 前四個引數用來繪製矩形位置,剩餘兩個引數繪製固定 x/y 弧度;
- RRect.fromLTRBR 前四個引數用來繪製矩形位置,最後一個引數繪製 Radius 弧度;
- RRect.fromLTRBAndCorners 前四個引數用來繪製矩形位置,剩餘四個可選擇引數,根據需求設定四個角 Radius 弧度,可不同;
- RRect.fromRectXY 第一個引數繪製矩形,可以用上面介紹的多種矩形繪製方式,剩餘兩個引數繪製固定 x/y 弧度;
- RRect.fromRectAndRadius 第一個引數繪製矩形,可以用上面介紹的多種矩形繪製方式,最後一個引數繪製 Radius 弧度;
- RRect.fromRectAndCorners 第一個引數繪製矩形,可以用上面介紹的多種矩形繪製方式,剩餘四個可選擇引數,根據需求設定四個角 Radius 弧度,最為靈活。
// RRect.fromLTRBXY 方式 canvas.drawRRect( RRect.fromLTRBXY(30.0, 30.0, 150.0, 100.0, 8.0, 8.0), Paint()..color = Colors.white..strokeWidth = 4.0..style = PaintingStyle.stroke); canvas.drawRRect( RRect.fromLTRBXY(210.0, 30.0, 330.0, 100.0, 8.0, 18.0), Paint()..color = Colors.white..style = PaintingStyle.fill); // RRect.fromLTRBR 方式 canvas.drawRRect( RRect.fromLTRBR(30.0, 140.0, 150.0, 210.0, Radius.circular(8.0)), Paint()..color = Colors.white..strokeWidth = 4.0..style = PaintingStyle.stroke); // RRect.fromLTRBAndCorners 方式 canvas.drawRRect( RRect.fromLTRBAndCorners(210.0, 140.0, 330.0, 210.0, topLeft: Radius.circular(5.0), topRight: Radius.circular(20.0), bottomRight: Radius.circular(5.0), bottomLeft: Radius.circular(20.0)), Paint()..color = Colors.white..strokeWidth = 4.0..style = PaintingStyle.stroke); // RRect.fromRectAndCorners 方式 canvas.drawRRect( RRect.fromRectAndCorners(Rect.fromLTWH(30.0, 260.0, 120.0, 70.0), topLeft: Radius.circular(5.0), topRight: Radius.circular(20.0), bottomRight: Radius.circular(5.0), bottomLeft: Radius.circular(20.0)), Paint()..color = Colors.white..strokeWidth = 4.0..style = PaintingStyle.stroke); // RRect.fromRectAndRadius 方式 canvas.drawRRect( RRect.fromRectAndRadius(Rect.fromLTWH(210.0, 260.0, 120.0, 70.0), Radius.elliptical(8.0, 18.0)), Paint()..color = Colors.white..strokeWidth = 4.0..style = PaintingStyle.stroke); // RRect.fromRectXY 方式 canvas.drawRRect( RRect.fromRectXY( Rect.fromCircle(center: Offset(90.0, 420.0), radius: 60.0), 8.0, 8.0), Paint()..color = Colors.white..strokeWidth = 4.0..style = PaintingStyle.stroke);

drawDRRect 繪製巢狀矩形
drawDRRect繪製巢狀矩形,第一個引數為外部矩形,第二個引數為內部矩形,可用上述多種設定圓角矩形方式;最後一個引數為 Paint 畫筆,且 PaintingStyle 為 fill 時填充的是兩個矩形之間的範圍。
canvas.drawDRRect( RRect.fromRectXY( Rect.fromCircle(center: Offset(90.0, 420.0), radius: 60.0), 8.0, 8.0), RRect.fromRectXY( Rect.fromCircle(center: Offset(90.0, 420.0), radius: 54.0), 8.0, 8.0), Paint()..color = Colors.whit..strokeWidth = 3.0 ..style = PaintingStyle.stroke); canvas.drawDRRect( RRect.fromRectXY( Rect.fromCircle(center: Offset(270.0, 420.0), radius: 60.0), 8.0, 8.0), RRect.fromRectXY( Rect.fromCircle(center: Offset(270.0, 420.0), radius: 54.0), 8.0, 8.0), Paint()..color = Colors.white..strokeWidth = 3.0 ..style = PaintingStyle.fill);

drawCircle 繪製圓形
drawCircle繪製圓形,僅需設定原點及半徑即可;
canvas.drawCircle(Offset(90.0, 420.0), 60.0, Paint()..color = Colors.white..strokeWidth = 3.0 ..style = PaintingStyle.stroke); canvas.drawCircle(Offset(270.0, 420.0), 60.0, Paint()..color = Colors.white..strokeWidth = 3.0 ..style = PaintingStyle.fill);

drawOval 繪製橢圓
drawOval繪製橢圓方式很簡單,主要繪製一個矩形即可;
canvas.drawOval(Rect.fromLTRB(30.0, 30.0, 150.0, 100.0), Paint()..color = Colors.white..strokeWidth = 3.0 ..style = PaintingStyle.stroke); canvas.drawOval(Rect.fromLTRB(210.0, 30.0, 330.0, 100.0), Paint()..color = Colors.white..strokeWidth = 3.0 ..style = PaintingStyle.fill);

drawPath 繪製路徑
drawPath用來繪製路徑, Flutter 提供了眾多路徑方法,小菜嘗試幾種常用的方法:
- moveTo() 即從當前座標點開始,不設定時預設為螢幕左上角位置;
- lineTo() 即從起點繪製到設定的新的點位;
- close() 即最後的點到起始點連線,但對於中間繪製矩形/弧等時最後不會相連;
- reset() 即清空連線;
- addRect() 新增矩形連線;
- addOval() 新增弧線,即貝塞爾(二階)曲線;
- cubicTo() 新增弧線,即貝塞爾(三階)曲線;
- relativeMoveTo() 相對於移動到當前點位,小菜認為與 moveTo 相比整個座標系移動;
- relativeLineTo() 相對連線到當前點位,並將座標系移動到當前點位;
canvas.drawPath( Path() ..moveTo(30.0, 100.0)..lineTo(120.0, 100.0) ..lineTo(90.0, 130.0)..lineTo(180.0, 130.0) ..close(), Paint() ..color = Colors.white..strokeWidth = 3.0 ..style = PaintingStyle.stroke); canvas.drawPath( Path() ..moveTo(200.0, 100.0)..lineTo(290.0, 100.0) ..lineTo(260.0, 130.0)..lineTo(350.0, 130.0) ..close(), Paint() ..color = Colors.white..strokeWidth = 3.0 ..style = PaintingStyle.fill); canvas.drawPath( Path() ..moveTo(30.0, 170.0)..lineTo(120.0, 170.0) ..lineTo(90.0, 210.0)..lineTo(180.0, 210.0) ..addRect(Rect.fromLTWH(180.0, 210.0, 120.0, 70.0)) ..addOval(Rect.fromLTWH(180.0, 210.0, 120.0, 70.0)) ..moveTo(230.0, 170.0)..lineTo(320.0, 170.0) ..close(), Paint() ..color = Colors.white..strokeWidth = 3.0..style = PaintingStyle.stroke); canvas.drawPath( Path() ..arcTo(Rect.fromCircle(center: Offset(60, 300), radius: 80), -PI / 6, PI * 2 / 3, false), Paint() ..color = Colors.white..strokeWidth = 3.0..style = PaintingStyle.stroke); canvas.drawPath( Path() ..moveTo(210.0, 300.0) ..cubicTo(210.0, 390.0, 270.0, 330.0, 330.0, 300.0), Paint() ..color = Colors.black..strokeWidth = 3.0..style = PaintingStyle.stroke);

小菜繪製了一個基本的座標系來比較一下 moveTo()/lineTo() 與 relativeMoveTo()/relativeLineTo() 的區別:
canvas.drawPath( Path() ..relativeMoveTo(30.0, 30.0)..relativeLineTo(120.0, 30.0) ..relativeLineTo(90.0, 60.0)..relativeLineTo(180.0, 60.0), Paint() ..color = Colors.blue..strokeWidth = 6.0 ..style = PaintingStyle.stroke); canvas.drawPath( Path() ..moveTo(30.0, 30.0)..lineTo(120.0, 30.0) ..lineTo(90.0, 60.0)..lineTo(180.0, 60.0), Paint() ..color = Colors.orange..strokeWidth = 6.0 ..style = PaintingStyle.stroke);

小菜對自定義 View 研究還不深入,有很多方法還沒有嘗試,有錯誤的地方希望多多指導!如下是小菜公眾號,歡迎閒來吐槽~

公眾號.jpg