You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中如何延迟后在Canvas连续绘制Offset?解决释放对象报错

嘿,我来帮你搞定这个「Object has been disposed」的报错问题!

问题根源分析

这个错误本质上是你在尝试使用一个已经被Flutter框架销毁的对象——大概率是你的PathData里封装的Path(或者其他可释放资源)在延迟绘制的过程中被提前清理了。因为CustomPainter的生命周期和渲染绑定,异步延迟操作很容易导致旧的绘制对象被框架标记为已销毁,后续再调用就会触发这个报错。

具体解决方案

我给你调整两个核心点,就能彻底解决这个问题:

1. 让PathData存储原始绘制数据,而非已构建的Path

不要在PathData里直接存已经创建好的Path对象,而是存储最原始的Offset列表和画笔配置,每次绘制时实时构建Path,从根源避免复用已销毁的对象。

// 重新定义PathData,只存原始数据
class PathData {
  final List<Offset> offsets;
  final Paint paint;

  PathData(this.offsets, this.paint);
}

2. 修改ReplayPainter,实时构建Path进行绘制

在paint方法里每次都重新创建Path,而不是复用之前的对象,确保每次绘制用的都是新鲜有效的资源:

class ReplayPainter extends CustomPainter {
  final List<PathData> strokes;

  ReplayPainter(this.strokes);

  @override
  void paint(Canvas canvas, Size size) {
    print(strokes.length);
    for (PathData stroke in strokes) {
      if (stroke.offsets.isEmpty) continue;
      // 每次paint时重新构建Path,彻底避免已disposed的对象问题
      final path = Path();
      path.moveTo(stroke.offsets.first.dx, stroke.offsets.first.dy);
      for (int i = 1; i < stroke.offsets.length; i++) {
        path.lineTo(stroke.offsets[i].dx, stroke.offsets[i].dy);
      }
      canvas.drawPath(path, stroke.paint);
    }
  }

  @override
  bool shouldRepaint(covariant ReplayPainter oldDelegate) {
    // 只有当strokes内容变化时才触发重绘,优化性能
    return oldDelegate.strokes != strokes;
  }
}

3. 用动画控制器优雅实现延迟绘制进度

如果你的延迟绘制是为了展示绘制过程,推荐用AnimationController来控制进度,它能完美和Flutter的渲染周期同步,避免异步操作带来的对象生命周期问题:

class ReplayDrawPage extends StatefulWidget {
  @override
  _ReplayDrawPageState createState() => _ReplayDrawPageState();
}

class _ReplayDrawPageState extends State<ReplayDrawPage> with SingleTickerProviderStateMixin {
  late AnimationController _playController;
  List<PathData> _fullDrawData = []; // 完整的所有绘制数据
  List<PathData> _currentDrawData = []; // 当前进度对应的绘制数据

  @override
  void initState() {
    super.initState();
    // 初始化动画控制器,设置总播放时长
    _playController = AnimationController(
      duration: const Duration(seconds: 4),
      vsync: this,
    )..addListener(() => _updateDrawProgress());
    
    // 这里假设你已经初始化好了_fullDrawData(所有的Offset集合)
    _playController.forward(); // 开始播放绘制动画
  }

  void _updateDrawProgress() {
    final progress = _playController.value; // 0~1的进度值
    setState(() {
      _currentDrawData = _fullDrawData.map((stroke) {
        // 根据进度截取对应数量的Offset
        final takeCount = (stroke.offsets.length * progress).round().clamp(0, stroke.offsets.length);
        return PathData(
          stroke.offsets.take(takeCount).toList(),
          stroke.paint,
        );
      }).toList();
    });
  }

  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      painter: ReplayPainter(_currentDrawData),
      size: Size.infinite,
    );
  }

  @override
  void dispose() {
    _playController.dispose();
    super.dispose();
  }
}

额外提醒

如果你的PathData里还持有其他可销毁的资源(比如Picture、Shader等),也要遵循同样的逻辑:只存储原始配置数据,在paint方法里实时创建,不要复用可能被框架销毁的对象。

内容的提问来源于stack exchange,提问作者user983327

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 09:12:11