Skip to content

前端转 Flutter 笔记 (Day 30):高级 UI——CustomPainter 与 Canvas 绘图 🎨

前言: 当系统自带的 ContainerStackContainer(decoration: ...) 已经无法满足你对视觉的终极追求时,就轮到 CustomPainter 登场了。 它是 Flutter 的“画笔”,让你直接在显卡这张画布上,像使用 CSS Canvas 或 SVG 路径一样,绘制任何你想要的形状。


1. 为什么需要 CustomPainter?

在 Web 端,我们有 <canvas> 和 SVG。在 Flutter 中:

  • 普通组件:像“积木”,通过组合实现 UI。
  • CustomPainter:像“油画”,通过指令绘制 UI。

优点

  1. 极致性能:跳过 Widget 组合层,直接下发绘制指令。
  2. 像素级控制:实现精细的图表、复杂的路径过渡、不规则阴影。

2. 核心架构:两板斧

实现一个自定义绘图需要两个关键部分:

2.1 画布 (Canvas) 与 画笔 (Paint)

  • Canvas:提供了绘制各种形状的方法,如 drawCircle, drawLine, drawPath
  • Paint:定义绘制的样式。粗细、颜色、是否空心、抗锯齿、渐变等。

2.2 shouldRepaint:性能守门员

这是一个核心函数,返回 true 代表需要重新绘制。

📌 避坑:不要无脑返回 true。如果你的绘图数据没变(比如只是传进来的背景色没变),返回 false 能极大减少 GPU 负担。


3. 实战:绘制丝滑的波浪背景 (Path)

前端同学熟悉的 cubicTo(三次贝塞尔曲线)在 Flutter 中同样适用。

dart
class MyWavyPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Colors.blue
      ..style = PaintingStyle.fill;

    final path = Path();
    path.lineTo(0, size.height * 0.8); // 移动到左下角附近
    
    // 三次贝塞尔曲线:控制点1, 控制点2, 终点
    path.cubicTo(
      size.width * 0.25, size.height,
      size.width * 0.75, size.height * 0.6,
      size.width, size.height * 0.8,
    );
    
    path.lineTo(size.width, 0); // 画到右上角
    path.close(); // 闭合路径

    canvas.drawPath(path, paint);
  }
  
  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

4. 动画结合:赋予画布灵魂

仅仅静态的波浪太无趣了。你可以传入一个 Animation<double> 给你的 Painter。

  1. StatefulWidget 中创建一个 AnimationController
  2. build 中使用 ListenableBuilder 包裹你的 CustomPaint
  3. 把动画值传入 CustomPainter 的构造函数。
  4. paint 方法中利用这个值动态改变 Path 的坐标。

这样,你就得到了一个动态流动的波浪,这种效果在 App 的启动页或个人中心头部非常“显贵”。


5. 性能军规

  1. 能用 BoxDecoration 解决的不要用 CustomPainter:简单的圆角、阴影、渐变,系统组件已经高度优化。
  2. 善用裁剪 (ClipPath):有时候先画一个大图再裁剪出形状,比画复杂路径更省心。
  3. 分层绘制:如果背景是复杂的绘图但前景是动态的,考虑用两个 CustomPaint,让背景层 shouldRepaintfalse

📌 CustomPainter 箴言:它是从“搬砖工”进化为“设计师”的必经之路。掌握了它,你就能在 Flutter 的世界里随心所欲地控制每一个像素。