Flutter 扫描二维码
在今天的移动开发中二维码扫描已经成为了常见的功能之一。Flutter作为一款跨平台的开发框架提供了丰富的插件和功能使得开发者可以轻松实现二维码扫描以及图像识别功能。本文将介绍如何在Flutter中通过结合scan插件、permission_handler插件以及image_picker插件实现二维码扫描和从相册选择二维码图片的功能。效果图1、相机扫描二维码2、相册选择二维码并扫描1、项目依赖首先我们需要在pubspec.yaml文件中添加以下依赖dependencies: flutter: sdk: flutter scan: ^1.6.0 # 用于扫描二维码 permission_handler: ^10.2.0 # 用于权限请求 image_picker: ^1.0.7 # 用于从相册选择图片这些插件的作用如下scan提供二维码扫描功能。permission_handler用于请求相机权限确保用户授权后才能使用相机进行二维码扫描。image_picker允许从相册选择图片便于用户上传二维码图片进行识别。2、配置权限在 Android 平台上为了使用相机功能需要在AndroidManifest.xml文件中添加必要的权限uses-permission android:nameandroid.permission.CAMERA/在 IOS平台上修改Info.plist文件打开ios/Runner/Info.plist文件确保添加以下配置iOS用虚拟机是调试不了的实测打不开相机keyNSCameraUsageDescription/key string需要访问相机用于二维码扫描/string keyNSPhotoLibraryUsageDescription/key string需要访问相册选择图片/string3、实现二维码扫描功能接下来我们来看看如何实现二维码扫描功能。我们需要创建一个主界面在其中添加按钮来请求相机权限并进行二维码扫描。没有二维码的小伙伴可以查看上一篇文章Flutter 生成二维码import package:flutter/material.dart; import package:scan/scan.dart; // 导入 scan 插件 import package:permission_handler/permission_handler.dart; // 导入权限请求插件 import package:image_picker/image_picker.dart; // 导入相册选择插件 void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { override Widget build(BuildContext context) { return MaterialApp( theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: QRScannerScreen(), ); } } class QRScannerScreen extends StatefulWidget { override _QRScannerScreenState createState() _QRScannerScreenState(); } class _QRScannerScreenState extends StateQRScannerScreen { ScanController controller ScanController(); // 创建扫描控制器 String qrcode Unknown; // 默认二维码内容 // 执行二维码扫描前请求权限 _requestPermissions() async { var cameraStatus await Permission.camera.request(); if (cameraStatus.isGranted) { // 权限已授予开始扫描 print(相机权限已授予开始扫描); // 点击按钮后跳转到全屏扫描界面 Navigator.push( context, MaterialPageRoute(builder: (context) QRScanPage(controller: controller)), ).then((result) { controller.pause(); // 暂停扫描 // 扫描完成后获取返回的二维码数据并更新显示 if (result ! null) { setState(() { qrcode result; // 更新二维码内容 }); } }); } else { // 权限被拒绝或未授权 print(相机权限未授予请授权); // 可以引导用户去设置页面 } } // 选择相册中的二维码图片 _selectImageFromGallery() async { final picker ImagePicker(); final pickedFile await picker.pickImage(source: ImageSource.gallery); if (pickedFile ! null) { String result await Scan.parse(pickedFile.path) ?? ; // 解析选中的图片 setState(() { qrcode result; // 更新二维码内容 }); } } override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text(二维码扫描器), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: Widget[ // 显示二维码扫描结果 const Text( 二维码扫描结果:, style: TextStyle(fontSize: 18), ), const SizedBox(height: 10), Text( qrcode, style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold), ), const SizedBox(height: 30), // 扫描按钮 ElevatedButton( onPressed: _requestPermissions, // 执行权限请求 child: const Text(请求权限并开始扫描), ), const SizedBox(height: 30), // 选择相册按钮 ElevatedButton( onPressed: _selectImageFromGallery, // 选择相册 child: const Text(从相册选择二维码图片), ), ], ), ), ); } }代码解释权限请求我们通过permission_handler插件请求相机权限。用户允许后才能进行二维码扫描。扫描功能我们使用ScanController来控制扫描过程。点击按钮后跳转到全屏扫描页面QRScanPage在该页面用户可以通过相机扫描二维码。从相册选择图片我们利用image_picker插件允许用户从相册选择图片并解析二维码。4、全屏扫描页面当用户点击扫描按钮时应用会进入一个全屏扫描页面。在该页面中二维码扫描功能全屏展示并且添加了一个“选择相册”按钮让用户可以在扫描时直接选择图片。class QRScanPage extends StatelessWidget { final ScanController controller; const QRScanPage({super.key, required this.controller}); override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(扫描二维码)), body: Stack( alignment: Alignment.center, children: [ // 使用 ScanView 进行全屏扫描 ScanView( controller: controller, scanAreaScale: 0.8, // 设置扫描区域占满整个屏幕 scanLineColor: Colors.green.shade400, // 设置扫描线颜色 onCapture: (data) { // 扫描到二维码后返回数据 controller.pause(); // 暂停扫描 Navigator.pop(context, data); // 返回扫描结果 }, ), // 在屏幕上添加选择相册按钮 Positioned( bottom: 100, child: ElevatedButton( onPressed: () async { // 选择相册 final picker ImagePicker(); final pickedFile await picker.pickImage(source: ImageSource.gallery); if (pickedFile ! null) { String result await Scan.parse(pickedFile.path) ?? ; // 解析选中的图片 Navigator.pop(context, result); // 返回二维码结果 } }, child: const Text( 选择相册, style: TextStyle( fontSize: 18, color: Colors.white, fontWeight: FontWeight.bold, ), ), ), ), ], ), ); } }代码解释ScanView提供扫描界面支持自定义扫描区域和扫描线颜色。onCapture当扫描到二维码时会返回扫描结果并暂停扫描。选择相册按钮点击按钮可以让用户从相册选择二维码图片进行解析。完整demo可以直接复制到新项目跑起来import package:flutter/material.dart; import package:scan/scan.dart; // 导入 scan 插件 import package:permission_handler/permission_handler.dart; // 导入权限请求插件 import package:image_picker/image_picker.dart; // 导入相册选择插件 void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { override Widget build(BuildContext context) { return MaterialApp( theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: QRScannerScreen(), ); } } class QRScannerScreen extends StatefulWidget { override _QRScannerScreenState createState() _QRScannerScreenState(); } class _QRScannerScreenState extends StateQRScannerScreen { ScanController controller ScanController(); // 创建扫描控制器 String qrcode Unknown; // 默认二维码内容 // 执行二维码扫描前请求权限 _requestPermissions() async { var cameraStatus await Permission.camera.request(); if (cameraStatus.isGranted) { // 权限已授予开始扫描 print(相机权限已授予开始扫描); // 点击按钮后跳转到全屏扫描界面 Navigator.push( context, MaterialPageRoute(builder: (context) QRScanPage(controller: controller)), ).then((result) { controller.pause(); // 暂停扫描 // 扫描完成后获取返回的二维码数据并更新显示 if (result ! null) { setState(() { qrcode result; // 更新二维码内容 }); } }); } else { // 权限被拒绝或未授权 print(相机权限未授予请授权); // 可以引导用户去设置页面 } } // 选择相册中的二维码图片 _selectImageFromGallery() async { final picker ImagePicker(); final pickedFile await picker.pickImage(source: ImageSource.gallery); if (pickedFile ! null) { String result await Scan.parse(pickedFile.path) ?? ; // 解析选中的图片 setState(() { qrcode result; // 更新二维码内容 }); } } override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text(二维码扫描器), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: Widget[ // 显示二维码扫描结果 const Text( 二维码扫描结果:, style: TextStyle(fontSize: 18), ), const SizedBox(height: 10), Text( qrcode, style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold), ), const SizedBox(height: 30), // 扫描按钮 ElevatedButton( onPressed: _requestPermissions, // 执行权限请求 child: const Text(请求权限并开始扫描), ), const SizedBox(height: 30), // 选择相册按钮 ElevatedButton( onPressed: _selectImageFromGallery, // 选择相册 child: const Text(从相册选择二维码图片), ), ], ), ), ); } } class QRScanPage extends StatelessWidget { final ScanController controller; const QRScanPage({super.key, required this.controller}); override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(扫描二维码)), body: Stack( alignment: Alignment.center, children: [ // 使用 ScanView 进行全屏扫描 ScanView( controller: controller, scanAreaScale: 0.8, // 设置扫描区域占满整个屏幕 scanLineColor: Colors.green.shade400, // 设置扫描线颜色 onCapture: (data) { // 扫描到二维码后返回数据 controller.pause(); // 暂停扫描 Navigator.pop(context, data); // 返回扫描结果 }, ), // 在屏幕上添加选择相册按钮 Positioned( bottom: 100, child: ElevatedButton( onPressed: () async { // 选择相册 final picker ImagePicker(); final pickedFile await picker.pickImage(source: ImageSource.gallery); if (pickedFile ! null) { String result await Scan.parse(pickedFile.path) ?? ; // 解析选中的图片 Navigator.pop(context, result); // 返回二维码结果 } }, child: const Text( 选择相册, style: TextStyle( fontSize: 18, color: Colors.white, fontWeight: FontWeight.bold, ), ), ), ), ], ), ); } }总结本文展示了如何在 Flutter 中实现二维码扫描和从相册选择二维码图片的功能。通过使用scan、permission_handler和image_picker插件我们可以轻松地添加二维码扫描和图片识别功能。在开发实际应用时可能还需要处理更多细节例如处理不同平台的权限请求、优化扫描体验等。希望这篇文章能帮助你更好地理解如何在 Flutter 中实现二维码扫描功能并能够应用到你的项目中。

