Flutter可自由变换画布绘图应用实时显示问题求助
Flutter绘图应用实时轨迹显示问题
我正在开发一款Flutter绘图应用,需求如下:
- 画布支持双指触发旋转、缩放和平移操作
- 单指在画布滑动时执行绘图
目前已实现带图片的画布自由变换功能,但绘图时仅在抬手后才显示轨迹,希望实现单指绘图过程中实时显示轨迹。试过双Widget分别处理当前/历史路径的方案,但担心频繁状态重建不想使用;尝试过在历史路径末尾添加当前轨迹导致卡顿,canvas.save/restore方法也无效,求其他解决方案。
现有代码
Widget代码
import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:label_up_study/models/sketch.dart'; class TestScreen1 extends StatefulWidget { const TestScreen1({super.key}); @override State<TestScreen1> createState() => _TestScreen1State(); } class _TestScreen1State extends State<TestScreen1> { Offset _offset = Offset.zero; late Offset _startOffset; late Offset _previousOffset; double _scale = 0.75; late double _previousScale; double _rotation = 0; Sketch _sketches = Sketch(points: []); List<Sketch> _allSketches = []; List<int> _pointerCounts = []; @override void initState() { WidgetsBinding.instance.addPostFrameCallback((timeStamp) {}); super.initState(); } @override Widget build(BuildContext context) { return Scaffold( body: GestureDetector( onScaleStart: (ScaleStartDetails details) { _pointerCounts.clear(); _pointerCounts.add(details.pointerCount); if (details.pointerCount == 2) { _startOffset = details.focalPoint; _previousOffset = _offset; _previousScale = _scale; } }, onScaleUpdate: (ScaleUpdateDetails details) { _pointerCounts.add(details.pointerCount); if (details.pointerCount == 2) { setState(() { _rotation = details.rotation; _scale = _previousScale * details.scale; final Offset normalizedOffset = (_startOffset - _previousOffset) / _previousScale; _offset = details.focalPoint - normalizedOffset * _scale; }); } }, child: Stack( alignment: Alignment.center, children: [ Container(color: Colors.grey), Transform.translate( offset: _offset, child: Transform.rotate( angle: _rotation, child: Transform.scale( scale: _scale, child: RepaintBoundary( child: Listener( onPointerDown: (pointEvent) { if (_pointerCounts.where((element) => element == 1).toList().length > 5){ setState(() { _sketches.points.add(pointEvent.localPosition); }); } }, onPointerMove: (pointEvent) { if (_pointerCounts.where((element) => element == 1).toList().length > 5){ setState(() { _sketches.points.add(pointEvent.localPosition); }); } }, onPointerUp: (pointEvent) { if (_pointerCounts.where((element) => element == 1).toList().length > 5){ setState(() { _allSketches = List<Sketch>.from(_allSketches)..add(_sketches!); _sketches = Sketch(points: []); }); } }, child: CustomPaint( foregroundPainter: MyCustomPainter( allSketches: _allSketches, currentSketches: _sketches, ), child: Image.asset('asset/img/hydrocolloid_dressing.jpg'), ), ), ), ), ), ), ], ), ), ); } } class MyCustomPainter extends CustomPainter { final List<Sketch> allSketches; final Sketch currentSketches; MyCustomPainter({required this.allSketches, required this.currentSketches}); @override void paint(Canvas canvas, Size size) { Paint paint = Paint()..style = PaintingStyle.fill; for(int i=0; i < allSketches.length; i++){ final points = allSketches[i].points; if(points.isEmpty){ return; } final allPath = Path(); allPath.moveTo(points.first.dx, points.first.dy); for (int j=1; j < points.length -1 ; ++j){ final p0 = points[j]; final p1 = points[j+1]; allPath.quadraticBezierTo(p0.dx, p0.dy, (p0.dx + p1.dx)/2, (p0.dy + p1.dy)/2); } canvas.drawPath(allPath, paint); } } @override bool shouldRepaint(MyCustomPainter delegate) { return true; } }
Sketch类代码
import 'package:flutter/material.dart'; class Sketch { final List<Offset> points; Sketch({required this.points}); }
解决方案
核心问题是CustomPainter未绘制当前实时轨迹,且手势判断逻辑冗余导致触发异常,以下是针对性修复:
1. 修复CustomPainter,添加实时轨迹绘制
修改MyCustomPainter的paint方法,单独处理当前正在绘制的路径,同时调整画笔样式为线条(原代码用填充会导致轨迹异常):
@override void paint(Canvas canvas, Size size) { Paint paint = Paint() ..style = PaintingStyle.stroke ..strokeWidth = 2.0 ..color = Colors.black; // 绘制历史路径 for (Sketch sketch in allSketches) { final points = sketch.points; if (points.length < 2) continue; final path = Path(); path.moveTo(points.first.dx, points.first.dy); for (int j = 1; j < points.length - 1; j++) { final p0 = points[j]; final p1 = points[j + 1]; path.quadraticBezierTo( p0.dx, p0.dy, (p0.dx + p1.dx) / 2, (p0.dy + p1.dy) / 2, ); } canvas.drawPath(path, paint); } // 绘制当前实时轨迹 final currentPoints = currentSketches.points; if (currentPoints.length >= 2) { final currentPath = Path(); currentPath.moveTo(currentPoints.first.dx, currentPoints.first.dy); for (int j = 1; j < currentPoints.length - 1; j++) { final p0 = currentPoints[j]; final p1 = currentPoints[j + 1]; currentPath.quadraticBezierTo( p0.dx, p0.dy, (p0.dx + p1.dx) / 2, (p0.dy + p1.dy) / 2, ); } canvas.drawPath(currentPath, paint); } }
2. 简化手势判断逻辑
原代码的指针数量统计逻辑冗余,直接通过当前指针ID判断单指操作:
Listener( onPointerDown: (pointEvent) { // 仅单指触发绘图 if (pointEvent.pointer == 1) { setState(() { _sketches.points.add(pointEvent.localPosition); }); } }, onPointerMove: (pointEvent) { if (pointEvent.pointer == 1) { setState(() { _sketches.points.add(pointEvent.localPosition); }); } }, onPointerUp: (pointEvent) { if (pointEvent.pointer == 1 && _sketches.points.isNotEmpty) { setState(() { _allSketches.add(_sketches); _sketches = Sketch(points: []); }); } }, // 子组件代码不变 )
3. 优化重绘效率
修改shouldRepaint逻辑,仅当数据变化时才重绘,减少不必要的状态重建:
import 'package:flutter/foundation.dart'; // 导入listEquals @override bool shouldRepaint(MyCustomPainter oldDelegate) { bool sketchesChanged = !listEquals(allSketches, oldDelegate.allSketches); bool currentChanged = !listEquals(currentSketches.points, oldDelegate.currentSketches.points); return sketchesChanged || currentChanged; }
内容的提问来源于stack exchange,提问作者jungeyonoh
相关产品推荐
相关产品推荐

