Flutter QR码扫描器优化:如何仅识别特定产品QR码?
实现仅扫描特定产品QR码的功能
要让你的Flutter二维码扫描器只识别特定产品的QR码,核心是在条码检测完成后加入验证逻辑,只处理符合预设规则的QR码,拒绝无关内容。以下是具体实现方案:
步骤1:定义特定产品的QR码规则
根据你的产品类别,为每个类别设定识别规则(比如前缀、关键词或正则表达式)。示例中我们给三类产品分别设定前缀规则:
- 显卡(Graphics Card):QR码内容以
GPU-开头 - 主板(Motherboard):以
MB-开头 - 处理器(Processor):以
CPU-开头
你可以根据实际需求调整规则,比如使用正则匹配复杂格式,或者包含特定关键词。
步骤2:修改条码检测逻辑
在onBarcodeDetected方法中加入验证逻辑,只有符合规则的QR码才进入保存流程;不符合则提示用户并重置扫描状态。
修改后的完整代码
以下是更新后的QrScanner代码,重点标记了新增/修改的部分:
import 'package:flutter/material.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; import 'package:provider/provider.dart'; import 'package:audioplayers/audioplayers.dart'; import 'serial_number_model.dart'; import 'serial_number_history.dart'; import 'inventory.dart'; const backgroundColor = Color.fromARGB(248, 248, 245, 245); // 新增:定义特定产品的QR码验证规则 Map<String, bool Function(String)> productQrRules = { 'Graphics Card': (code) => code.startsWith('GPU-'), 'Motherboard': (code) => code.startsWith('MB-'), 'Processor': (code) => code.startsWith('CPU-'), }; // 新增:判断QR码是否属于任何目标产品 bool isValidProductQr(String code) { return productQrRules.values.any((rule) => rule(code)); } class QrScanner extends StatefulWidget { const QrScanner({super.key}); @override State<QrScanner> createState() => _QrScannerState(); } class _QrScannerState extends State<QrScanner> { bool hasScanned = false; bool isFlashActive = false; CameraFacing cameraDirection = CameraFacing.back; late MobileScannerController scannerController; late AudioPlayer audioPlayer; @override void initState() { super.initState(); scannerController = MobileScannerController(); audioPlayer = AudioPlayer(); } Future<void> playScanSound() async { await audioPlayer.play(AssetSource('scan.mp3')); } // 修改:加入QR码验证逻辑 void onBarcodeDetected(Barcode barcode) { if (!hasScanned) { final code = barcode.rawValue ?? 'Unknown Code'; setState(() { hasScanned = true; }); // 验证QR码是否符合规则 if (isValidProductQr(code)) { playScanSound(); showSaveDialog(code); } else { // 提示无效QR码并重置扫描状态 ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('无效的产品QR码,请扫描目标产品')), ); resetScanState(); } } } void showSaveDialog(String code) { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('保存序列号!'), content: const Text('请选择保存类别:'), actions: [ ...buildCategoryButtons(code), TextButton( child: const Text('取消'), onPressed: () { Navigator.pop(context); resetScanState(); }, ), ], ), ); } List<Widget> buildCategoryButtons(String code) { final categories = ['Graphics Card', 'Motherboard', 'Processor']; return categories.map((category) { // 可选:根据QR码自动匹配对应类别(比如前缀对应类别) bool isEnabled = productQrRules[category]!(code); return TextButton( child: Text(category), onPressed: isEnabled ? () { saveSerialNumber(category, code); Navigator.pop(context); } : null, // 不符合类别的按钮置灰不可点击 ); }).toList(); } Future<void> saveSerialNumber(String category, String code) async { await Provider.of<SerialNumberModel>(context, listen: false) .addSerialNumber(category, code); if (!mounted) return; Navigator.push( context, MaterialPageRoute( builder: (context) => SerialNumberHistoryScreen( category: category, initialSerialNumbers: Provider.of<SerialNumberModel>(context) .getSerialNumbers(category) .map((map) => map['serialNumber']!) .toList(), serialNumbers: const [], ), ), ); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('已保存到 $category!')), ); resetScanState(); } void resetScanState() { WidgetsBinding.instance.addPostFrameCallback((_) { setState(() { hasScanned = false; }); }); } Future<void> toggleFlash() async { setState(() { isFlashActive = !isFlashActive; }); scannerController.toggleTorch(); } void switchCamera() { setState(() { cameraDirection = cameraDirection == CameraFacing.back ? CameraFacing.front : CameraFacing.back; scannerController.switchCamera(); String cameraPosition = cameraDirection == CameraFacing.front ? '前置' : '后置'; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('已切换到$cameraPosition摄像头')), ); }); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: backgroundColor, appBar: AppBar( actions: [ IconButton( onPressed: toggleFlash, icon: Icon(isFlashActive ? Icons.flash_off : Icons.flash_on, color: Colors.grey), ), IconButton( onPressed: switchCamera, icon: const Icon(Icons.camera, color: Colors.grey), ), ], iconTheme: const IconThemeData(color: Color.fromARGB(221, 131, 97, 97)), centerTitle: true, title: const Text("QR扫描器", style: TextStyle( color: Color.fromARGB(221, 0, 0, 0), fontWeight: FontWeight.bold)), leading: Builder(builder: (context) { return IconButton( icon: const Icon(Icons.menu), onPressed: () => Scaffold.of(context).openDrawer(), ); }), ), body: Container( width: double.infinity, padding: const EdgeInsets.all(16), child: Column( children: [ const Expanded( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text("请将设备对准目标产品的QR码", style: TextStyle( color: Colors.black87, fontSize: 18, fontWeight: FontWeight.bold)), SizedBox(height: 10), Text("扫描将自动进行"), ], ), ), Expanded( flex: 4, child: Center( child: SizedBox( width: double.infinity, height: 400, child: Stack( alignment: Alignment.center, children: [ Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.black, width: 2)), ), SizedBox( width: double.infinity, height: double.infinity, child: MobileScanner( controller: scannerController, onDetect: (BarcodeCapture barcodeCapture) { onBarcodeDetected(barcodeCapture.barcodes.first); }, ), ), ], ), ), ), ), const Expanded( child: Align( alignment: Alignment.center, child: Text("TechShack", style: TextStyle( color: Color.fromARGB(221, 247, 228, 228), fontSize: 14))), ), ], ), ), drawer: Drawer( child: ListView( padding: EdgeInsets.zero, children: [ DrawerHeader( decoration: const BoxDecoration( color: Color.fromARGB( 255, 73, 167, 255), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( 'TechShack', style: TextStyle( color: Colors.black, fontSize: 24, fontWeight: FontWeight.bold, ), ), Image.asset( 'assets/logo.png', height: 120, width: 120, fit: BoxFit.cover, ), ], ), ), buildDrawerItem('Graphics Card'), buildDrawerItem('Motherboard'), buildDrawerItem('Processor'), ListTile( title: const Text('库存'), onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => const Inventory(), ), ); }, ), ], ), ), ); } Widget buildDrawerItem(String category) { return ListTile( title: Text(category), onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => Consumer<SerialNumberModel>( builder: (context, serialNumberModel, child) { return SerialNumberHistoryScreen( category: category, initialSerialNumbers: serialNumberModel .getSerialNumbers(category) .map((map) => map['serialNumber']!) .toList(), serialNumbers: const [], ); }, ), ), ); }, ); } }
额外优化说明
- 自动匹配类别:在
buildCategoryButtons中,我们给不符合类别的按钮设置了onPressed: null,这样用户只能选择对应类别的保存选项,避免误操作。 - 规则可扩展性:
productQrRules是一个Map结构,你可以轻松添加更多产品类别的规则,比如'RAM': (code) => code.startsWith('RAM-')。 - 自定义规则:如果你的QR码格式更复杂,可以使用正则表达式,比如:
这个规则表示显卡的QR码必须是'Graphics Card': (code) => RegExp(r'^GPU-\d{8}$').hasMatch(code),GPU-开头加8位数字。
内容的提问来源于stack exchange,提问作者camelk12334
相关产品推荐
相关产品推荐