相关新闻

HarmonyOS PromptAction 弹窗不显示怎么办:UIContext、页面销毁和回调兜底怎么拆

HarmonyOS PromptAction 弹窗不显示怎么办:UIContext、页面销毁和回调兜底怎么拆

# HarmonyOS PromptAction 弹窗不显示怎么办:UIContext、页面销毁和回调兜底怎么拆弹窗问题很容易被写成“接口没生效”,但我实际排查时更常见的是上下文已经不对了:按钮点击时页面还在,异步请求回来时页面可能已经返回&#xff1…

2026/10/13 19:17:15 阅读更多 →
Windows 超详细安装 d2l 踩坑全记录|d2l0.17.6 /d2l1.0.3 依赖冲突、pandas 编译失败、numpy 与 OpenCV 版本不兼容一站式解决

Windows 超详细安装 d2l 踩坑全记录|d2l0.17.6 /d2l1.0.3 依赖冲突、pandas 编译失败、numpy 与 OpenCV 版本不兼容一站式解决

最近在 Windows 环境安装《动手学深度学习》配套库 d2l,先后尝试教材配套稳定版d2l0.17.6和新版d2l1.0.3,踩了大量 Windows 平台独有的坑:离线高版本 whl 依赖连锁冲突、pandas 源码编译 ssize_t 未定义、VC 编译工具失效、numpy 与 opencv 版…

