Making.School.Asignment.app/making_school_asignment_app/lib/main_offest.dart

301 lines
9.9 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:making_school_asignment_app/common/utils/gesture_recognition/ramer_douglas_peucker.dart';
import 'package:making_school_asignment_app/common/utils/gesture_recognition/shape_recognizer.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return ScreenUtilInit(
designSize: const Size(375, 812),
builder: () => MaterialApp(
title: '手势识别实验',
theme: ThemeData(primarySwatch: Colors.blue, useMaterial3: true),
home: const GestureCanvasPage(),
),
);
}
}
class GestureCanvasPage extends StatefulWidget {
const GestureCanvasPage({super.key});
@override
State<GestureCanvasPage> createState() => _GestureCanvasPageState();
}
class _GestureCanvasPageState extends State<GestureCanvasPage> {
final List<List<Offset>> _allStrokes = [];
final _currentStrokeNotifier = ValueNotifier<List<Offset>>([]);
ui.Image? _cachedImage;
GestureType _recognizedGesture = GestureType.none;
late ShapeRecognizer _recognizer;
// 用于控制是否显示可视化特征点的状态
bool _showSimplifiedPoints = true;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
// 在这里初始化识别器,可以方便地调整参数
_recognizer = ShapeRecognizer(
rdpEpsilon: 8.r, // 你可以集中在这里调整这个值
/// 勾的最小角度调小一些,避免漏识别
checkAngleMin: 30.0,
/// 勾的最大角度调大一些,避免误识别
checkAngleMax: 150.0,
crossAngleMin: 30.0,
crossAngleMax: 160.0,
minIntersectionDistance: 0.1,
checkLengthRatio: 1.1,
// [核心修改] 放宽对斜线斜率的限制
slashSlopeMin: 0.2, // 这个值允许用户绘制相当平缓的斜线角度低至约11度这覆盖了绝大多数的书写习惯同时这个斜率也足以和基本水平的线条区分开。
slashSlopeMax:
5.0, // 这个值允许用户绘制非常陡峭的斜线角度高达约79度。对于比这更陡峭的线条它们已经非常接近垂直线而我们之前优化过的 _isSlash 函数中有专门处理垂直线的逻辑 (deltaX < 1e-6),所以这个组合非常稳健,几乎可以覆盖所有非水平的直线。
);
}
});
}
Future<void> _updateCachedImage() async {
final recorder = ui.PictureRecorder();
final canvas = Canvas(recorder);
final size = context.size!;
_GestureCachePainter(allStrokes: _allStrokes).paint(canvas, size);
final picture = recorder.endRecording();
final newImage = await picture.toImage(size.width.toInt(), size.height.toInt());
if (mounted) {
setState(() {
_cachedImage = newImage;
});
}
}
void _onPanStart(DragStartDetails details) {
_currentStrokeNotifier.value = [details.localPosition];
}
void _onPanUpdate(DragUpdateDetails details) {
_currentStrokeNotifier.value = List.from(_currentStrokeNotifier.value)..add(details.localPosition);
}
void _onPanEnd(DragEndDetails details) {
if (_currentStrokeNotifier.value.isNotEmpty) {
_allStrokes.add(List.from(_currentStrokeNotifier.value));
_currentStrokeNotifier.value = [];
_updateCachedImage().then((_) {
if (mounted) {
setState(() {
_recognizedGesture = _recognizer.recognize(_allStrokes);
print('识别到的手势: $_recognizedGesture');
});
}
});
}
}
void _clearCanvas() {
setState(() {
_allStrokes.clear();
_recognizedGesture = GestureType.none;
_cachedImage = null;
});
_currentStrokeNotifier.value = [];
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('手势识别 (可视化调试)'),
actions: [
// 在AppBar中添加一个信息提示
Padding(
padding: const EdgeInsets.only(right: 16.0),
child: Icon(
Icons.science_outlined,
color: _showSimplifiedPoints ? Colors.green : Colors.grey,
),
)
],
),
body: Stack(
children: [
GestureDetector(
onPanStart: _onPanStart,
onPanUpdate: _onPanUpdate,
onPanEnd: _onPanEnd,
child: ValueListenableBuilder<List<Offset>>(
valueListenable: _currentStrokeNotifier,
builder: (context, currentStroke, child) {
return CustomPaint(
painter: _GestureCanvasPainter(
cachedImage: _cachedImage,
currentStroke: currentStroke,
// [修改] 将调试所需的数据传入Painter
allStrokes: _allStrokes,
rdpEpsilon: _recognizer.rdpEpsilon,
showSimplifiedPoints: _showSimplifiedPoints,
),
size: Size.infinite,
);
},
),
),
Align(
alignment: Alignment.topLeft,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
'识别结果: ${_recognizedGesture.toString().split('.').last}\nEpsilon: ${_recognizer.rdpEpsilon.toStringAsFixed(2)}',
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.blue),
),
),
),
],
),
// [修改] 使用Column包裹多个FAB
floatingActionButton: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
FloatingActionButton(
onPressed: _clearCanvas,
tooltip: '清空画布',
child: const Icon(Icons.clear),
),
const SizedBox(height: 10),
// 用于切换可视化模式的按钮
FloatingActionButton(
onPressed: () {
setState(() {
_showSimplifiedPoints = !_showSimplifiedPoints;
});
},
tooltip: '切换特征点显示',
backgroundColor: _showSimplifiedPoints ? Colors.green : Colors.grey[400],
child: const Icon(Icons.science_outlined),
),
],
),
);
}
}
class _GestureCanvasPainter extends CustomPainter {
final ui.Image? cachedImage;
final List<Offset> currentStroke;
final Paint _paint;
// [修改] 接收新增的调试参数
final List<List<Offset>> allStrokes;
final double rdpEpsilon;
final bool showSimplifiedPoints;
final Paint _debugPaint; // 专门用于绘制特征点的画笔
_GestureCanvasPainter({
required this.cachedImage,
required this.currentStroke,
required this.allStrokes,
required this.rdpEpsilon,
required this.showSimplifiedPoints,
}) : _paint = Paint()
..color = Colors.black
..strokeCap = StrokeCap.round
..strokeWidth = 5.0
..style = PaintingStyle.stroke,
// 初始化调试画笔
_debugPaint = Paint()
..color = Colors.red
..strokeCap = StrokeCap.round
..strokeWidth = 8.0;
@override
void paint(Canvas canvas, Size size) {
// 步骤1: 绘制缓存的图片 (非常快)
if (cachedImage != null) {
canvas.drawImage(cachedImage!, Offset.zero, Paint());
}
// 步骤2: 绘制当前正在画的笔画
if (currentStroke.length > 1) {
final path = Path();
path.moveTo(currentStroke.first.dx, currentStroke.first.dy);
for (int i = 1; i < currentStroke.length; i++) {
path.lineTo(currentStroke[i].dx, currentStroke[i].dy);
}
canvas.drawPath(path, _paint);
}
// 步骤3: 如果开启了可视化,则绘制简化后的特征点
if (showSimplifiedPoints) {
// 为所有已完成的笔画绘制特征点
for (final stroke in allStrokes) {
if (stroke.length > 1) {
final simplifiedPoints = RamerDouglasPeucker.simplify(stroke, rdpEpsilon);
canvas.drawPoints(ui.PointMode.points, simplifiedPoints, _debugPaint);
}
}
// 为当前正在绘制的笔画也实时显示特征点
if (currentStroke.length > 1) {
final simplifiedPoints = RamerDouglasPeucker.simplify(currentStroke, rdpEpsilon);
canvas.drawPoints(ui.PointMode.points, simplifiedPoints, _debugPaint);
}
}
}
@override
bool shouldRepaint(covariant _GestureCanvasPainter oldDelegate) {
// [修改] 当笔画、总笔画数或可视化开关变化时,都需要重绘
return oldDelegate.currentStroke != currentStroke ||
oldDelegate.showSimplifiedPoints != showSimplifiedPoints ||
oldDelegate.allStrokes.length != allStrokes.length;
}
}
// 缓存绘制器保持不变
class _GestureCachePainter extends CustomPainter {
final List<List<Offset>> allStrokes;
final Paint _paint;
_GestureCachePainter({required this.allStrokes})
: _paint = Paint()
..color = Colors.black
..strokeCap = StrokeCap.round
..strokeWidth = 5.0
..style = PaintingStyle.stroke;
@override
void paint(Canvas canvas, Size size) {
for (final stroke in allStrokes) {
if (stroke.length > 1) {
final path = Path();
path.moveTo(stroke.first.dx, stroke.first.dy);
for (int i = 1; i < stroke.length; i++) {
path.lineTo(stroke[i].dx, stroke[i].dy);
}
canvas.drawPath(path, _paint);
}
}
}
@override
bool shouldRepaint(covariant _GestureCachePainter oldDelegate) {
return oldDelegate.allStrokes != allStrokes;
}
}