Flutter二维码的生成和扫描
qr_flutter:二维码生成工具,通过传递字符串可以直接生成 Widget
。
mobile_scanner:该插件同时支持条形码和二维码,也支持开关灯、本地图片扫描、返回图片等功能。这里只介绍基础用法,更多用法请参考官方demo。
二维码生成
-
添加依赖
dartflutter pub add qr_flutter
-
导入
dartimport 'package:qr_flutter/qr_flutter.dart';
-
使用
-
基本二维码
dartchild: QrImageView( data: "这个是二维码生成数据", ersion: QrVersions.auto, size: 150, ),
-
带有图像的二维码
dartchild: QrImageView( data: "这个是二维码生成数据", version: QrVersions.auto, size: 150, embeddedImage: const AssetImage("images/bg.png"), embeddedImageStyle: QrEmbeddedImageStyle(size: Size(80, 80)), ),
-
无法验证二维码时显示错误状态
dartQrImageView( data: "这个是二维码生成数据", version: QrVersions.auto, size: 150,lse, errorStateBuilder: (cxt, err) { return Container( child: Center( child: Text( 'Uh oh! Something went wrong...', textAlign: TextAlign.center, ), ), ); }, )
-
二维码的扫描
-
添加依赖
dartflutter pub add mobile_scanner
-
导入
dartimport 'package:mobile_scanner/mobile_scanner.dart';
-
配置
默认情况下,此软件包使用适用于 Android 的 MLKit 条码扫描的捆绑版本。此版本可立即用于设备。但它会将应用程序的大小增加大约 3 到 10 MB。
另一种方法是使用适用于 Android 的 MLKit 条形码扫描的未捆绑版本 。此版本在首次使用时通过 Google Play 服务下载。它将应用程序大小增加了约 600KB。您可以在此处阅读有关两个版本之间差异的更多信息。
完整代码如下
- main.dart
dart
import 'package:code_scanner/qr_code_generate.dart';
import 'package:code_scanner/qr_code_scan.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('二维码扫描')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const QrCodeGenerate(),
),
);
},
child: const Text('二维码生成'),
),
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const QrCodeScan()),
);
},
child: const Text('二维码扫描'),
),
],
),
),
);
}
}
- 二维码生成qr_code_generate.dart
dart
import 'package:flutter/material.dart';
import 'package:qr_flutter/qr_flutter.dart';
class QrCodeGenerate extends StatelessWidget {
const QrCodeGenerate({super.key});
@override
Widget build(BuildContext context) {
return Container(
decoration: const BoxDecoration(color: Colors.white),
child: Align(
alignment: Alignment.center,
child: QrImageView(
data: "这个是二维码生成数据",
version: QrVersions.auto,
size: 150,
// embeddedImage: const AssetImage("images/bg.png"),
// embeddedImageStyle: QrEmbeddedImageStyle(size: Size(80, 80)),
),
),
);
}
}
- 二维码扫描主页面qr_code_scan.dart
dart
import 'package:code_scanner/buttons/analyze_image_button.dart';
import 'package:code_scanner/buttons/switch_camera_button.dart';
import 'package:code_scanner/buttons/toggle_flashlight_button.dart';
import 'package:code_scanner/widget/zoom_scale_slider_widget.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
class QrCodeScan extends StatefulWidget {
const QrCodeScan({super.key});
@override
State<QrCodeScan> createState() => _QrCodeScanState();
}
class _QrCodeScanState extends State<QrCodeScan> {
Barcode? barcode;
MobileScannerController controller = MobileScannerController(
returnImage: true,
autoZoom: true,
);
late final scanWindow = Rect.fromCenter(
center: MediaQuery.sizeOf(context).center(const Offset(0, -100)),
width: 300,
height: 200,
);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('二维码扫描')),
body: Stack(
children: [
MobileScanner(
controller: controller,
onDetect: (BarcodeCapture barcodes) {
if (mounted) {
setState(() {
barcode = barcodes.barcodes.firstOrNull;
});
}
},
),
//扫描框
ScanWindowOverlay(scanWindow: scanWindow, controller: controller),
BarcodeOverlay(boxFit: BoxFit.contain, controller: controller),
//扫描成功后返回的图片
IgnorePointer(
child: Align(
alignment: Alignment.topRight,
child: Card(
clipBehavior: Clip.hardEdge,
shape: RoundedRectangleBorder(
side: const BorderSide(color: Colors.white),
borderRadius: BorderRadius.circular(10),
),
child: SizedBox(
width: 100,
height: 100,
child: StreamBuilder<BarcodeCapture>(
stream: controller.barcodes,
builder: (context, snapshot) {
final BarcodeCapture? barcode = snapshot.data;
if (barcode == null) {
return const Center(
child: Text(
'Your scanned barcode will appear here',
textAlign: TextAlign.center,
),
);
}
final Uint8List? barcodeImage = barcode.image;
if (barcodeImage == null) {
return const Center(
child: Text('No image for this barcode.'),
);
}
return Image.memory(
barcodeImage,
fit: BoxFit.cover,
gaplessPlayback: true,
errorBuilder: (context, error, stackTrace) {
return Center(
child: Text('Could not decode image bytes. $error'),
);
},
);
},
),
),
),
),
),
Align(
alignment: Alignment.bottomCenter,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
alignment: Alignment.bottomCenter,
height: 50,
color: const Color.fromRGBO(0, 0, 0, 0.4),
child: Text(
barcode?.displayValue ?? '未识别二维码',
style: const TextStyle(color: Colors.white, fontSize: 20),
),
),
// 镜头焦距
if (!kIsWeb) ZoomScaleSliderWidget(controller: controller),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
// 打开/关闭手电筒
ToggleFlashlightButton(controller: controller),
// 切换镜头
SwitchCameraButton(controller: controller),
//扫描本地图片
AnalyzeImageButton(controller: controller),
],
),
],
),
),
],
),
);
}
}
- 调整镜头焦距zoom_scale_slider_widget.dart
dart
import 'package:flutter/material.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
class ZoomScaleSliderWidget extends StatelessWidget {
final MobileScannerController controller;
const ZoomScaleSliderWidget({super.key, required this.controller});
@override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: controller,
builder: (context, state, child) {
if (!state.isInitialized || !state.isRunning) {
return const SizedBox.shrink();
}
return Row(
children: [
Expanded(
child: Slider(
value: state.zoomScale,
onChanged: controller.setZoomScale,
),
),
Text(
state.zoomScale.toString(),
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.red,
),
),
],
);
},
);
}
}
- 开启关闭闪光灯analyze_image_button.dart
dart
import 'package:flutter/material.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
class ToggleFlashlightButton extends StatelessWidget {
final MobileScannerController controller;
const ToggleFlashlightButton({super.key, required this.controller});
Future<void> _onPressed() async => controller.toggleTorch();
@override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: controller,
builder: (context, state, child) {
if (!state.isInitialized || !state.isRunning) {
return const SizedBox.shrink();
}
// 根据手电筒状态显示不同的图标
switch (state.torchState) {
//显示自动闪光图标
case TorchState.auto:
return IconButton(
color: Colors.white,
iconSize: 32,
icon: const Icon(Icons.flash_auto),
onPressed: _onPressed,
);
//显示关闭闪光图标
case TorchState.off:
return IconButton(
color: Colors.white,
iconSize: 32,
icon: const Icon(Icons.flash_off),
onPressed: _onPressed,
);
//显示开启闪光图标
case TorchState.on:
return IconButton(
color: Colors.white,
iconSize: 32,
icon: const Icon(Icons.flash_on),
onPressed: _onPressed,
);
//显示无闪光功能图标
case TorchState.unavailable:
return const SizedBox.square(
dimension: 48,
child: Icon(Icons.no_flash, size: 32, color: Colors.grey),
);
}
},
);
}
}
- 切换前置后置镜头switch_camera_button.dart
dart
import 'package:flutter/material.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
class SwitchCameraButton extends StatelessWidget {
final MobileScannerController controller;
const SwitchCameraButton({super.key, required this.controller});
Future<void> _onPressed() async => controller.switchCamera();
@override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: controller,
builder: (context, state, child) {
if (!state.isInitialized || !state.isRunning) {
return const SizedBox.shrink();
}
// 可用镜头的数量
final int? availableCameras = state.availableCameras;
if (availableCameras != null && availableCameras < 2) {
return const SizedBox.shrink();
}
final Widget icon;
switch (state.cameraDirection) {
// 前置摄像头
case CameraFacing.front:
icon = const Icon(Icons.camera_front);
// 后置摄像头
case CameraFacing.back:
icon = const Icon(Icons.camera_rear);
// 外接摄像头
case CameraFacing.external:
icon = const Icon(Icons.usb);
// 未知摄像头
case CameraFacing.unknown:
icon = const Icon(Icons.device_unknown);
}
return IconButton(
color: Colors.white,
iconSize: 32,
icon: icon,
onPressed: _onPressed,
);
},
);
}
}
- 读取图库照片analyze_image_button.dart
dart
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
class AnalyzeImageButton extends StatelessWidget {
final MobileScannerController controller;
const AnalyzeImageButton({super.key, required this.controller});
Future<void> _onPressed(BuildContext context) async {
// 检查当前是否在Web平台运行
if (kIsWeb) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Analyze image is not supported on web'),
backgroundColor: Colors.red,
),
);
return;
}
final picker = ImagePicker();
final XFile? image = await picker.pickImage(source: ImageSource.gallery);
if (image == null) {
return;
}
final BarcodeCapture? barcodes = await controller.analyzeImage(image.path);
if (!context.mounted) {
return;
}
final snackBar = barcodes != null && barcodes.barcodes.isNotEmpty
? SnackBar(
content: Text(
barcodes.barcodes.first.rawValue ?? 'Unknown barcode',
),
backgroundColor: Colors.green,
)
: const SnackBar(
content: Text('No barcode found!'),
backgroundColor: Colors.red,
);
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
@override
Widget build(BuildContext context) {
return IconButton(
onPressed: () => _onPressed(context),
icon: const Icon(Icons.image),
color: Colors.white,
);
}
}
