Flutter CustomPainter:如何无需白色绘制实现擦除功能
解决Flutter背景图上绘图的擦除问题
核心思路是图层分离+混合模式:把绘制内容放在单独图层,擦除时使用BlendMode.clear清除绘制图层的像素,而非用白色覆盖背景。以下是具体实现步骤:
1. 修复并扩展DrawingPoints类
增加擦除标记字段,同时修正原代码中==运算符的逻辑错误:
class DrawingPoints { final Offset points; final Paint paint; final bool isEraser; const DrawingPoints({required this.points, required this.paint, this.isEraser = false}); @override bool operator ==(Object other) { if (identical(this, other)) return true; return other is DrawingPoints && other.points == points && other.paint.color == paint.color && other.isEraser == isEraser; } @override int get hashCode => points.hashCode ^ paint.hashCode ^ isEraser.hashCode; }
2. 主页面添加擦除模式与触摸事件处理
新增擦除状态变量,在触摸时生成对应模式的Paint(擦除模式启用BlendMode.clear),同时将背景图与绘制层分离:
// 新增擦除状态控制变量 bool _isErasing = false; // 页面核心组件 Column( children: [ // 切换擦除/绘制模式的按钮 IconButton( icon: Icon(_isErasing ? Icons.brush : Icons.delete_outline), onPressed: () => setState(() => _isErasing = !_isErasing), ), Expanded( child: Listener( onPointerDown: (details) { final renderBox = context.findRenderObject() as RenderBox?; if (renderBox == null) return; final localPos = renderBox.globalToLocal(details.position); final paint = _isErasing ? Paint() ..strokeCap = StrokeCap.round ..isAntiAlias = true ..blendMode = BlendMode.clear // 核心:清除绘制层像素 ..strokeWidth = strokeWidth * 2 // 放大擦除笔刷,适配儿童操作 : Paint() ..strokeCap = StrokeCap.round ..isAntiAlias = true ..color = widget.selectedColor.withOpacity(1.0) ..strokeWidth = strokeWidth; setState(() { points.add(DrawingPoints( points: localPos, paint: paint, isEraser: _isErasing, )); }); }, onPointerMove: (details) { final renderBox = context.findRenderObject() as RenderBox?; if (renderBox == null) return; final localPos = renderBox.globalToLocal(details.position); final paint = _isErasing ? Paint() ..strokeCap = StrokeCap.round ..isAntiAlias = true ..blendMode = BlendMode.clear ..strokeWidth = strokeWidth * 2 : Paint() ..strokeCap = StrokeCap.round ..isAntiAlias = true ..color = widget.selectedColor.withOpacity(1.0) ..strokeWidth = strokeWidth; setState(() { points.add(DrawingPoints( points: localPos, paint: paint, isEraser: _isErasing, )); }); }, onPointerUp: (details) => setState(() => points.add(null)), child: Stack( fit: StackFit.expand, children: [ // 替换为你的背景图片组件 Image.asset('assets/background.jpg', fit: BoxFit.cover), CustomPaint( size: Size.infinite, painter: DrawingPainter(pointsList: points), ), ], ), ), ), ], )
3. 优化CustomPainter绘制逻辑
使用离屏图层(saveLayer)隔离绘制内容,确保擦除操作仅影响绘制层,不干扰背景:
class DrawingPainter extends CustomPainter { final List<DrawingPoints?> pointsList; final List<Offset> offsetPoints = []; DrawingPainter({required this.pointsList}); @override void paint(Canvas canvas, Size size) { // 创建独立绘制图层,隔离背景与绘制内容 final rect = Offset.zero & size; canvas.saveLayer(rect, Paint()); for (int i = 0; i < pointsList.length - 1; i++) { if (pointsList[i] != null && pointsList[i + 1] != null) { canvas.drawLine( pointsList[i]!.points, pointsList[i + 1]!.points, pointsList[i]!.paint, ); } else if (pointsList[i] != null && pointsList[i + 1] == null) { offsetPoints ..clear() ..add(pointsList[i]!.points) ..add(Offset(pointsList[i]!.points.dx + 0.1, pointsList[i]!.points.dy + 0.1)); canvas.drawPoints(PointMode.points, offsetPoints, pointsList[i]!.paint); } } canvas.restore(); // 将绘制图层合并到主画布 } @override bool shouldRepaint(DrawingPainter oldDelegate) { return oldDelegate.pointsList != pointsList; } }
关键说明
BlendMode.clear:该模式会清除当前图层对应区域的像素,直接露出下方背景图,完美实现擦除效果,不会覆盖背景。- 离屏图层:通过
saveLayer将绘制内容与背景完全隔离,避免擦除操作影响背景,同时优化绘制性能。 - 儿童友好调整:放大擦除笔刷尺寸,降低操作难度。
内容的提问来源于stack exchange,提问作者Loïc Yabili
相关产品推荐
相关产品推荐

