Flutter自繪-02-Canvas 與 Paint基礎

天色將變發表於2021-03-02
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),根據左上角頂點和右下角頂點定義一個矩形。

示例: image.png

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

image.png

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

image.png

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 畫筆

示例: image.png

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

image.png

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

示例: image.png

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 是顏色的混合模式

image.png

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

image.png 引數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; image.png

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; image.png

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() image.png

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,是否將弧與中心連線,形成一個扇形

image.png

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.百度翻譯的,沒明白啥意思

image.png

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,是會報錯的。

image.png

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的距離 image.png

// 平移
  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倍 看效果: image.png

// 縮放
  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)

看效果: 先平移,再旋轉: image.png

// 旋轉
  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) image.png

 // 斜切
  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();
  }
複製程式碼

相關文章