2、画笔Paint:Paint对象属性详解、颜色与样式、抗锯齿与图像滤波、画笔性能优化
好,咱们进入第二章。画笔Paint,说白了就是Flutter绘制的「灵魂」。你画布铺好了,路径画好了,但颜色不对、边缘锯齿、性能拉胯,那画面简直没法看。我刚开始用CustomPainter时,就吃过这个亏——画了个圆,边缘全是锯齿,项目经理直接截图发群里问我是不是用画图板画的……
所以这一章,咱们把Paint这个对象彻底吃透。从属性到样式,从抗锯齿到性能优化,一条龙讲清楚。
2.1 Paint对象的核心属性
Paint对象在Flutter里,其实就是一个配置类。你给它什么参数,它就用什么方式去画。我个人习惯把Paint的配置分成三类:颜色与样式、边缘处理、性能相关。
先看最常用的几个属性:
| 属性名 | 类型 | 作用 |
|---|---|---|
| color | Color | 画笔颜色 |
| style | PaintingStyle | 填充还是描边 |
| strokeWidth | double | 描边宽度 |
| strokeCap | StrokeCap | 线条端点样式 |
| strokeJoin | StrokeJoin | 线条连接处样式 |
| isAntiAlias | bool | 是否抗锯齿 |
| filterQuality | FilterQuality | 图像滤波质量 |
| shader | Shader? | 渐变或图像着色器 |
| maskFilter | MaskFilter? | 模糊等遮罩效果 |
嗯,这里要注意:color和shader是互斥的。你设置了shader,color就失效了。我在项目中遇到过有人同时设了color和shader,结果颜色死活不对,排查了半天才发现是shader覆盖了color。
2.2 颜色与样式:不只是填个色
颜色这块,Flutter提供了Color类,支持ARGB。但实际开发中,我建议直接用Color(0xFF2196F3)这种十六进制写法,清晰明了。别用Colors.blue这种,因为后期调色时你根本不知道它具体是什么蓝。
样式方面,PaintingStyle就两个值:fill和stroke。但组合起来玩法很多。比如你想画一个空心圆,再在内部填个半透明色,那就得用两个Paint对象:
Paint strokePaint = Paint()
..color = Colors.blue
..style = PaintingStyle.stroke
..strokeWidth = 4.0;
Paint fillPaint = Paint()
..color = Colors.blue.withOpacity(0.2)
..style = PaintingStyle.fill;
canvas.drawCircle(Offset(100, 100), 50, fillPaint);
canvas.drawCircle(Offset(100, 100), 50, strokePaint);
你想想看,如果只用同一个Paint,先fill再stroke,第二个会把第一个覆盖掉。所以记住:不同样式,用不同Paint对象。
2.3 抗锯齿与图像滤波:边缘的「美颜」
抗锯齿,就是isAntiAlias属性。默认是true,我建议永远不要把它设为false。除非你刻意追求像素风,否则锯齿边缘真的很掉价。
为什么会这样?因为屏幕是像素矩阵,你画一条斜线,不可能完美贴合每个像素。抗锯齿就是通过边缘半透明像素来「欺骗」眼睛,让线条看起来平滑。我曾经为了性能测试,关掉抗锯齿画了一堆图形,结果画面惨不忍睹,性能提升却微乎其微。所以,别省这个。
图像滤波filterQuality,这个属性很多人忽略。它影响的是图片缩放时的采样质量。有四个级别:
| 枚举值 | 质量 | 性能 | 适用场景 |
|---|---|---|---|
| none | 最差 | 最快 | 极少使用 |
| low | 低 | 快 | 缩略图预览 |
| medium | 中等 | 中等 | 一般场景 |
| high | 最好 | 最慢 | 大图精细展示 |
嗯,这里要注意:如果你用canvas.drawImage或drawImageRect,这个属性就很重要。我做过一个图片编辑App,用户缩放图片时卡顿严重,后来把filterQuality从high降到了medium,流畅度直接翻倍。说白了,就是质量和性能的trade-off。
filterQuality.high,结果列表卡成PPT。后来发现,列表里的图片根本不需要那么高的滤波质量。所以,只在真正需要精细展示的地方用high,其他地方用medium甚至low就够了。
2.4 画笔性能优化:别让Paint拖垮你的帧率
性能优化这块,我踩过的坑最多。先列几个核心原则:
- 复用Paint对象:不要在paint方法里new Paint()。在构造函数里创建好,paint方法里直接用。
- 减少属性变更:每次修改Paint的属性,都会触发内部状态刷新。如果频繁改颜色或宽度,考虑用多个Paint对象。
- 避免不必要的抗锯齿:虽然我说默认开抗锯齿,但如果你画的是水平或垂直线条,其实可以关掉。因为这类线条天然对齐像素,抗锯齿反而增加计算量。
- 慎用MaskFilter和Shader:这两个东西很耗性能。尤其是模糊效果,能不用就不用。如果一定要用,考虑用图片代替。
举个例子,我之前做一个实时绘图App,用户手指滑动时每秒要画几十个点。一开始我每个点都new Paint(),结果帧率直接掉到20fps。后来改成复用Paint,帧率稳定在60fps。差距就这么大。
class MyPainter extends CustomPainter {
final Paint _linePaint;
final Paint _fillPaint;
MyPainter() :
_linePaint = Paint()
..color = Colors.blue
..style = PaintingStyle.stroke
..strokeWidth = 2.0
..isAntiAlias = true,
_fillPaint = Paint()
..color = Colors.blue.withOpacity(0.3)
..style = PaintingStyle.fill;
@override
void paint(Canvas canvas, Size size) {
// 直接使用缓存的Paint对象
canvas.drawCircle(Offset(50, 50), 30, _fillPaint);
canvas.drawCircle(Offset(50, 50), 30, _linePaint);
}
@override
bool shouldRepaint(covariant MyPainter oldDelegate) {
return false; // 如果内容不变,返回false避免重绘
}
}
好,这一章就到这里。下一章咱们聊聊路径Path,那可是绘制复杂图形的核心。到时候我会分享一个我画「贝塞尔曲线」时踩过的坑,保证让你少走弯路。