feat(homework_review): 优化批注绘制与坐标换算,提升交互流畅度
- 批注绘制改为 Path 聚合一次性绘制 - 移除高频 print,开启 CustomPaint isComplex/willChange - 无变换时跳过逆矩阵 - 回滚不必要的 RepaintBoundary/低过滤 - 调整 Path 判空判断写法
This commit is contained in:
parent
1fc54d7526
commit
c7afafc3aa
|
|
@ -518,7 +518,7 @@ class QuestionImageView extends HookWidget with EventBusMixin<BottomOperationBar
|
||||||
if (activePointers.value > 0) {
|
if (activePointers.value > 0) {
|
||||||
activePointers.value = activePointers.value - 1;
|
activePointers.value = activePointers.value - 1;
|
||||||
}
|
}
|
||||||
print("---进入:onPointerUp ${activePointers.value}");
|
// 移除高频日志,避免导致掉帧
|
||||||
timerRef.value?.cancel();
|
timerRef.value?.cancel();
|
||||||
if (!annotationState.pen.value) return;
|
if (!annotationState.pen.value) return;
|
||||||
|
|
||||||
|
|
@ -526,7 +526,7 @@ class QuestionImageView extends HookWidget with EventBusMixin<BottomOperationBar
|
||||||
sateData.handwritings = vnHandWritings.value; // 添加笔迹数据
|
sateData.handwritings = vnHandWritings.value; // 添加笔迹数据
|
||||||
},
|
},
|
||||||
onPointerMove: (PointerMoveEvent event) {
|
onPointerMove: (PointerMoveEvent event) {
|
||||||
print("进入:onPointerMove ${activePointers.value}");
|
// 移除高频日志,避免导致掉帧
|
||||||
if (activePointers.value != 1) return;
|
if (activePointers.value != 1) return;
|
||||||
startTimerForHandwriting(vnHandWritings);
|
startTimerForHandwriting(vnHandWritings);
|
||||||
if (!annotationState.pen.value) return;
|
if (!annotationState.pen.value) return;
|
||||||
|
|
@ -555,9 +555,24 @@ class QuestionImageView extends HookWidget with EventBusMixin<BottomOperationBar
|
||||||
// (dy / theScale) - (max(0, imageHeightOffsetStart) / theScale) + ((sateData.zoomOffset?.dy.abs() ?? 0) / theScale),
|
// (dy / theScale) - (max(0, imageHeightOffsetStart) / theScale) + ((sateData.zoomOffset?.dy.abs() ?? 0) / theScale),
|
||||||
|
|
||||||
// 屏幕坐标 -> 图片坐标(通过逆矩阵)
|
// 屏幕坐标 -> 图片坐标(通过逆矩阵)
|
||||||
final inv = Matrix4.inverted(matrix);
|
// final inv = Matrix4.inverted(matrix);
|
||||||
final v = inv.transform3(Vector3(localPosition.dx, localPosition.dy, 0));
|
// final v = inv.transform3(Vector3(localPosition.dx, localPosition.dy, 0));
|
||||||
localPosition = Offset(v.x, v.y);
|
// localPosition = Offset(v.x, v.y);
|
||||||
|
|
||||||
|
/// 屏幕坐标 -> 图片坐标(通过逆矩阵)。当无缩放/平移时走快速路径,避免不必要求逆
|
||||||
|
/// 它们是 Matrix4 内部 4x4 矩阵的平移分量(列主序存储)。
|
||||||
|
/// storage[12] 是平移的 x 分量,storage[13] 是平移的 y 分量(storage[14] 则是 z)。
|
||||||
|
/// 因为 Matrix4 采用列主序,索引计算为 index = col * 4 + row,平移在第 4 列的前 3 行,所以是 12、13、14。
|
||||||
|
/// 用途:当 theScale == 1.0 && storage[12] == 0.0 && storage[13] == 0.0 时,可判定无缩放/平移,走快速路径跳过逆矩阵。
|
||||||
|
final double tx = matrix.storage[12];
|
||||||
|
final double ty = matrix.storage[13];
|
||||||
|
if (theScale == 1.0 && tx == 0.0 && ty == 0.0) {
|
||||||
|
// identity 变换,局部坐标无需转换
|
||||||
|
} else {
|
||||||
|
final inv = Matrix4.inverted(matrix);
|
||||||
|
final v = inv.transform3(Vector3(localPosition.dx, localPosition.dy, 0));
|
||||||
|
localPosition = Offset(v.x, v.y);
|
||||||
|
}
|
||||||
|
|
||||||
// 在图片坐标系做边界校验(更直观):0..maxWidth、0..actualHeight
|
// 在图片坐标系做边界校验(更直观):0..maxWidth、0..actualHeight
|
||||||
if (localPosition.dy < 0 || localPosition.dy > actualHeight) return;
|
if (localPosition.dy < 0 || localPosition.dy > actualHeight) return;
|
||||||
|
|
@ -625,7 +640,8 @@ class QuestionImageView extends HookWidget with EventBusMixin<BottomOperationBar
|
||||||
RepaintBoundary(
|
RepaintBoundary(
|
||||||
key: logic.pictureOverviewKey,
|
key: logic.pictureOverviewKey,
|
||||||
child: CustomPaint(
|
child: CustomPaint(
|
||||||
// isComplex: true,
|
isComplex: true,
|
||||||
|
willChange: true,
|
||||||
size: Size(maxWidth, actualHeight),
|
size: Size(maxWidth, actualHeight),
|
||||||
foregroundPainter: DrawingPainter(ctrl: vnHandWritings),
|
foregroundPainter: DrawingPainter(ctrl: vnHandWritings),
|
||||||
// child: $TheCachedNetworkImage(
|
// child: $TheCachedNetworkImage(
|
||||||
|
|
@ -658,19 +674,40 @@ class DrawingPainter extends CustomPainter {
|
||||||
final Paint paintBrush = Paint()
|
final Paint paintBrush = Paint()
|
||||||
..color = Colors.red
|
..color = Colors.red
|
||||||
..strokeCap = StrokeCap.round
|
..strokeCap = StrokeCap.round
|
||||||
..strokeWidth = 0.7.sp;
|
..strokeJoin = StrokeJoin.round
|
||||||
|
..isAntiAlias = true
|
||||||
|
..strokeWidth = 0.7.sp
|
||||||
|
..style = PaintingStyle.stroke;
|
||||||
DrawingPainter({required this.ctrl}) : super(repaint: ctrl);
|
DrawingPainter({required this.ctrl}) : super(repaint: ctrl);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void paint(Canvas canvas, Size size) {
|
void paint(Canvas canvas, Size size) {
|
||||||
var points = ctrl.value;
|
final List<dynamic> points = ctrl.value;
|
||||||
var pointsLength = points.length;
|
if (points.isEmpty) return;
|
||||||
for (int i = 0; i < pointsLength; i++) {
|
|
||||||
Offset? offsetData = points[i];
|
Path path = Path();
|
||||||
Offset? nextOffsetData = pointsLength - 1 == i ? null : points[i + 1];
|
Offset? previous;
|
||||||
if (offsetData != null && nextOffsetData != null) {
|
for (int i = 0; i < points.length; i++) {
|
||||||
canvas.drawLine(offsetData, nextOffsetData, paintBrush);
|
final Offset? current = points[i] as Offset?;
|
||||||
|
if (current == null) {
|
||||||
|
// 提交当前子路径
|
||||||
|
if (path.computeMetrics().isNotEmpty) {
|
||||||
|
canvas.drawPath(path, paintBrush);
|
||||||
|
}
|
||||||
|
path = Path();
|
||||||
|
previous = null;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
if (previous == null) {
|
||||||
|
path.moveTo(current.dx, current.dy);
|
||||||
|
} else {
|
||||||
|
path.lineTo(current.dx, current.dy);
|
||||||
|
}
|
||||||
|
previous = current;
|
||||||
|
}
|
||||||
|
// 绘制最后的子路径
|
||||||
|
if (path.computeMetrics().isNotEmpty) {
|
||||||
|
canvas.drawPath(path, paintBrush);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue