Flutter CustomPainter 更新静态笔点列表后不触发重绘问题
Flutter CustomPainter 不触发重绘问题排查
核心原因
shouldRepaint的执行前提是CustomPaint组件接收到了新的 CustomPainter 实例,Flutter 才会对比新旧 painter、执行方法判断是否需要重绘- 当前实现把渲染数据
penPointsToRender定义为 Painter 类的静态变量,本质上和 Widget 树的构建流程完全解绑,就算往列表里加了数据,只要没有新建 Painter 实例、触发 CustomPaint 重建,Flutter 根本感知不到数据变化 - 哪怕把
shouldRepaint写死返回true,没有前置的 Painter 实例更新链路,这个方法永远不会被调用,自然不会执行重绘 - 现有代码中
notifyListeners()的调用位置完全错误:只在setBrushGestureDetection方法初始化手势回调的时候调用了一次,实际触摸过程中往列表加数据的操作,完全没有通知监听者,就算状态链路打通也不会触发更新
修复步骤
1. 移除 Painter 类的静态渲染数据
不要把渲染点位存在 Painter 类的静态字段里,将其改为 Painter 的实例成员,由外部传入,和实例生命周期绑定。
修正后的 MainCanvas 实现:
class MainCanvas extends CustomPainter{ final List<Offset?> penPointsToRender; MainCanvas({required this.penPointsToRender}); @override void paint(Canvas canvas, Size size) { Paint paint = Paint() ..color=Colors.black ..strokeWidth=4 ..strokeCap = StrokeCap.round; renderPenPoints(paint, canvas); } void renderPenPoints(Paint paint, Canvas canvas){ for (int i = 0; i < penPointsToRender.length - 1; i++) { final p1 = penPointsToRender[i]; final p2 = penPointsToRender[i + 1]; if (p1 != null && p2 != null) { canvas.drawLine(p1, p2, paint); } } } @override bool shouldRepaint(covariant MainCanvas oldDelegate) { // 对比新旧渲染数据引用,避免无意义重绘 return oldDelegate.penPointsToRender != penPointsToRender; } }
2. 打通状态到画布的重建链路
- 将
penPointsToRender列表挪入CanvasGestureDetectorModel中作为可监听的状态字段 - 用
Consumer<CanvasGestureDetectorModel>包裹CustomPaint组件,当 Model 通知状态更新时,自动使用最新的点位列表生成新的MainCanvas实例传入
示例构建代码:
Consumer<CanvasGestureDetectorModel>( builder: (context, model, child) { return CustomPaint( painter: MainCanvas(penPointsToRender: model.penPointsToRender), // 手势检测器作为子组件传入,避免手势逻辑跟着状态重建重复绑定 child: child, ); }, child: CanvasGestureDetector( Provider.of<CanvasGestureDetectorModel>(context, listen: false) ), )
3. 补全数据更新后的通知逻辑
在所有修改点位列表的手势回调中,完成数据写入后立刻调用 notifyListeners(),触发监听组件重建:
void setBrushGestureDetection(BuildContext context){ onPanDown = (panInfo){ penPointsToRender.add(panInfo.localPosition); notifyListeners(); }; onPanUpdate = (panInfo){ // 原有边界判断逻辑保留 if( panInfo.globalPosition.dx < CanvasArea.canvasPaddingHorizontal / 2 || panInfo.globalPosition.dx > CanvasArea.canvasPaddingHorizontal / 2 + (MediaQuery.of(context).size.width - CanvasArea.canvasPaddingHorizontal) || panInfo.globalPosition.dy < CanvasArea.canvasPaddingVertical / 2 - 20 || panInfo.globalPosition.dy > (MediaQuery.of(context).size.height - CanvasArea.canvasPaddingVertical) + (CanvasArea.canvasPaddingVertical ) / 2 -20 ){return;} final lastPoint = penPointsToRender.last; if (lastPoint != panInfo.localPosition) { penPointsToRender.add(panInfo.localPosition); notifyListeners(); } }; onPanEnd = (panInfo){ penPointsToRender.add(null); notifyListeners(); }; }
性能优化提示:点位较多时,可以用
RepaintBoundary包裹CustomPaint组件,把画布的重绘范围和页面其他组件隔开,减少全量页面重绘的开销。
内容的提问来源于stack exchange,提问作者Amit Ritter
相关产品推荐
相关产品推荐

