样式调整
This commit is contained in:
@@ -1,285 +1,82 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:ln_jq_app/common/styles/theme.dart';
|
||||
import 'package:mobile_scanner/mobile_scanner.dart';
|
||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
||||
|
||||
import 'controller.dart';
|
||||
|
||||
class QrCodePage extends GetView<QrCodeController> {
|
||||
const QrCodePage({super.key});
|
||||
const QrCodePage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Get.put(QrCodeController());
|
||||
return Scaffold(
|
||||
extendBodyBehindAppBar: true,
|
||||
appBar: AppBar(
|
||||
title: const Text('扫码', style: TextStyle(color: Colors.white)),
|
||||
centerTitle: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios, color: Colors.white),
|
||||
onPressed: () => Get.back(),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: controller.requestPhotoPermission,
|
||||
child: const Text('相册', style: TextStyle(color: Colors.white, fontSize: 16)),
|
||||
return GetBuilder<QrCodeController>(
|
||||
init: QrCodeController(),
|
||||
id: 'qrcode',
|
||||
builder: (controller) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('绑定车辆'),
|
||||
centerTitle: true,
|
||||
elevation: 0,
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Obx(() { // 1. 使用 Obx 包裹整个 body
|
||||
// 根据权限状态来决定显示什么
|
||||
if (controller.hasPermission.value) {
|
||||
// 如果有权限,显示扫描器
|
||||
return _buildScannerView(context);
|
||||
} else {
|
||||
// 如果没有权限,显示引导界面
|
||||
return _buildPermissionDeniedView();
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
Widget _buildScannerView(BuildContext context){
|
||||
if (!controller.animationController.isAnimating) {
|
||||
controller.animationController.repeat(reverse: false);
|
||||
}
|
||||
return Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
// 使用 MobileScanner 作为扫描视图
|
||||
MobileScanner(
|
||||
controller: controller.scannerController,
|
||||
onDetect: controller.onDetect,
|
||||
// 您可以自定义扫描框的样式
|
||||
scanWindow: Rect.fromCenter(
|
||||
center: Offset(
|
||||
MediaQuery.of(context).size.width / 2,
|
||||
MediaQuery.of(context).size.height / 2 - 50,
|
||||
),
|
||||
width: 250,
|
||||
height: 250,
|
||||
),
|
||||
),
|
||||
// 扫描动画和覆盖层
|
||||
_buildScannerOverlay(context),
|
||||
// 底部的功能按钮
|
||||
Positioned(bottom: 80, left: 0, right: 0, child: _buildActionButtons()),
|
||||
],
|
||||
);
|
||||
}
|
||||
body: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
child: SingleChildScrollView(child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const SizedBox(height: 24),
|
||||
Icon(
|
||||
Icons.directions_car_rounded,
|
||||
size: 100,
|
||||
color: Theme.of(context).primaryColor.withOpacity(0.1),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
const Text(
|
||||
"请选择绑定方式",
|
||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Text(
|
||||
"您可以拍摄照片自动识别,\n或手动输入车牌号进行绑定。",
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 14, color: Colors.grey),
|
||||
),
|
||||
const SizedBox(height: 60),
|
||||
|
||||
Widget _buildPermissionDeniedView() {
|
||||
// 确保动画是停止的
|
||||
if (controller.animationController.isAnimating) {
|
||||
controller.animationController.stop();
|
||||
}
|
||||
|
||||
return Container(
|
||||
color: Colors.black,
|
||||
alignment: Alignment.center,
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.no_photography, color: Colors.white70, size: 64),
|
||||
const SizedBox(height: 16),
|
||||
const Text(
|
||||
'需要相机权限',
|
||||
style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Text(
|
||||
'请授予相机权限以使用扫码功能。',
|
||||
style: TextStyle(color: Colors.white70, fontSize: 14),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
ElevatedButton(
|
||||
onPressed: controller.requestPermission, // 点击按钮重新请求权限
|
||||
child: const Text('授予权限'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 构建扫描区域的覆盖层和动画
|
||||
Widget _buildScannerOverlay(BuildContext context) {
|
||||
// 模拟扫描框的位置和大小
|
||||
const double scanAreaSize = 250.0;
|
||||
return Stack(
|
||||
children: [
|
||||
// 半透明的覆盖层
|
||||
ColorFiltered(
|
||||
colorFilter: ColorFilter.mode(Colors.black.withOpacity(0.5), BlendMode.srcOut),
|
||||
child: Stack(
|
||||
children: [
|
||||
Container(decoration: const BoxDecoration(color: Colors.transparent)),
|
||||
Align(
|
||||
alignment: Alignment.center,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(bottom: 100), // 微调位置
|
||||
width: scanAreaSize,
|
||||
height: scanAreaSize,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
// 拍照识别按钮
|
||||
ElevatedButton.icon(
|
||||
onPressed: controller.takePhotoAndRecognize,
|
||||
icon: const Icon(Icons.camera_alt_rounded),
|
||||
label: const Text("拍照识别车牌"),
|
||||
style: ElevatedButton.styleFrom(
|
||||
minimumSize: const Size(double.infinity, 56),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(28)),
|
||||
textStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// 扫描动画
|
||||
Align(
|
||||
alignment: Alignment.center,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(bottom: 100),
|
||||
width: scanAreaSize,
|
||||
height: scanAreaSize,
|
||||
child: AnimatedBuilder(
|
||||
animation: controller.scanAnimation,
|
||||
builder: (context, child) {
|
||||
return CustomPaint(
|
||||
painter: ScannerAnimationPainter(
|
||||
controller.scanAnimation.value,
|
||||
AppTheme.themeColor,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
const SizedBox(height: 20),
|
||||
|
||||
/// 构建底部的功能按钮(闪光灯、相册)
|
||||
Widget _buildActionButtons() {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text(
|
||||
'将二维码/条形码放入框内,即可自动扫描',
|
||||
style: TextStyle(color: Colors.white, fontSize: 14),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
// 闪光灯按钮
|
||||
_buildIconButton(
|
||||
onPressed: controller.toggleFlash,
|
||||
//闪光灯状态的变化
|
||||
child: Obx(
|
||||
() => IconButton(
|
||||
icon: Icon(
|
||||
controller.isFlashOn.value ? Icons.flash_on : Icons.flash_off,
|
||||
color: Colors.white,
|
||||
size: 28,
|
||||
),
|
||||
onPressed: controller.toggleFlash,
|
||||
// 3. 手动输入按钮
|
||||
OutlinedButton.icon(
|
||||
onPressed: (){
|
||||
controller.manualInputBind("",1);
|
||||
},
|
||||
icon: const Icon(Icons.edit_note_rounded),
|
||||
label: const Text("手动输入车牌"),
|
||||
style: OutlinedButton.styleFrom(
|
||||
minimumSize: const Size(double.infinity, 56),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(28)),
|
||||
side: BorderSide(color: Theme.of(context).primaryColor, width: 1.5),
|
||||
textStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
),
|
||||
// 翻转相机按钮
|
||||
_buildIconButton(
|
||||
onPressed: controller.flipCamera,
|
||||
child: const Icon(
|
||||
Icons.flip_camera_ios,
|
||||
color: Colors.white,
|
||||
size: 28,
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 100), // 底部留白
|
||||
],
|
||||
),),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Widget _buildIconButton({required VoidCallback onPressed, required Widget child}) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withOpacity(0.3),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: IconButton(
|
||||
onPressed: onPressed,
|
||||
icon: child,
|
||||
iconSize: 32, // 增大点击区域
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 扫描动画的绘制器
|
||||
class ScannerAnimationPainter extends CustomPainter {
|
||||
final double value;
|
||||
final Color borderColor;
|
||||
|
||||
ScannerAnimationPainter(this.value, this.borderColor);
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint = Paint()
|
||||
..color = borderColor
|
||||
..strokeWidth = 3
|
||||
..style = PaintingStyle.stroke;
|
||||
|
||||
final cornerLength = 20.0;
|
||||
// 绘制四个角的边框
|
||||
// Top-left
|
||||
canvas.drawPath(
|
||||
Path()
|
||||
..moveTo(0, cornerLength)
|
||||
..lineTo(0, 0)
|
||||
..lineTo(cornerLength, 0),
|
||||
paint,
|
||||
);
|
||||
// Top-right
|
||||
canvas.drawPath(
|
||||
Path()
|
||||
..moveTo(size.width - cornerLength, 0)
|
||||
..lineTo(size.width, 0)
|
||||
..lineTo(size.width, cornerLength),
|
||||
paint,
|
||||
);
|
||||
// Bottom-left
|
||||
canvas.drawPath(
|
||||
Path()
|
||||
..moveTo(0, size.height - cornerLength)
|
||||
..lineTo(0, size.height)
|
||||
..lineTo(cornerLength, size.height),
|
||||
paint,
|
||||
);
|
||||
// Bottom-right
|
||||
canvas.drawPath(
|
||||
Path()
|
||||
..moveTo(size.width - cornerLength, size.height)
|
||||
..lineTo(size.width, size.height)
|
||||
..lineTo(size.width, size.height - cornerLength),
|
||||
paint,
|
||||
);
|
||||
|
||||
// 绘制扫描线
|
||||
final linePaint = Paint()
|
||||
..color = borderColor.withOpacity(0.8)
|
||||
..strokeWidth = 2
|
||||
..shader = LinearGradient(
|
||||
colors: [borderColor.withOpacity(0), borderColor, borderColor.withOpacity(0)],
|
||||
stops: const [0.0, 0.5, 1.0],
|
||||
).createShader(Rect.fromLTWH(0, 0, size.width, size.height));
|
||||
|
||||
final y = size.height * value;
|
||||
canvas.drawLine(Offset(0, y), Offset(size.width, y), linePaint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant CustomPainter oldDelegate) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user