Flutter CustomPaint 入门:别再只会写 Widget 了,你得学会“画”

6 阅读4分钟

说实话,大多数 Flutter 开发者有个明显短板:

👉 只会“拼 Widget”,不会“画 UI”

一旦设计稿稍微离谱一点,比如:

  • 波形图
  • 不规则背景
  • 自定义进度条
  • Canvas 动画

很多人第一反应是:
“Flutter 怎么没有现成组件?”

不是没有,是你还没用到 CustomPaint

这篇我不跟你讲官方文档那套,我用一个更工程化的方式,带你把 CustomPaint 这玩意吃透。


一、先把认知掰正:Flutter 本质就是一块画布

很多人学 Flutter,会有种违和感:

“怎么感觉不像原生?”

你没感觉错。

Flutter 本质上就是:

👉 一块高性能 Canvas + 一堆 UI 封装

你平时写的:

Container()
Text()
Row()

本质上都是在“帮你画”。


换个更接地气的理解

我一般跟新人这么解释:

Flutter = 画室
Widget = 画笔
CustomPaint = 你自己拿笔画

当你理解到这一步,CustomPaint 就不再神秘了。


二、什么时候你必须用 CustomPaint?

一句话:

官方组件满足不了,就该你自己画了

典型场景:

  • 特殊 UI(设计稿还原)
  • 图表 / 波形 / 动画
  • 高性能绘制(比堆 Widget 更轻)

三、CustomPaint 到底干了什么?

核心就一句话:

👉 给你一块画布,让你自己画

但它有个前提:

你必须提供一个“画家”(CustomPainter)


四、真正关键:CustomPainter 才是核心

你不能直接用 CustomPainter,因为:

👉 它是抽象类

必须自己实现。


标准写法(工程通用模板)

class WavePainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    // 真正绘制的地方
  }

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

两个方法,讲人话解释一下

1️⃣ paint(你真正干活的地方)

  • canvas:画布
  • size:当前画布大小

👉 所有图形,都是在这里画出来的


2️⃣ shouldRepaint(性能开关)

很多人乱写这里,这是个性能坑。

👉 它决定要不要重新绘制

经验规则:

  • 静态 UI → false
  • 动态变化 → true

五、开始画:先搞一支“画笔”

在 Canvas 世界里,没有画笔你什么都干不了。

final paint = Paint()
  ..color = Colors.white
  ..strokeWidth = 3
  ..strokeCap = StrokeCap.round;

这个东西本质就是:

👉 你所有绘制风格的控制器

可以控制:

  • 颜色
  • 粗细
  • 填充 / 描边
  • 抗锯齿

六、实战:画一个“音频波形”

这段是整篇最有价值的地方,我帮你重新讲清楚逻辑。


核心思路

  1. 找中线(y轴中心)
  2. 随机生成高度
  3. 画一根根竖线
  4. 横向平移

示例代码(已重构表达)

void paint(Canvas canvas, Size size) {
  final paint = Paint()
    ..color = Colors.white
    ..strokeWidth = 3
    ..strokeCap = StrokeCap.round;

  final centerY = size.height / 2;

  double x = 0;

  final values = List.generate(100, (_) {
    return Random().nextDouble() * centerY;
  });

  for (final v in values) {
    if (x > size.width) break;

    canvas.drawLine(
      Offset(x, centerY - v),
      Offset(x, centerY + v),
      paint,
    );

    x += 6; // 线宽 + 间距
  }
}

七、这段代码真正干了啥?(重点)

很多教程到这里就结束了,但新人其实没懂。

我帮你拆开讲👇


1️⃣ 坐标系:从左上角开始

Canvas 的坐标系是:

👉 左上角 = (0,0)

往右是 x+
往下是 y+


2️⃣ 为什么要 centerY

final centerY = size.height / 2;

👉 用来让波形“居中”

否则你画出来会贴在顶部。


3️⃣ 为什么高度不会超出?

Random().nextDouble() * centerY;

因为:

  • nextDouble() ∈ [0,1)
  • 最大只会到 centerY

👉 所以不会画出画布


4️⃣ drawLine 本质

canvas.drawLine(start, end, paint);

其实就是:

👉 从 A 点画到 B 点

我们做的是:

  • 上:centerY - v
  • 下:centerY + v

👉 形成一根“柱状线”


5️⃣ x += 6 是干嘛的?

很多人看不懂这个。

👉 控制横向间距

等价于:

  • 线宽:3
  • 间距:3

6️⃣ 为什么要限制 width?

if (x > size.width) break;

👉 防止画出画布(性能 + 安全)


八、尺寸问题:为什么默认是 300x300?

Flutter 默认给你一个:

👉 300 × 300 的画布

如果你不控制,它就这么大。


正确做法

Container(
  width: 400,
  height: 100,
  child: CustomPaint(
    painter: WavePainter(),
  ),
)

九、很多人踩的坑(经验总结)

❌ 1. 以为 CustomPaint 很重

其实相反:

👉 比堆 Widget 更轻


❌ 2. shouldRepaint 全写 true

结果:

👉 每帧重绘,直接掉帧


❌ 3. 不控制绘制范围

👉 画出边界 = 渲染异常


❌ 4. 想用它替代一切

别极端:

  • 普通 UI → Widget
  • 特殊绘制 → CustomPaint

十、最后讲点“工程经验”

CustomPaint 这个东西,本质上是:

Flutter 给你开的“后门能力”

你一旦掌握它:

  • UI 上限直接提升
  • 不再被组件限制
  • 能做真正差异化设计

一句话总结

不会 CustomPaint 的 Flutter 开发,本质上还停留在“拼积木阶段”