2026/10/18 10:15:47 阅读更多 →
RabbitMQ学习笔记-交换机类型和路由规则

RabbitMQ学习笔记-交换机类型和路由规则

交换机是RabbitMQ路由消息的核心,不同类型的交换机对应不同的路由逻辑,交换机和路由规则是多对多绑定,适配不同的业务场景。RabbitMQ提供4种默认交换机类型,其中前3种最常用。1、Direct Exchange(直连交换机&#xff0…

2026/10/14 3:50:37 阅读更多 →

最新新闻

摩天大楼电梯仿真:从建模到调度的完整实践指南

摩天大楼电梯仿真:从建模到调度的完整实践指南

简介:基于Java实现的摩天大楼电梯类型与调度算法仿真项目,模拟真实电梯运行,覆盖单向、双向、观光、高速、消防等电梯建模,并实现FCFS、最近楼层优先、最少行程时间、预调度和群控算法。通过随机生成乘客流量,可对比不…

2026/10/18 10:15:41 阅读更多 →
Pin-to-Pin替代CLRC663实战:从选型到量产的嵌入式硬件迁移复盘

Pin-to-Pin替代CLRC663实战:从选型到量产的嵌入式硬件迁移复盘

1. 从一次被迫的芯片迁移说起做嵌入式硬件这行的朋友,大概都经历过那种“半夜被供应链电话叫醒”的时刻。前两年全球缺芯潮最凶的时候,我手上一个量产了两年的读卡器项目,主控用的就是那颗经典的 CLRC663。这颗芯片在13.56MHz近场通信领域算是…

2026/10/18 10:15:41 阅读更多 →
AI辅助毕业论文全流程:从选题到降重排版的实用指南

AI辅助毕业论文全流程:从选题到降重排版的实用指南

每年三月一过,私信里问论文的人肉眼可见地多起来。有的说选题被导师否了三次,有的说文献综述写得像读书笔记,还有的更直接:格式改了六遍,学校查重平台的红字还是密密麻麻。今年我的回复里多了一个固定动作——把Paperx…

2026/10/18 10:15:41 阅读更多 →
庖丁解牛式危机应对:拆解四层结构,用最小切口重启人生

庖丁解牛式危机应对:拆解四层结构,用最小切口重启人生

说实话,我第一次读庄子《庖丁解牛》时,注意力全被那把用了十九年还像新磨出来一样的刀吸引住了。一个厨子,不砍骨头,不硬碰硬,只是一刀一刀顺着牛身体的自然纹理滑进去,游刃有余。后来我才意识到&#xff0…

2026/10/18 10:15:41 阅读更多 →
具身智能巡检机器人:工业场景下的选型与实践指南

具身智能巡检机器人:工业场景下的选型与实践指南

1. 从一次现场调研说起:工业巡检到底卡在哪去年在某个沿海化工园区的控制室里,我盯着DCS报警灯看了一下午,旁边安全员端着保温杯念叨同一句话:"白天跑不完,夜里不敢睡。"巡检这活儿,人干不好、机…

2026/10/18 10:15:41 阅读更多 →
KV Cache全解析:大模型推理显存计算与长上下文优化实战

KV Cache全解析:大模型推理显存计算与长上下文优化实战

我的大模型学习记录写到第六篇了。前几篇从tokenizer一路折腾到Transformer结构,又从预训练聊到指令微调,自以为对“大模型是怎么工作的”已经有点感觉了。直到上周试着把某个7B开源模型在本地跑起来,输入一段2K字的长文本让它做摘要&#xf…

2026/10/18 10:14:40 阅读更多 →

日新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 0:55:02 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 14:23:36 阅读更多 →