Flutter Canvas學習之繪圖篇

JSShou發表於2021-05-30

本篇會作為一個系列,原文傳送門:

基於Flutter版本: 2.0.3

前言

上一篇講了Flutter CustomPaint 元件相關的基本概念,這篇圍繞canvas基礎繪製能力展開。

繪製點-drawPoints

void drawPoints(PointMode pointMode, List points, Paint paint)

  • pointMode: 設定點、線
    • PointMode.points 設定點
    • PointMode.lines 兩個兩個點之間連線,如果傳入的points是奇數,最後一個點將會被忽略
    • PointMode.polygon 將所有點連線起來
  • points: 一個Offset陣列,可以畫多個點

如果paint設定了strokeCap = StrokeCap.round,畫的點將是圓形(其它任何API畫的點、線都同理,都跟隨畫筆屬性)

import 'dart:ui' as ui;
//...
// 畫點
Paint paint = Paint()
  ..color = Colors.red
  ..strokeWidth = 20;
canvas.drawPoints(
    ui.PointMode.points,
    [
      Offset(100, 100),
      Offset(250, 180),
      Offset(200, 300),
    ],
    paint);
// 將端點設定為圓形
paint.strokeCap = StrokeCap.round;
canvas.drawPoints(ui.PointMode.points, [Offset(100, 200)], paint);
複製程式碼

points 如果將入參都設定為PointMode.polygon polygon

繪製線段-drawLine

void drawLine(Offset p1, Offset p2, Paint paint)

  • p1,p2表示兩個端點的位置
canvas.drawLine(Offset(100, 100), Offset(250,180), paint);
複製程式碼

drawLine

drawLine只能用於繪製一條線段,drawPoints可以繪製同樣效果

canvas.drawPoints(
  ui.PointMode.lines,
  [
    Offset(100, 100),
    Offset(250, 180),
  ],
  paint);
複製程式碼

繪製區域-drawRect

因為很多方法都會用到Rect,所以這個我會把drawRect方法放到最開頭講。

Rect表示繪製一個矩形區域,它沒有建構函式,它只有一些靜態方法

fromLTRB

Rect Rect.fromLTRB(double left, double top, double right, double bottom)

此方法是所有該類方法的母本,其它方法都是使用此方法實現的

  • left: 矩形左邊距離畫布左邊距離
  • top: 矩形頂部距離畫布頂部距離
  • right: 矩形右邊距離畫布左邊邊距離
  • bottom: 矩形底部距離畫布頂部距離

下面是一個例子

canvas.drawRect(Rect.fromLTRB(50, 50, 350, 350), paint);
複製程式碼

fromCenter

Rect.fromCenter({Offset center, double width, double height})

畫一個長方形

  • center: 長方形方形中心點位置
  • width: 長方形的寬
  • height: 長方形的高
canvas.drawRect(
      Rect.fromCenter(center: Offset(200,300), width: 250, height: 350), paint);
複製程式碼

fromCircle

Rect.fromCircle({Offset center, double radius})

畫一個正方形

  • center: 正方形中心點位置
  • radius: 正方形四條邊距離中心點距離
canvas.drawRect(
        Rect.fromCircle(center: Offset(200, 300), radius: 150), paint);
複製程式碼

rect_from_circle

fromPoints

Rect.fromPoints(Offset a, Offset b)

使用兩個點確定一個矩形

  • a: 矩形左上角的位置
  • b: 矩形右下角的位置
canvas.drawRect(Rect.fromPoints(Offset(100, 200), Offset(300, 400)), paint);
複製程式碼

rect_from_point

繪製圓角矩形-RRect

void drawRRect(RRect rrect, Paint paint)

RRect用來繪製帶圓角的矩形,其繪製的位置原理同Rect.fromLTRB一樣,只是多了一個設定圓角的引數。

RRect rRect = RRect.fromLTRBR(100, 100, 350, 350, Radius.circular(30));
canvas.drawRRect(rRect, paint);
複製程式碼

draw_rrect

繪製圓-drawOval

void drawOval(Rect rect, Paint paint)

drawOval用於繪製圓形

Rect pRect = Rect.fromLTRB(50, 150, 400, 350);
// 為了區別,先繪製一個矩形區域
canvas.drawRect(pRect, paint);
paint.color = Colors.yellow;
// 繪製橢圓
canvas.drawOval(pRect, paint);
複製程式碼

