持续创造,加速成长!这是我参与「日新计划 10 月更文应战」的第25天,点击查看活动概况

CustomPaint是Flutter中用于自在制作的一个widget,它与android原生的制作规则基本一致,以当时Canves(画布)的左上角为原点进行制作。在有些场景中,咱们会需求制作一些高度定制化的组件,比如 UI 设计师给咱们出了个难题 —— 弄一个奇形怪状的边框。这个时分咱们就不能直接使用 Flutter 自带的那些组件了,而是需求手动制作组件,那就会需求用到 CuntomPaint 组件。CustomPaint 组件和前端Canvas差不多,允许咱们在一个画布上制作各种元素,包括点、线、矩形、圆弧、文字、图片等等。

CustomPaint 介绍

CustomPaint是一个 Widget,其中有三个重要的参数:

CustomPaint(
  child: childWidget(),
  foregroundPainter: foregroundPainter(),
  painter: backgroundPainter(),
)

childCustomPaint的子组件;

painterforegroundPainter:都是 CustomPainter 类,用于定义 canvas 制作的内容。区别在于,首先是执行 painter 的制作指令。然后是在布景上渲染 child 子组件。最终,foregroundPainter 的内容会制作在 child 上一层。

25、Flutter之CustomPaint 自定义绘画
事例展现:

import 'package:demo202112/utils/common_appbar.dart';
import "package:flutter/material.dart";
class MyPaint extends StatelessWidget {
  const MyPaint({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: getAppBar('CustomPaint'),
      body: CustomPaint(
        painter: MyPainer(),
        child: Container(height: 80,width: 80,child: Text('child测试'),color: Colors.red,),
        foregroundPainter: MyForeGroundPainer(),
      ),
    );
  }
}
class MyPainer extends CustomPainter{
  late Paint _paint;
  @override
  void paint(Canvas canvas, Size size) {
    _paint = Paint();
    _paint.color = Colors.blue;
    canvas.drawCircle(Offset(100, 100), 100, _paint);
    canvas.drawLine(Offset(300, 300), Offset(400, 400), _paint);
    // TODO: implement paint
  }
  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    // TODO: implement shouldRepaint
    throw UnimplementedError();
  }
}
class MyForeGroundPainer extends CustomPainter{
  late Paint _paint;
  @override
  void paint(Canvas canvas, Size size) {
    _paint = Paint();
    _paint.color = Colors.green;
    canvas.drawCircle(Offset(100, 100), 70, _paint);
    // canvas.drawLine(Offset(300, 300), Offset(400, 400), _paint);
    // TODO: implement paint
  }
  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    // TODO: implement shouldRepaint
    throw UnimplementedError();
  }
}

运转作用:

25、Flutter之CustomPaint 自定义绘画

child: 赤色区域,传入一个子widget,这个widget图层会在painter在上,在foregroundPainter之下。

painter:蓝色区域。

foregroundPainter:绿色区域,它与painter都是CustomPainter类型的。经过名字大约也就知道了,它会在painter的上层,也便是说在相同的方位去制作,foregroundPainter 会覆盖painter。

CustomPainter供给了一个paint绘图办法供咱们制作图形,该办法携带canvassize两个参数,其中 canvas 是画布,size 是画布巨细。canvas 供给了很多制作图形的办法,比如制作路径、矩形、圆形和线条等等。

//画圆
drawCircle(Offset c, double radius, Paint paint) → void
//画图片
drawImage(Image image, Offset p, Paint paint) → void
//画九宫图
drawImageNine(Image image, Rect center, Rect dst, Paint paint) → void
//画线
drawLine(Offset p1, Offset p2, Paint paint) → void
//画椭圆
drawOval(Rect rect, Paint paint) → void
//画文字
drawParagraph(Paragraph paragraph, Offset offset) → void
//画Rect区域
drawRect(Rect rect, Paint paint) → void
//画阴影
drawShadow(Path path, Color color, double elevation, bool transparentOccluder) → void

制作点

class MyPoints extends CustomPainter{
  Paint _paint = Paint()
  ..color = Colors.red
  ..strokeWidth = 15;
  @override
  void paint(Canvas canvas, Size size) {
    // TODO: implement paint
    var points =[
      Offset(0, 0),
      Offset(size.width/2, size.height/2),
      Offset(size.width, size.height),
    ];
    canvas.drawPoints(PointMode.points, points, _paint);
  }
  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    // TODO: implement shouldRepaint
    throw UnimplementedError();
  }
}

运转作用:

25、Flutter之CustomPaint 自定义绘画

PointMode有3种模式:

  • points:点
  • lines:将2个点制作为线段,假如点的个数为奇数,最终一个点将会被疏忽
  • polygon:将整个点制作为一条线

制作线 和路径

