perf(app): 手写引擎性能优化 — 双层架构 + 光栅化缓存 + O(1) 点缓冲
性能优化: - 新建 StrokeRasterCache: 已完成笔画光栅化为 ui.Image 合成位图 - 新建 CachedStrokesPainter: 每帧仅 drawImage,O(1) 开销 - 新建 ActiveStrokePainter: 仅渲染当前笔画,isComplete: false - _currentPoints 改为可变缓冲区 + ValueNotifier 驱动,消除 O(N²) 列表拷贝 - 双层 Stack 架构: 已缓存层(不随指针移动重绘) + 实时层(仅当前笔画) Bug 修复: - 橡皮擦 saveLayer 合成: BlendMode.dstOut 在离屏缓冲区中正确工作 - pointsToOutline 新增 isComplete 参数: 实时绘制传 false,完成笔画传 true - 模式切换不再销毁 HandwritingCanvas: IgnorePointer 替代 if/else 分支 架构改进: - 提取 createPaintForStroke() 为顶层函数,供缓存和 Painter 共用 - 移除旧 StrokePainter 类,由双层 Painter 替代 - LayoutBuilder 跟踪画布尺寸,尺寸变化时缓存自动失效 文件变更: - 新建 stroke_cache.dart (~210 行) - 新建 cached_strokes_painter.dart (~35 行) - 新建 active_stroke_painter.dart (~70 行) - 重写 handwriting_canvas.dart (~300 行) - 重构 stroke_renderer.dart (~185 行, 移除旧 Painter) - 修改 editor_page.dart (IgnorePointer 模式切换) 验证: flutter analyze 0 error
This commit is contained in:
72
app/lib/features/editor/widgets/active_stroke_painter.dart
Normal file
72
app/lib/features/editor/widgets/active_stroke_painter.dart
Normal file
@@ -0,0 +1,72 @@
|
||||
// 当前笔画实时 Painter — 绘制正在绘制中的笔画
|
||||
//
|
||||
// 接收可变点缓冲区的直接引用 + 版本号驱动重绘。
|
||||
// 每帧仅计算当前笔画的轮廓路径,不影响已完成笔画层。
|
||||
// isComplete: false 让 perfect_freehand 对实时笔尖做端点平滑。
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'stroke_model.dart';
|
||||
import 'stroke_renderer.dart';
|
||||
|
||||
/// 当前笔画实时 Painter
|
||||
///
|
||||
/// 由 ListenableBuilder 包裹,监听 ValueNotifier<int> _strokeVersion。
|
||||
/// 每次 pointer move 递增 version,触发此 Painter 重绘。
|
||||
/// 仅渲染当前正在绘制的笔画,已完成笔画由 CachedStrokesPainter 处理。
|
||||
class ActiveStrokePainter extends CustomPainter {
|
||||
/// 当前笔画的采样点(直接引用可变缓冲区,不拷贝)
|
||||
final List<StrokePoint> points;
|
||||
|
||||
/// 画笔类型
|
||||
final BrushType brushType;
|
||||
|
||||
/// 画笔颜色(CSS 十六进制)
|
||||
final String color;
|
||||
|
||||
/// 画笔宽度
|
||||
final double width;
|
||||
|
||||
/// 版本号,每次 pointer move 递增
|
||||
final int version;
|
||||
|
||||
ActiveStrokePainter({
|
||||
required this.points,
|
||||
required this.brushType,
|
||||
required this.color,
|
||||
required this.width,
|
||||
required this.version,
|
||||
});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
if (points.length < 2) return;
|
||||
|
||||
// isComplete: false — 实时笔尖不做端点封口,视觉更自然
|
||||
final outlinePoints = pointsToOutline(
|
||||
points,
|
||||
brushType,
|
||||
width,
|
||||
isComplete: false,
|
||||
);
|
||||
if (outlinePoints.isEmpty) return;
|
||||
|
||||
final path = buildStrokePath(outlinePoints);
|
||||
|
||||
// 构造临时 Stroke 用于获取 Paint
|
||||
final stroke = Stroke(
|
||||
id: '__active__',
|
||||
points: points,
|
||||
brushType: brushType,
|
||||
color: color,
|
||||
width: width,
|
||||
);
|
||||
final paint = createPaintForStroke(stroke);
|
||||
canvas.drawPath(path, paint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant ActiveStrokePainter oldDelegate) {
|
||||
return oldDelegate.version != version;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user