351 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			Dart
		
	
	
			
		
		
	
	
			351 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			Dart
		
	
	
| import 'dart:io';
 | |
| 
 | |
| import 'package:flutter/material.dart';
 | |
| import 'package:flutter/rendering.dart';
 | |
| import 'dart:ui' as ui;
 | |
| import 'package:flutter/services.dart';
 | |
| 
 | |
| import 'package:path_provider/path_provider.dart';
 | |
| 
 | |
| class MineSignPage extends StatelessWidget {
 | |
|   const MineSignPage({super.key});
 | |
| 
 | |
|   @override
 | |
|   Widget build(BuildContext context) {
 | |
|     return Scaffold(
 | |
|       body: const SignatureConfirmPage(),
 | |
|     );
 | |
|   }
 | |
| }
 | |
| 
 | |
| class SignatureConfirmPage extends StatefulWidget {
 | |
|   const SignatureConfirmPage({super.key});
 | |
| 
 | |
|   @override
 | |
|   State<SignatureConfirmPage> createState() => _SignatureConfirmPageState();
 | |
| }
 | |
| 
 | |
| class _SignatureConfirmPageState extends State<SignatureConfirmPage> {
 | |
|   final GlobalKey _signatureKey = GlobalKey();
 | |
|   List<Offset?> _points = [];
 | |
|   bool _hasSignature = false;
 | |
|   File? fileN;
 | |
|   Uint8List? _postBytes;
 | |
|   late String imagepath = "";
 | |
| 
 | |
|   void _clearSignature() {
 | |
|     setState(() {
 | |
|       _points.clear();
 | |
|       _hasSignature = false;
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   void _confirmSignature() {
 | |
|     if (!_hasSignature) {
 | |
|       ScaffoldMessenger.of(context).showSnackBar(
 | |
|         const SnackBar(content: Text('请先签名')),
 | |
|       );
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     _saveSignPic();
 | |
| 
 | |
|     // // 保存签名逻辑
 | |
|     // ScaffoldMessenger.of(context).showSnackBar(
 | |
|     //   const SnackBar(content: Text('签名已确认')),
 | |
|     // );
 | |
|     //模拟保存后返回
 | |
|   }
 | |
| 
 | |
|   // 保存签名
 | |
|   void _saveSignPic() async {
 | |
|     RenderRepaintBoundary boundary = _signatureKey.currentContext!
 | |
|         .findRenderObject() as RenderRepaintBoundary;
 | |
|     var image = await boundary.toImage(pixelRatio: 1);
 | |
|     ByteData? byteData =
 | |
|     await image.toByteData(format: ui.ImageByteFormat.png);
 | |
| 
 | |
|     int timestamp = DateTime.now().millisecondsSinceEpoch;
 | |
| 
 | |
|     Directory dir = await getTemporaryDirectory();
 | |
|     // 在文件名中添加时间戳
 | |
|     String path = '${dir.path}/sign_$timestamp.png';
 | |
| 
 | |
|     File file2 = File(path);
 | |
|     // 检查文件是否存在
 | |
|     if (await file2.exists()) {
 | |
|       await file2.delete();
 | |
|     }
 | |
| 
 | |
|     var file = await File(path).create(recursive: true);
 | |
|     if (byteData != null) {
 | |
|       file.writeAsBytesSync(byteData.buffer.asInt8List(), flush: true);
 | |
| 
 | |
|       setState(() {
 | |
|         _postBytes = byteData.buffer.asUint8List();
 | |
|         fileN = file;
 | |
|         imagepath = file.path;
 | |
|         Future.delayed(const Duration(milliseconds: 500), () {
 | |
|           Navigator.pop(context, imagepath);
 | |
|         });
 | |
|       });
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   void initState() {
 | |
|     super.initState();
 | |
|     SystemChrome.setPreferredOrientations([
 | |
|       DeviceOrientation.landscapeRight,
 | |
|       DeviceOrientation.landscapeLeft,
 | |
|     ]);
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   void dispose() {
 | |
|     // 不要忘记重置方向设置,以避免影响其他页面或应用。
 | |
|     SystemChrome.setPreferredOrientations([
 | |
|       DeviceOrientation.portraitUp,
 | |
|       DeviceOrientation.portraitDown,
 | |
|       DeviceOrientation.landscapeLeft,
 | |
|       DeviceOrientation.landscapeRight,
 | |
|     ]);
 | |
|     super.dispose();
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   Widget build(BuildContext context) {
 | |
|     return Scaffold(
 | |
|       body: SafeArea(
 | |
|         child: Column(
 | |
|           children: [
 | |
|             // 标题区域
 | |
|             _buildTitleBar(),
 | |
|             // MyAppbar(title: "签字"),
 | |
| 
 | |
|             // 签字区域
 | |
|             Expanded(
 | |
|               child: _buildSignatureArea(),
 | |
|             ),
 | |
| 
 | |
|             // 按钮区域
 | |
|             _buildActionButtons(),
 | |
|           ],
 | |
|         ),
 | |
|       ),
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   Widget _buildTitleBar() {
 | |
|     return Padding(
 | |
|       padding: const EdgeInsets.symmetric(horizontal: 16),
 | |
|       child: Row(
 | |
|         mainAxisAlignment: MainAxisAlignment.spaceBetween,
 | |
|         children: [
 | |
|           IconButton(
 | |
|             icon: const Icon(Icons.arrow_back),
 | |
|             onPressed: () => Navigator.pop(context),
 | |
|           ),
 | |
|           const Text(
 | |
|             '签字',
 | |
|             style: TextStyle(
 | |
|               fontSize: 20,
 | |
|               fontWeight: FontWeight.bold,
 | |
|             ),
 | |
|           ),
 | |
|           const SizedBox(width: 48), // 占位保持标题居中
 | |
|         ],
 | |
|       ),
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   Widget _buildSignatureArea() {
 | |
|     return Padding(
 | |
|       padding: const EdgeInsets.symmetric(horizontal: 24),
 | |
|       child: Container(
 | |
|         decoration: BoxDecoration(
 | |
|           borderRadius: BorderRadius.circular(12),
 | |
|           border: Border.all(color: Colors.grey[300]!),
 | |
|         ),
 | |
|         child: Column(
 | |
|           children: [
 | |
| 
 | |
|             // 签字画布
 | |
|             Expanded(
 | |
|               child: GestureDetector(
 | |
|                 onPanStart: (details) {
 | |
|                   setState(() {
 | |
|                     _points.add(details.localPosition);
 | |
|                     _hasSignature = true;
 | |
|                   });
 | |
|                 },
 | |
|                 onPanUpdate: (details) {
 | |
|                   setState(() {
 | |
|                     _points.add(details.localPosition);
 | |
|                   });
 | |
|                 },
 | |
|                 onPanEnd: (details) {
 | |
|                   setState(() {
 | |
|                     _points.add(null);
 | |
|                   });
 | |
|                 },
 | |
|                 child: RepaintBoundary(
 | |
|                   key: _signatureKey,
 | |
|                   child: Container(
 | |
|                     // 给画布添加白色背景,不透明导出
 | |
|                     color: Colors.white,
 | |
|                     child: Stack(
 | |
|                       children: [
 | |
|                         // if (imagepath.length > 0)
 | |
|                         // Image.file(
 | |
|                         //   File(imagepath), // 显示选择的图片文件
 | |
|                         //   fit: BoxFit.contain, // 设置图片填充方式为完整显示,保持宽高比例
 | |
|                         // ),
 | |
| 
 | |
|                         // 背景横线
 | |
|                         _buildBackgroundLines(),
 | |
| 
 | |
|                         // 签名画布
 | |
|                         CustomPaint(
 | |
|                           painter: SignaturePainter(points: _points),
 | |
|                           size: Size.infinite,
 | |
|                         ),
 | |
| 
 | |
|                         // 提示文字
 | |
|                         if (!_hasSignature)
 | |
|                           const Center(
 | |
|                             child: Text(
 | |
|                               '请在此处签名',
 | |
|                               style: TextStyle(
 | |
|                                 fontSize: 16,
 | |
|                                 color: Colors.grey,
 | |
|                               ),
 | |
|                             ),
 | |
|                           ),
 | |
|                       ],
 | |
|                     ),
 | |
|                   ),
 | |
|                 ),
 | |
|               ),
 | |
|             ),
 | |
|           ],
 | |
|         ),
 | |
|       ),
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   Widget _buildBackgroundLines() {
 | |
|     return LayoutBuilder(
 | |
|       builder: (context, constraints) {
 | |
|         return CustomPaint(
 | |
|           painter: BackgroundLinesPainter(),
 | |
|           size: Size(constraints.maxWidth, constraints.maxHeight),
 | |
|         );
 | |
|       },
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   Widget _buildActionButtons() {
 | |
|     return Padding(
 | |
|       padding: const EdgeInsets.only(left: 24,right: 24,top: 10,bottom: 10),
 | |
|       child: Row(
 | |
|         mainAxisAlignment: MainAxisAlignment.spaceEvenly,
 | |
|         children: [
 | |
|           // 重签按钮
 | |
|           Expanded(
 | |
|             child: OutlinedButton(
 | |
|               onPressed: _clearSignature,
 | |
|               style: OutlinedButton.styleFrom(
 | |
|                 padding: const EdgeInsets.symmetric(vertical: 8),
 | |
|                 side: const BorderSide(color: Colors.blue),
 | |
|                 shape: RoundedRectangleBorder(
 | |
|                   borderRadius: BorderRadius.circular(8),
 | |
|                 ),
 | |
|               ),
 | |
|               child: const Text(
 | |
|                 '重签',
 | |
|                 style: TextStyle(
 | |
|                   fontSize: 16,
 | |
|                   color: Colors.blue,
 | |
|                   fontWeight: FontWeight.w500,
 | |
|                 ),
 | |
|               ),
 | |
|             ),
 | |
|           ),
 | |
| 
 | |
|           const SizedBox(width: 20),
 | |
| 
 | |
|           // 确定按钮
 | |
|           Expanded(
 | |
|             child: ElevatedButton(
 | |
|               onPressed: _confirmSignature,
 | |
|               style: ElevatedButton.styleFrom(
 | |
|                 backgroundColor: Colors.blue,
 | |
|                 padding: const EdgeInsets.symmetric(vertical: 8),
 | |
|                 shape: RoundedRectangleBorder(
 | |
|                   borderRadius: BorderRadius.circular(8),
 | |
|                 ),
 | |
|               ),
 | |
|               child: const Text(
 | |
|                 '确定',
 | |
|                 style: TextStyle(
 | |
|                   fontSize: 16,
 | |
|                   color: Colors.white,
 | |
|                   fontWeight: FontWeight.w500,
 | |
|                 ),
 | |
|               ),
 | |
|             ),
 | |
|           ),
 | |
|         ],
 | |
|       ),
 | |
|     );
 | |
|   }
 | |
| }
 | |
| 
 | |
| // 签名绘制器
 | |
| class SignaturePainter extends CustomPainter {
 | |
|   final List<Offset?> points;
 | |
| 
 | |
|   SignaturePainter({required this.points});
 | |
| 
 | |
|   @override
 | |
|   void paint(Canvas canvas, Size size) {
 | |
|     Paint paint = Paint()
 | |
|       ..color = Colors.black
 | |
|       ..strokeCap = StrokeCap.round
 | |
|       ..strokeWidth = 3.0;
 | |
| 
 | |
|     for (int i = 0; i < points.length - 1; i++) {
 | |
|       if (points[i] != null && points[i + 1] != null) {
 | |
|         canvas.drawLine(points[i]!, points[i + 1]!, paint);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   bool shouldRepaint(SignaturePainter oldDelegate) =>
 | |
|       oldDelegate.points != points;
 | |
| }
 | |
| 
 | |
| // 背景横线绘制器
 | |
| class BackgroundLinesPainter extends CustomPainter {
 | |
|   @override
 | |
|   void paint(Canvas canvas, Size size) {
 | |
|     Paint paint = Paint()
 | |
|     // ..color = Colors.grey[200]!
 | |
|       ..color = Color.from(alpha: 0, red: 0, green: 0, blue: 0)!
 | |
|       ..strokeWidth = 0;
 | |
| 
 | |
|     // 绘制横线
 | |
|     for (double y = 40; y < size.height; y += 40) {
 | |
|       canvas.drawLine(
 | |
|         Offset(0, y),
 | |
|         Offset(size.width, y),
 | |
|         paint,
 | |
|       );
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   bool shouldRepaint(CustomPainter oldDelegate) => false;
 | |
| } |