Flutter二维码的生成和扫描

Flutter二维码的生成和扫描

qr_flutter:二维码生成工具,通过传递字符串可以直接生成 Widget

mobile_scanner:该插件同时支持条形码和二维码,也支持开关灯、本地图片扫描、返回图片等功能。这里只介绍基础用法,更多用法请参考官方demo

二维码生成

  1. 添加依赖

    dart 复制代码
    flutter pub add qr_flutter
  2. 导入

    dart 复制代码
    import 'package:qr_flutter/qr_flutter.dart';
  3. 使用

    • 基本二维码

      dart 复制代码
      child: QrImageView(
       data: "这个是二维码生成数据",
       ersion: QrVersions.auto,
       size: 150,
      ),
    • 带有图像的二维码

      dart 复制代码
      child: QrImageView(
       data: "这个是二维码生成数据",
       version: QrVersions.auto,
       size: 150,
       embeddedImage: const AssetImage("images/bg.png"),
       embeddedImageStyle: QrEmbeddedImageStyle(size: Size(80, 80)),
      ),
    • 无法验证二维码时显示错误状态

      dart 复制代码
      QrImageView(
        data: "这个是二维码生成数据",
        version: QrVersions.auto,
        size: 150,lse,
        errorStateBuilder: (cxt, err) {
          return Container(
            child: Center(
              child: Text(
                'Uh oh! Something went wrong...',
                textAlign: TextAlign.center,
              ),
            ),
          );
        },
      ) 

二维码的扫描

  1. 添加依赖

    dart 复制代码
    flutter pub add mobile_scanner
  2. 导入

    dart 复制代码
    import 'package:mobile_scanner/mobile_scanner.dart';
  3. 配置

    默认情况下,此软件包使用适用于 Android 的 MLKit 条码扫描的捆绑版本。此版本可立即用于设备。但它会将应用程序的大小增加大约 3 到 10 MB。

    另一种方法是使用适用于 Android 的 MLKit 条形码扫描的未捆绑版本 。此版本在首次使用时通过 Google Play 服务下载。它将应用程序大小增加了约 600KB。您可以在此处阅读有关两个版本之间差异的更多信息。

完整代码如下

  1. 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('二维码扫描'),
            ),
          ],
        ),
      ),
    );
  }
}
  1. 二维码生成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)),
        ),
      ),
    );
  }
}
  1. 二维码扫描主页面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),
                  ],
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}
  1. 调整镜头焦距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,
              ),
            ),
          ],
        );
      },
    );
  }
}
  1. 开启关闭闪光灯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),
            );
        }
      },
    );
  }
}
  1. 切换前置后置镜头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,
        );
      },
    );
  }
}
  1. 读取图库照片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,
    );
  }
}
相关推荐
鹏多多4 小时前
flutter-详解控制组件显示的两种方式Offstage与Visibility
前端·flutter
猪哥帅过吴彦祖10 小时前
Flutter 系列教程:常用基础组件 (上) - `Text`, `Image`, `Icon`, `Button`
android·flutter·ios
恋猫de小郭11 小时前
Fluttercon EU 2025 :Let's go far with Flutter
android·前端·flutter
小李飞刀李寻欢12 小时前
flutter 详细解读
flutter
QQ12958455041 天前
错误解决:Flutter找不到合适的Visual Studio 工具链
flutter·visual studio
程序员老刘1 天前
Flutter版本选择指南:避坑3.27 | 2025年9月
flutter·客户端
清风细雨_林木木1 天前
flutter 里面的渐变色设置
前端·flutter
猪哥帅过吴彦祖1 天前
Flutter 系列教程:布局基础 (下) - Stack 绝对定位和 Expanded 弹性布局
前端·flutter·ios
小林的技术分享1 天前
Flutter 创建一个插件(FFI)
flutter