You need to enable JavaScript to run this app. Flutter CustomPainter 更新静态笔点列表后不触发重绘问题
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.29 04:54:04