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
相关产品推荐
相关产品推荐

