应用开发过程中难免会遇到框架提供的控件不能满足需求的情况,比如一些定制化的绘制图形,比较红包雨,小游戏等等。 遇见这种情况时,在Android上我们可以在SurfaceView上利用Canvas绘制一些定制效果,在Web上我们可以利用WebGL中的Canvas进行绘制。同样地,Flutter中我们可以使用CustomPaint和Canva进行一些特殊的绘制。

先来看一下CustomPaint的定义:

  const CustomPaint({
    Key key,
    this.painter,
    this.foregroundPainter,
    this.size = Size.zero,
    this.isComplex = false,
    this.willChange = false,
    Widget child,
  })

其中比较重要的参数就是painter和foregroundPainter,其中painter表示的是背景画笔,foregroundPainter表示的是前景画笔,这两个画笔都是CustomPainter类型。 我们开发过程中一般会继承CustomPainter类实现自己的画笔。

现在我们就开始绘制一个围棋棋盘吧,先看看最终的效果图:

棋盘效果图

绘制棋盘

首先我们要知道,围棋棋盘的盘面由 18x18个方格和19x19条线组成。知道了这一点,我们对应的绘制出线段即可。

/// 围棋棋盘盘面,由 18x18个方格和19x19条线组成
class BackgroundPainter extends CustomPainter {
  Paint myPaint = Paint();
  final stars = [
    Offset(3, 3),
    Offset(9, 3),
    Offset(15, 3),
    Offset(3, 9),
    Offset(9, 9),
    Offset(15, 9),
    Offset(3, 15),
    Offset(9, 15),
    Offset(15, 15),
  ];

  @override
  void paint(Canvas canvas, Size size) {
    double cellWidth = size.width / 18;
    double cellHeight = size.height / 18;

    myPaint
      ..isAntiAlias = true
      ..style = PaintingStyle.fill
      ..color = Color(0x77cdb175);
    canvas.drawRect(Offset.zero & size, myPaint);

    myPaint
      ..style = PaintingStyle.stroke
      ..color = Colors.black87
      ..strokeWidth = 1.0;

    for (int i = 0; i <= 18; i++) {
      double dy = cellHeight * i;
      canvas.drawLine(Offset(0, dy), Offset(size.width, dy), myPaint);
    }

    for (int i = 0; i <= 18; i++) {
      double dx = cellWidth * i;
      canvas.drawLine(Offset(dx, 0), Offset(dx, size.height), myPaint);
    }

    myPaint
      ..color = Colors.black
      ..style = PaintingStyle.fill;
    stars.forEach((star) {
      canvas.drawCircle(star * cellWidth, 2, myPaint);
    });
  }

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

绘制黑白棋子

从上面的代码可以看到,主要工作都是在paint方法中执行的,绘制棋子当然也不例外:

/// 绘制零星几个棋子
class SimplePainter extends CustomPainter {
  Paint blackPaint = Paint()
    ..color = Colors.black
    ..style = PaintingStyle.fill;
  Paint whitePaint = Paint()
    ..color = Colors.white
    ..style = PaintingStyle.fill;

  final blacks = [
    Offset(2, 1),
    Offset(2, 2),
    Offset(2, 0),
    Offset(11, 2),
    Offset(13, 7),
    Offset(7, 7),
  ];
  final whites = [
    Offset(2, 3),
    Offset(3, 2),
    Offset(1, 2),
    Offset(8, 3),
    Offset(9, 12),
    Offset(10, 11),
  ];

  @override
  void paint(Canvas canvas, Size size) {
    double cellWidth = size.width / 18;
    double cellHeight = size.height / 18;

    blacks.forEach((offset) {
      blackPaint
        ..shader = prefix0.Gradient.radial(
            offset * cellWidth, 9, [Colors.black, Colors.grey[850]]);
      canvas.drawCircle(offset * cellWidth, 9, blackPaint);
    });

    whites.forEach((offset) {
      whitePaint
        ..shader = prefix0.Gradient.radial(
            offset * cellWidth, 9, [Colors.white, Colors.grey[100]]);
      canvas.drawCircle(offset * cellWidth, 9, whitePaint);
    });
  }

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

Canvas与CustomPaint的组合

上面两步定义了背景画笔用来绘制棋盘和星位(围棋术语),前景画笔用来绘制棋子,现在就将它们用在CustomPaint中吧:

class CustomPaintDemo extends StatefulWidget {
  @override
  _CustomPaintDemoState createState() => _CustomPaintDemoState();
}

class _CustomPaintDemoState extends State<CustomPaintDemo> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("CustomPaint"),
        centerTitle: true,
      ),
      body: Center(
        child: CustomPaint(
          size: Size(360, 360),
          painter: BackgroundPainter(),
          foregroundPainter: SimplePainter(),
        ),
      ),
    );
  }
}

源码

https://github.com/jiangkang/flutter-system