drawOval

黃色區域就是繪製的圓,它就是在Rect中進行繪製的

繪製圓弧-drawArc

drawArc(Rect rect, double startAngle, double sweepAngle, bool useCenter, Paint paint)

繪製圓弧,useCenter表示是否繪製中心點到圓弧兩邊

Rect rect = Rect.fromCircle(
    center: Offset(size.width / 2, size.height / 2), radius: 100);
// 為了方便檢視,把區域也繪製出來
canvas.drawRect(rect);
//繪製圓弧
canvas.drawArc(rect, 90 * (pi / 180), 90 * (pi / 180), false, paint);
複製程式碼

path_addrect

有關角度的介紹,請看我上篇文章

繪製路徑-drawPath

void drawPath(Path path, Paint paint)

這個厲害了,drawPath是一個很強大的方法,它可以說是canvas的精髓,幾乎所以的其它繪製都可以用它來繪製。其它方法可以簡略看看,這個一定要重點學習理解。下面來看看它的能力

繪製簡單的形狀

Path path = new Path();
path.moveTo(100, 100);
path.lineTo(200, 200);
path.lineTo(250, 180);
path.lineTo(200, 300);
path.lineTo(100, 200);
canvas.drawPath(path, paint);
複製程式碼

drawPath

Path有很多方法,下面介紹常用方法

moveTo

設定畫筆開始的位置

lineTo

繪製的下一個位置,傳入的是相對於座標系的具體位置,會按照程式碼順序進行移動繪製

relativeLineTo

與lineTo類似,不過傳入的是相對於上一個點為原點的位置,比如上一個點是在(100,100),傳入的是(150,150),如果用lineTo要達到同樣效果應該傳入(250,250)。它有很多方法,帶了relative的都是同樣的原理,所以後續relative-方法就不講解了。

arcTo

繪製一個弧線

void arcTo(Rect rect, double startAngle, double sweepAngle, bool forceMoveTo)

此方法同drawArc類似,前三個引數都一樣,最後一個參數列示是否跟path之前的繪製(如通過lineTo繪製的線段)相連,false表示連線,true表示不相連。

quadraticBezierTo

繪製二階貝塞爾曲線

void quadraticBezierTo(double x1, double y1, double x2, double y2)

什麼是塞爾曲線,維基百科這樣介紹

在數學的數值分析領域中,貝塞爾曲線(英語:Bézier curve)是計算機圖形學中相當重要的引數曲線。更高維度的廣泛化貝塞爾曲線就稱作貝茲曲面,其中貝茲三角是一種特殊的例項。

它的繪製過程如下(來自維基百科):

bezier

二階貝塞爾曲線的確立,需要三個點,P0開始點、P1過程點、P2結束點

使用quadraticBezierTo來繪製時,我們可以通過moveTo來確定開始點P0,x1、y1確定P1,x2、y2確定P2

var path = Path();
path.moveTo(50, 500);
path.quadraticBezierTo(100, 300, 350, 300);
canvas.drawPath(path1, paint);
複製程式碼

quadraticBezierTo

其中藍色的座標系、黃色的點是我畫的輔助線,紅色即是上面程式碼執行結果

conicTo