class MyGraph extends CustomPainter{
  final Paint _paint = Paint()
    ..color = Colors.red
    ..strokeWidth = 15;
  final Paint _paintPath = Paint()
    ..color = Colors.blue
    ..strokeWidth = 5
  ..style = PaintingStyle.fill;
  @override
  void paint(Canvas canvas, Size size) {
    // TODO: implement paint
    //制作线
    canvas.drawLine(Offset(0, 30),Offset(size.width-30, size.height), _paint);
    //制作路径
    var _path = Path()
    ..moveTo(0, 0)
    ..lineTo(size.width, 0)
    ..lineTo(size.width, size.height)
    ..close();
    canvas.drawPath(_path, _paintPath);
    //这里留意Paint.style,还能够设置为PaintingStyle.fill,
    //制作圆形
    canvas.drawCircle(Offset(size.width/2+50, size.height/2+50), 20, _paint);
    //制作椭圆
    canvas.drawOval(Rect.fromLTRB(0, 0, size.width, size.height/2), _paint);
    //制作弧
    canvas.drawArc(Rect.fromLTRB(0, 0, size.width, size.height), 0, pi/2, true, _paint);
    //制作圆角矩形
    canvas.drawRRect(RRect.fromLTRBR(0, 0, size.width, size.height, Radius.circular(10)), _paint);
  }
  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    // TODO: implement shouldRepaint
    throw UnimplementedError();
  }
}

运转作用:

25、Flutter之CustomPaint 自定义绘画

制作五子棋

首先制作布景,淡黄色,再制作棋盘网格线,随后制作黑白子,具体代码:

class CustomPaintRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: CustomPaint(
        size: Size(300, 300), //指定画布巨细
        painter: MyPainter(),
      ),
    );
  }
}
class MyPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    double eWidth = size.width / 15;
    double eHeight = size.height / 15;
    //画棋盘布景
    var paint = Paint()
      ..isAntiAlias = true
      ..style = PaintingStyle.fill //填充
      ..color = Color(0x77cdb175); //布景为纸黄色
    canvas.drawRect(Offset.zero & size, paint);
    //画棋盘网格
    paint
      ..style = PaintingStyle.stroke //线
      ..color = Colors.black87
      ..strokeWidth = 1.0;
    for (int i = 0; i <= 15; ++i) {
      double dy = eHeight * i;
      canvas.drawLine(Offset(0, dy), Offset(size.width, dy), paint);
    }
    for (int i = 0; i <= 15; ++i) {
      double dx = eWidth * i;
      canvas.drawLine(Offset(dx, 0), Offset(dx, size.height), paint);
    }
    //画一个黑子
    paint
      ..style = PaintingStyle.fill
      ..color = Colors.black;
    canvas.drawCircle(
      Offset(size.width / 2 - eWidth / 2, size.height / 2 - eHeight / 2),
      min(eWidth / 2, eHeight / 2) - 2,
      paint,
    );
    //画一个白子
    paint.color = Colors.white;
    canvas.drawCircle(
      Offset(size.width / 2 + eWidth / 2, size.height / 2 - eHeight / 2),
      min(eWidth / 2, eHeight / 2) - 2,
      paint,
    );
  }
  //在实际场景中正确使用此回调能够避免重绘开销,本示例咱们简单的回来true
  @override
  bool shouldRepaint(CustomPainter oldDelegate) => true;
}

运转作用:

25、Flutter之CustomPaint 自定义绘画

制作是比较贵重的操作,所以咱们在实现自绘控件时应该考虑到功能开销,下面是两条关于功能优化的主张:

  • 尽可能的使用好shouldRepaint回来值;在UI树从头build时,控件在制作前都会先调用该办法以确认是否有必要重绘;假如咱们制作的UI不依靠外部状况,那么就应该始终回来false,因为外部状况改动导致从头build时不会影响咱们的UI外观;假如制作依靠外部状况,那么咱们就应该在shouldRepaint中判断依靠的状况是否改动,假如已改动则应回来true来重绘,反之则应回来false不需求重绘。

  • 制作尽可能多的分层;在上面五子棋的示例中,咱们将棋盘和棋子的制作放在了一同,这样会有一个问题:由于棋盘始终是不变的,用户每次落子时变的只是棋子,但是假如依照上面的代码来实现,每次制作棋子时都要从头制作一次棋盘,这是没必要的。优化的办法便是将棋盘独自抽为一个Widget,并设置其shouldRepaint回调值为false,然后将棋盘Widget作为布景。然后将棋子的制作放到另一个Widget中,这样落子时只需求制作棋子。

总结

CustomPaint class供给了让用户自定义widget的能力,它暴露了一个canvas,能够经过这个canvas来制作widget,CustomPaint会先调用painter制作布景,然后再制作child,最终调用foregroundPainter来制作前景。

canvas–画布,真正的制作是由canvas跟paint来完结的,画布供给了各种制作的接口来制作图形,除此以外画布还供给了平移、缩放、旋转等矩阵改换接口,画布都有固定巨细跟形状,还能够使用画布供给的裁剪接口来裁剪画布的巨细形状等等

Paint—笔画,是用来设置在画布上面制作图形时的一些笔画属性,如:颜色、线宽、制作模式、抗锯齿等等.

自绘控件非常强大,理论上能够实现任何2D图画外观,想更深化的了解,能够找到其对应的RenderObject目标,如Text Widget最终会经过RenderParagraph目标来经过Canvas实现文本制作逻辑。了解了更底层的制作逻辑,才能更好的在实际项目中灵活应用。