Flutter自定義繪製(1)- 繪製基礎

膠水發表於2019-02-18

CustomPainter

Flutter 中實現繪製的主要是CustomPainter類、

我們一般繼承這個類,來使用它;

class MyPainter extends CustomPainter{
  @override
  void paint(Canvas canvas, Size size) {
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return null;
  }

}複製程式碼

然後放在父控制元件的child裡用CustomPaint包裹

child:  new CustomPaint(
    size: new Size(200,200),
    painter: new MyPainter())複製程式碼

故事的開始:paint()

自定義繪製非常簡單,建立好 Paint 物件,重寫 paint(),繪製程式碼放在paint()裡面,大概就是這樣:

Paint _paint = Paint()
  ..color = Colors.amber //畫筆顏色
  ..strokeCap = StrokeCap.round //畫筆筆觸型別
  ..isAntiAlias = true //是否啟動抗鋸齒
  ..strokeWidth = 15.0; //畫筆的寬度

@override
void paint(Canvas canvas, Size size) {
  // 畫個實心圓
  canvas.drawCircle(new Offset(200, 200), 100, _paint);
}複製程式碼

Flutter自定義繪製(1)- 繪製基礎


Canvas和Paint基礎

Canvas.drawXXX() 系列方法和 Paint 的基礎掌握了,就能夠進行簡單的繪製需求。

  1. Canvas 類下的所有 draw- 開頭的方法,例如 drawCircle() drawArc()
  2. Paint 的初始化

    Paint _paint = Paint()
      ..color = Colors.deepOrange//畫筆顏色
      ..strokeCap = StrokeCap.round //畫筆筆頭型別
      ..isAntiAlias = true //是否開啟抗鋸齒
      ..blendMode = BlendMode.src//顏色混合模式
      ..style = PaintingStyle.fill //畫筆樣式,預設為填充
      ..colorFilter = ColorFilter.mode(Colors.blueAccent,
          BlendMode.src) //顏色渲染模式
      ..maskFilter = MaskFilter.blur(BlurStyle.inner, 3.0) //模糊遮罩效果
      ..filterQuality = FilterQuality.high //顏色渲染模式的質量
      ..strokeWidth = 5.0; //畫筆的寬度複製程式碼

好了,基礎介紹完了,大家可以直接點選下方,開啟Flutter官方文件檢視原汁原味的資料哦

這篇文章到這裡就可以關閉了,嘻嘻。

Flutter自定義繪製(1)- 繪製基礎

以下結合示例說說主要方法的使用

填充顏色 - drawColor(Color color, BlendMode blendMode) 