void conicTo(double x1, double y1, double x2, double y2, double w

同樣也是繪製二階貝塞爾曲線,但是同quadraticBezierTo相比,它多了一個引數w,用於控制曲線的弧度。當 w < 1 時,曲線弧度更小;w = 1 時同quadraticBezierTo效果一樣;w > 1 時,弧度更大

path.conicTo(100, 300, 350, 300, 3);
複製程式碼

conicTo

cubicTo

繪製三階貝塞爾曲線

void cubicTo(double x1, double y1, double x2, double y2, double x3, double y3)

三階貝塞爾曲線相比二階貝塞爾曲線只是多了一個點,原理類似

path.moveTo(50, 500);
path.cubicTo(50, 200, 300, 400, 350, 150);
複製程式碼

cubicTo

addRect

繪製一個矩形區域

path.addRect(Rect.fromLTRB(50, 50, 350, 350));
複製程式碼

此效果跟上面drawRect中第一個例子一樣

addRRect

繪製一個帶圓角的矩形

RRect rRect = RRect.fromLTRBR(100, 100, 350, 350, Radius.circular(30));
path.addRRect(rRect);
複製程式碼

上面程式碼效果跟drawRRect一樣

addArc

繪製一個圓弧

Path path = new Path();
// 畫一個矩形區域
Rect rect = Rect.fromCircle(
    center: Offset(size.width / 2, size.height / 2), radius: 100);
canvas.drawRect(rect, paint);
// 在矩形區域畫圓弧
path.addArc(rect, 90 * (pi / 180), 90 * (pi / 180));
paint.color = Colors.red;
canvas.drawPath(path, paint);
複製程式碼

上面程式碼同drawArc效果一樣

canvas.drawArcpath.addArcpath.arcTo(當forceMoveTo為true時),三種方式都能繪製同樣的圓弧效果

addOval

繪製一個橢圓

Rect pRect = Rect.fromLTRB(50, 150, 400, 350);
path.addOval(pRect);
canvas.drawPath(path, paint);
複製程式碼

程式碼效果同drawOval

addPolygon

通過點繪製線段

void addPolygon(List points, bool close)

  • point: 傳入多個點的位置
  • close: 為true時最後一個點會和第一個點相連
Path path = new Path();
path.addPolygon([
      Offset(100, 100),
      Offset(250, 180),
      Offset(200, 300),
    ], false);
canvas.drawPath(path, paint);
複製程式碼

addPolygon

computeMetrics

PathMetrics computeMetrics({bool forceClosed = false})

computeMetrics方法用於返回一個之前繪製的路徑的一份快照。當我們使用moveTolineToarcToconicTo等繪製路徑時,可以使用此來實現只繪製其中一部分。

比如:

var path = Path();
path.moveTo(50, 500);
path.cubicTo(50, 200, 300, 400, 350, 150);
// 將完整繪製圖形置為紅色
paint.color = Colors.red;
canvas.drawPath(path, paint);
ui.PathMetrics pathMetrics = path.computeMetrics();
// 繪製一半
var progress = 0.5;
// 將顏色更改為紫色用於區分
paint.color = Colors.deepPurple;
for (ui.PathMetric pathMetric in pathMetrics) {
  Path extractPath = pathMetric.extractPath(
    0.0,
    pathMetric.length * progress,
  );
  canvas.drawPath(extractPath, paint);
}
複製程式碼

computeMetrics

extendWithPath

void extendWithPath(Path path, Offset offset, {Float64List matrix4})

用於複製一份之前繪製的路徑並平移offset的位置,原路徑會和新路徑連線。matrix4是對新路徑進行一個4D矩陣處理。

var path = Path();
path.moveTo(50, 500);
// 繪製一個三階貝塞爾曲線
path.cubicTo(50, 200, 300, 400, 350, 150);
// 處理
path.extendWithPath(path, Offset(50, 30),
    matrix4: Float64List.fromList(
        [1, 0, 0, 0, .1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 2]));
canvas.drawPath(path, paint);
複製程式碼

extendWithPath 圖中藍色圈起來的部分就是方法的效果

shift

Path shift(Offset offset)

可用於複製之前繪製的路徑並平移offset位置,返回Path。與extendWithPath不同的是,此方法僅僅是複製路徑,不會跟原路徑相連。

var path = Path();
path.moveTo(50, 500);
path.cubicTo(50, 200, 300, 400, 350, 150);

// 原始圖形為紅色
paint.color = Colors.red;
canvas.drawPath(path, paint);
// 複製原路徑並平移
var path2 = path.shift(Offset(50,20));
paint.color = Colors.yellow;
canvas.drawPath(path2, paint);
複製程式碼

shift

close

void close()

用於將路徑起點與終點連線起來

總結

經過上面學習,我們知道了其實多種繪製方式能達到一樣的效果

繪製點

  • canvas.drawPoints
  • path.addPolygon

繪製直線

  • drawPoints傳參PointMode.polygonPointMode.lines
  • drawLine
  • path.lineTo

繪製圓弧

  • canvas.drawArc
  • path.addArc

下一篇將介紹Canvas圖形變換相關如旋轉、平移、縮放等,如有興趣,請關注

相關文章