這個方法一般用於畫板底色填充及蒙版(使用蒙版時要注意圖片混合模式設為BlendMode.srcOver

canvas.drawColor(Color.fromARGB(80, 255, 0, 0), BlendMode.srcOver);複製程式碼

Flutter自定義繪製(1)- 繪製基礎


畫圓 - drawCircle(Offset c, double radius, Paint paint)

第一個引數 c 是圓心點的座標,直接new一個Offset出來填入x,y座標就完事了,第二個參 radius 是圓的半徑,paint不必多說;

canvas.drawCircle(new Offset(200, 200), 100, _paint);複製程式碼

Flutter自定義繪製(1)- 繪製基礎

座標系是以控制元件左上角開始的,跟初中學的座標系可不一樣;


畫矩形 - drawRect(Rect rect, Paint paint)

canvas.drawRect(new Rect.fromLTWH(10, 50, 50, 50),_paint);
_paint.style = PaintingStyle.stroke; // 中途將畫筆風格設為環形
canvas.drawRect(new Rect.fromLTWH(120, 50, 50, 50),_paint);複製程式碼

效果如下:

Flutter自定義繪製(1)- 繪製基礎


畫點 - drawPoints(PointMode pointMode, List points, Paint paint) 

第一個引數是點的模式,分三種

  • PointMode.points 
  • PointMode.lines 
  • PointMode.polygon 

第二個引數是一個點的集合,第三個引數...emmm,好了話不多說,上圖

PointMode.points - 點模式

List<Offset> points = new List();
points.add(new Offset(100, 100));
points.add(new Offset(125, 200));
points.add(new Offset(50, 150));
points.add(new Offset(150, 150));
points.add(new Offset(75, 200));
canvas.drawPoints(PointMode.points, points, _paint);複製程式碼

Flutter自定義繪製(1)- 繪製基礎

PointMode.lines - 情侶模式

canvas.drawPoints(PointMode.points, points, _paint);複製程式碼

Flutter自定義繪製(1)- 繪製基礎

咦,我左下角的點呢?哦,原來這個模式沒有配對的小點點會被刪除,嗚嗚嗚,單身狗沒人權的嘛?

PointMode.polygon - 連線模式

canvas.drawPoints(PointMode.polygon, points, _paint);複製程式碼

Flutter自定義繪製(1)- 繪製基礎


畫橢圓 - drawOval(Rect rect, Paint paint) 

這個方法和drawRect()是一毛一樣的,只不過一個畫的是矩形,一個是橢圓

canvas.drawOval(new Rect.fromLTWH(50, 50, 100, 50), _paint);
_paint.style = PaintingStyle.stroke;
canvas.drawOval(new Rect.fromLTWH(170, 50, 100, 50), _paint);複製程式碼

Flutter自定義繪製(1)- 繪製基礎


畫一條直線 - drawLine(Offset p1, Offset p2, Paint paint) 

p1線的起點,p2線的終點

canvas.drawLine(new Offset(100, 100), new Offset(200, 200), _paint);複製程式碼

Flutter自定義繪製(1)- 繪製基礎

Flutter並沒有提供畫多條線的方法,只能多寫幾次 drawLine() 或者使用 drawPoints() 的情侶模式


畫圓角矩形 - drawRRect(RRect rrect, Paint paint)

canvas.drawRRect(new RRect.fromLTRBR(50, 50, 200, 100, new Radius.circular(10.0)), _paint);
canvas.drawRRect(new RRect.fromLTRBR(50, 150, 200, 250, new Radius.elliptical(10.0, 30.0)), _paint);複製程式碼

Flutter自定義繪製(1)- 繪製基礎

下面這個矩形看似縱向拉伸過了,其實沒有,只是圓角模式是 elliptical (橢圓


畫矩形環 - drawDRRect(RRect outer, RRect inner, Paint paint)  

第一個引數的區域必須包括第二個引數的區域,否則無法顯示;

canvas.drawDRRect(new RRect.fromLTRBR(50, 50, 200, 100, new Radius.circular(10.0)),
    new RRect.fromLTRBR(60, 60, 190, 90, new Radius.circular(10.0)), _paint);複製程式碼

Flutter自定義繪製(1)- 繪製基礎


畫圓弧或扇形 - drawArc(Rect rect, double startAngle, double sweepAngle, bool useCenter, Paint paint)

第一個引數仍然是一塊矩形區域,第二個引數 startAngle 與第三個引數 sweepAngle 需要注意是弧度制,要轉化一下  乘以個  (pi / 180.0) 就行了度(下圖中紅線是 0 度的位置;順時針為正角度,逆時針為負角度),第四個引數 useCenter 代表是否與圓心連線,

canvas.drawArc(new Rect.fromLTWH(50, 50, 100, 50), 0.0 * (pi / 180.0), 90 * (pi / 180.0), false, _paint);
canvas.drawArc(new Rect.fromLTWH(50, 50, 100, 50), 200.0* (pi / 180.0), 90 * (pi / 180.0), true, _paint);
_paint.style = PaintingStyle.stroke; // 畫線模式
canvas.drawArc(new Rect.fromLTWH(50, 50, 100, 50), 100.0* (pi / 180.0), 90 * (pi / 180.0), false, _paint);複製程式碼

Flutter自定義繪製(1)- 繪製基礎

以上就是 Canvas 所有的簡單圖形的繪製。除了簡單圖形的繪製, Canvas 還可以使用drawPath(Path path, Paint paint)來繪製自定義圖形。


畫路徑 - drawPath(Path path, Paint paint) 

這個方法通過描述路徑的方式來繪製圖形,用法大概是這樣:

 Path _path = Path();
  @override
  void paint(Canvas canvas, Size size) {
    _paint.style = PaintingStyle.stroke; // 畫線模式
    _path.addArc(new Rect.fromLTWH(50, 50, 50, 50), 135.0 * (pi / 180.0), 225.0 * (pi / 180.0));
    _path.addArc(new Rect.fromLTWH(100, 50, 50, 50), 180.0 * (pi / 180.0), 225.0 * (pi / 180.0));
    _path.lineTo(100, 140);
    _path.lineTo(58, 93);
    canvas.drawPath(_path, _paint);
  }複製程式碼

Flutter自定義繪製(1)- 繪製基礎


Path 主要有方法如下:

直接描述路徑的方法還可以細分為兩組:新增子圖形和畫線(直線或曲線)

  • addXXX() - 新增子圖形(由於此類方法引數與上面介紹的畫簡單圖形一樣,就不多贅述了)
  1. addArc(Rect oval, double startAngle, double sweepAngle) - 新增圓弧
  2. addOval(Rect oval) - 新增圓
  3. addPolygon(List<Offset> points, bool close) - 新增一個由點的集合描述的多邊形
  4. addRect(Rect rect) - 新增矩形
  5. addRRect(Rect rect) - 新增圓角矩形
  6. addPath(Path path, Offset offset) - 新增子路徑
  • XXXTo() - 畫線(直線或曲線)

向目標位置畫直線 - lineTo(double x, double y) / relativelineTo(double x, double y) 

當前位置向目標位置畫一條直線, x 和 y 是目標位置的座標。這兩個方法的區 別是, lineTo(x, y) 的引數是絕對座標,而 relativeLineTo(x, y) 的引數是相對當前位置相對座標 ;

_paint.style = PaintingStyle.stroke; // 畫線模式
_path.lineTo(100, 100); // 由當前位置 (0, 0) 向 (100, 100) 畫一條直線
_path.relativeLineTo(100, 0); // 由當前位置 (100, 100) 向正右方畫100畫素的位置
canvas.drawPath(_path, _paint);複製程式碼

Flutter自定義繪製(1)- 繪製基礎


畫二階貝塞爾曲線 - quadraticBezierTo(double x1, double y1, double x2, double y2) /  relativeQuadraticBezierTo(double x1, double y1, double x2, double y2) 

x1,y1是控制點的座標;x2,y2是結束點的座標;relativeQuadraticBezierTo()同上面相對直線方法

_paint.style = PaintingStyle.stroke; // 畫線模式
List<Offset> points = new List();
points.add(new Offset(100, 50)); // 畫出控制點位置,方便理解
canvas.drawPoints(PointMode.points, points, _paint);
_path.moveTo(0, 100); // 移動起點到(0,100)
_path.quadraticBezierTo(100, 50, 200, 100);
canvas.drawPath(_path, _paint);複製程式碼

Flutter自定義繪製(1)- 繪製基礎


畫三階貝塞爾曲線 - cubicTo(double x1, double y1, double x2, double y2, double x3, double y3) / relativeCubicTo(double x1, double y1, double x2, double y2, double x3, double y3) 

和上面這個 quadraticBezierTo()和relativeQuadraticBezierTo() 的二階貝塞爾曲線同理,就不多說了。


移動到某點 - moveTo(double x, double y) / relativeMoveTo(double dx, double dy) 

不論是直線還是貝塞爾曲線,都是以當前位置作為起點,而不能指定起點。但可以通過 moveTo(x, y) 或 relativeMoveTo(x, y) 來改變當前位置,從而間接地設定這些方法的起點。

_paint.style = PaintingStyle.stroke; // 畫線模式
_path.moveTo(20, 40); // 移動起點到(20,20)
_path.lineTo(80, 100); // 畫條斜線
_path.moveTo(100, 40); // 移動起點到(100,20)
_path.lineTo(100, 100); // 畫條直線
canvas.drawPath(_path, _paint);複製程式碼

Flutter自定義繪製(1)- 繪製基礎

但凡事都有例外 arcTo() 這個方法並不從當前位置開始繪製

畫弧線 - arcTo(Rect rect, double startAngle, double sweepAngle, bool forceMoveTo) 

前三個引數,我們已經很熟悉了,最後一個引數的意思是,畫這個弧的時候是拖著筆到起點還是抬下筆到起點

_paint.style = PaintingStyle.stroke; // 畫線模式
_path.moveTo(20, 40); // 移動起點到(20,20)
_path.lineTo(80, 100); // 畫條斜線
_path.arcTo(new Rect.fromLTWH(60, 60, 100, 100), 0.0 * (pi / 180.0), 90.0 * (pi / 180.0), false);
canvas.drawPath(_path, _paint);複製程式碼

拖著筆:

Flutter自定義繪製(1)- 繪製基礎

抬下筆:

Flutter自定義繪製(1)- 繪製基礎


封閉當前路徑 - close() 

_paint.style = PaintingStyle.stroke; // 畫線模式
_path.moveTo(20, 40); // 移動起點到(20,20)
_path.lineTo(80, 100); // 畫條斜線
_path.arcTo(new Rect.fromLTWH(60, 60, 100, 100), 0.0 * (pi / 180.0), 90.0 * (pi / 180.0), false);
_path.close(); // 封閉當前路徑
canvas.drawPath(_path, _paint);複製程式碼

Flutter自定義繪製(1)- 繪製基礎

到這裡Canvas圖形的繪製就講的差不多了,圖形簡單時,使用 drawCircle() drawRect() 等方法來直接繪製;圖形複雜時,使用 drawPath() 來繪製自定義圖形。 除此之外, Canvas 還可以繪製圖片和文字。

畫圖片 - drawImage(Image image, Offset p, Paint paint) / drawImageRect(Image image, Rect src, Rect dst, Paint paint) 

drawImage() 從指定點開始將圖片寬高按畫素繪製,由於無法控制圖片的大小,並不常用;

第一個引數是ui包下的Image,並不是 Image Widget

Image 可以通過以下程式碼獲取

ui.Image image;

/**
 * 初始化圖片
 *
  Future<VoidCallback> initImage() async {
    image = await _loadImage("./assets/images/img.jpg");
    return null;
  }

  /**
   * 通過assets路徑,獲取資源圖片
   */
  Future<Image> _loadImage(String assets) async {
    final ByteData data = await rootBundle.load(assets);
    if (data == null) throw 'Unable to read data';
    Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List());
    FrameInfo frame = await codec.getNextFrame();
    return frame.image;
  }複製程式碼

然後在initState() 方法中初始化(  shouldRepaint() 方法一定要記得返回 true  ,否則無法重繪

void initState() {
  super.initState();
  painter.initImage().then((val) {
    setState(() {
    });
  });
}複製程式碼

最後在 paint() 方法中填入以下程式碼:

canvas.drawImage(image, new Offset(0, 0), _paint);複製程式碼

Flutter自定義繪製(1)- 繪製基礎

drawImageRect() 這個方法經常使用;主要了解第二個引數與第三個引數:

  • Rect src - 原圖的區域,一般傳圖片的寬高
  • Rect dst - 顯示的區域, 指圖片顯示的區域,如果原圖區域寬高比與顯示區域不一致,原圖會被拉伸壓縮


canvas.drawImageRect(image, Offset(0.0, 0.0) & Size(image.width.toDouble(), image.height.toDouble()), Offset(0.0, 0.0) & Size(200, 200), _paint);複製程式碼

正常比例:

Flutter自定義繪製(1)- 繪製基礎

拉伸:

Flutter自定義繪製(1)- 繪製基礎

畫文字 - drawParagraph(Paragraph paragraph, Offset offset)

程式碼註釋的很清楚,這裡迴圈畫了5段文字

for (int i = 0; i<5 ;i++){
  // 新建一個段落建造器,然後將文字基本資訊填入;
  ParagraphBuilder pb = ParagraphBuilder(ParagraphStyle(
    textAlign: TextAlign.left,
    fontWeight: FontWeight.w300,
    fontStyle: FontStyle.normal,
    fontSize: 15.0+i,
  ));
  pb.pushStyle(ui.TextStyle(color: Colors.black87));
  pb.addText('Flutter一統移動端');
  // 設定文字的寬度約束
  ParagraphConstraints pc = ParagraphConstraints(width: 300);
  // 這裡需要先layout,將寬度約束填入,否則無法繪製
  Paragraph paragraph = pb.build()..layout(pc);
  // 文字左上角起始點
  Offset offset = Offset(50, 50+i*40.0);
  canvas.drawParagraph(paragraph, offset);
}複製程式碼

Flutter自定義繪製(1)- 繪製基礎

Canvas及paint的使用基礎部分大概就是這樣了,下期文章見

Flutter自定義繪製(1)- 繪製基礎



相關文章