
接了个需求要给线下物料做一批二维码甲方提了句能不能别用那种黑底白点的方块了说实话市面上大多数二维码确实实用大于颜值——豆腐块、三个定位角、密密麻麻的模块放在宣传海报上就像贴了张补丁。项目是 Flutter 的技术栈我就顺手把主流的二维码方案都翻了一遍最终锁定了 pretty_qr_code。这个库能解决什么问题简单说就是让 Flutter 开发者不用自己去写 Canvas 绘制逻辑也不用在样式上反复调半天。它支持自定义颜色、圆角定位眼、圆形数据模块、嵌入 Logo、加载网络图片甚至通过 ImageProvider 把这些能力统一管理最后产出一张可以直接展示、也能导出的二维码图片。适合谁看Flutter 开发者尤其是那些要在名片、海报、邀请函、包装码、活动物料里放一个不丢面子的二维码的人。这篇文章不是 API 文档的复读更多是我在实际接入过程中对参数取舍、识别率踩坑以及最终效果如何平衡的经验汇总。你可以把它当成一份“用 pretty_qr_code 做高颜值二维码”的实操笔记。1. 为什么偏偏是 pretty_qr_code1.1 三个主流 Flutter 二维码方案怎么选Flutter 生态里能生成二维码的库不算少但真正在项目里常见的就三个qr_flutter、fast_qr_code、pretty_qr_code。很多人上来就选 qr_flutter因为老牌、教程多、用起来稳。但我对比了一圈之后发现不同库适合的场景差异还是挺大的。拿 qr_flutter 来说它最成熟QrPainter 直接画在 Canvas 上支持自定义颜色和嵌入图片但 API 设计偏保守。比如 styled 参数需要你自己组装 QrPainter然后再包一个 QrImageView数据模块的形状也以方形为主定制空间相对有限。它适合快速实现“能扫”的二维码但要做到“好看”需要额外写不少适配逻辑。fast_qr_code 是另一条路线它的卖点就是快。通过预先生成 ByteData然后把二维码作为图片渲染在列表页大量展示时不容易卡顿。如果你要做的是“一屏几十个二维码”的展示场景这个库很合适。但它的强项不在样式定制视觉上依然中规中矩。而 pretty_qr_code 最吸引我的地方是它把二维码从“Canvas 绘制”升级成了“ImageProvider 图片供给”。这意味着二维码本身可以像 Flutter 里的普通图片一样被缓存、被加载、被导出同时 Logo 的嵌入方式也更灵活支持 AssetImage、NetworkImage、MemoryImage 等常见图片来源。配合 QrEyeStyle、QrDataModuleStyle、QrImageDecoration 这几个装饰参数我能在一个组件里完成从配色到圆角再到 Logo 的全部处理代码量反而比 qr_flutter 那套嵌套方式更少。对比项qr_flutterfast_qr_codepretty_qr_code定制能力中等需手动组装 painter弱偏向高性能展示强装饰参数集中渲染性能普通 Canvas 绘制预生成图片适合大批量图片供给合理使用后性能不错Logo 支持支持但需要额外封装支持内置且图片来源类型丰富上手成本教程多但 API 旧低中低看完文档就能写适合场景通用业务码列表、海量码品牌物料的视觉码、分享卡片1.2 底层逻辑二维码图片是怎么生成的二维码的本质是个矩阵点阵。它先把字符串内容编码成二进制数据流经过纠错、掩码等步骤最终变成一个由深色和浅色模块组成的正方形图案。QrImageProvider 干的事就是把这些点阵数据转换成一张可供渲染的图片而不是让开发者自己逐格画方块。这个设计带来的好处在实际使用中很明显。首先二维码内容不变时图片可以提供缓存能力同一张码不会反复重建。其次Logo 作为一个图片层叠加进来不需要单独写 Canvas 绘制。最后因为它是 ImageProvider我可以用标准方式导出 PNG甚至直接扔进分享插件做图片发送省掉很多手工处理。所以我的建议是选库之前先搞清楚自己要的是什么。如果你只是需要一个“扫得出、能跳转”的基础码qr_flutter 没问题。但如果你和我一样要把二维码放进品牌视觉体系里那 pretty_qr_code 这种以图片供给为核心的方案明显更省心。2. 核心参数逐个拆解从颜色到 Logo 全覆盖2.1 内容与尺寸想清楚要装什么二维码的容错和尺寸跟你要放进去的内容长度直接相关。QrImageProvider 的 data 参数可以传 URL、纯文本、WiFi 配置甚至任意字符串但内容越长生成的模块密度就越高视觉上看起来就越“碎”。我踩过的一个坑就是早期做 demo 时直接把一长串带参数的链接塞进去出来的二维码密密麻麻缩小到 200 像素后扫码识别明显变慢。后来统一改用短链接二维码的模块数量降了下来同样的尺寸下识别速度快了不少。尺寸方面qrSize 的单位是逻辑像素不是模块数。它控制的是整张二维码图片的显示尺寸至于内部有多少个模块由内容长度和纠错级别决定。我的经验值是常规场景下二维码展示尺寸不要低于 240否则手机离远一点就扫不动。如果是要做分享大图甚至印刷物料那就直接上 500 以上给后处理留出压缩空间。2.2 眼睛与模块把三个定位角做得圆润二维码好不好看很大程度取决于定位眼的形状。普通二维码那三个大黑框是最显眼的视觉元素改圆角或改成圆形之后整体气质立刻不一样。pretty_qr_code 通过 QrEyeStyle 控制定位眼这里有两个关键参数一个是颜色另一个是形状。形状我用得最多的是 rounded 和 circle。rounded 适合品牌气质偏稳重的场景circle 则更现代、更轻盈。下面是一段最小配置PrettyQrCode( imageProvider: QrImageProvider( data: https://flutter.cn, ), qrSize: 300, eyeStyle: QrEyeStyle( eyeShape: QrEyeShape.rounded, color: const Color(0xFF1E4A7A), ), dataModuleStyle: QrDataModuleStyle( dataModuleShape: QrDataModuleShape.circle, color: const Color(0xFF1E4A7A), ), )数据模块的 DataModuleShape 也值得单独说。square 是默认的方块circle 是圆形。我实测下来圆形模块在手机屏幕上观感最细腻尤其在浅色背景上棱角感没那么重。但要注意全用圆形时二维码整体点阵的视觉密度会降低如果背景比较花识别率会受影响所以背景越复杂越要用回 square。2.3 嵌入 LogoQrImageDecoration 的正确姿势品牌二维码的核心需求就是放 Logo。pretty_qr_code 的 Logo 传递方式很直接在 QrImageProvider 里传入 image 参数再通过 QrImageDecoration 控制它在二维码中心的呈现样式。这里有一个经验值Logo 的宽高占二维码整体尺寸的 20% 到 30% 比较安全。太小了没有品牌辨识度太大了会遮住太多数据模块导致扫码失败。我通常的做法是二维码尺寸 320 像素左右时Logo 控制在 80 到 90 像素再给 Logo 包一个 6 到 8 像素的白色圆角边。白色圆角边很重要。因为二维码中心区域大概率有深色模块Logo 直接压上去会融为一体加一层白色底板才能把 Logo 从码体里“抠”出来。QrImageDecoration 里有个 color 参数就是用来设置这个底板的形状选 rounded 比方形自然得多。PrettyQrCode( imageProvider: QrImageProvider( data: https://flutter.cn, image: const AssetImage(assets/images/logo.png), ), qrSize: 320, eyeStyle: const QrEyeStyle( eyeShape: QrEyeShape.rounded, color: Color(0xFF1E4A7A), ), dataModuleStyle: const QrDataModuleStyle( dataModuleShape: QrDataModuleShape.circle, color: Color(0xFF1E4A7A), ), imageDecoration: const QrImageDecoration( width: 90, height: 90, shape: QrImageShape.rounded, color: Colors.white, padding: EdgeInsets.all(6), ), )还有一个细节Logo 图片本身最好不要带复杂的透明纹理。如果你塞一张背景透明的 PNG叠加在二维码上之后透明区域会直接透出底下的黑色模块视觉上很脏。我的习惯是 Logo 图片自带纯色背景或者在 QrImageDecoration 里把 color 设置为白色这样外层始终有一层干净底板。2.4 渐变和背景色不一定要动码体关于“高颜值”很多人第一反应是给二维码模块本身做渐变色。但这里我想先泼盆冷水pretty_qr_code 没有内置的二维码渐变色参数而且直接给深色模块填充浅色调会显著降低对比度识别率容易翻车。我在项目里更推荐一种稳妥的玩法把渐变放在二维码的外圈背景上。也就是用 Container 包一层渐变底里面放一个白底圆角的二维码。这样整体视觉上有色彩感但二维码的扫描区域依然保持深模块、浅底板的二分关系识别完全不受影响。Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( gradient: const LinearGradient( colors: [Color(0xFF3A7BD5), Color(0xFF00D2FF)], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(20), ), child: PrettyQrCode( imageProvider: QrImageProvider(data: https://flutter.cn), qrSize: 260, ), )如果你实在想让二维码码体本身带渐变就得自己走 ShaderMask 或者 CustomPainter 叠加颜色的路子了。这个操作不是不行而是你要花额外精力去验证不同真实手机上的识别效果投入产出比不高。至少我目前的项目里外层渐变方案已经能满足大部分视觉需求。2.5 静区二维码四周留白不是玄学二维码识别依赖四周的一圈白色“静区”这是扫码算法定位边界的依据。很多高颜值二维码翻车不是因为码做得不好看而是二维码直接贴在渐变背景或图案边缘抹掉了静区。在 pretty_qr_code 的组件体系里我没有找到专门设置静区宽度的参数所以最方便的做法是对外再包一层有 padding 的容器。二维码尺寸在 260 到 320 像素之间时外圈留白 16 到 24 像素比较稳。如果是印刷物料静区要更大一些宁可整体缩小二维码也不要让它贴到裁切线上。3. 从零到一完整接入实操3.1 创建项目并安装依赖如果你是从空白项目开始直接命令创建就行flutter create qr_demo cd qr_demo flutter pub add pretty_qr_code这会把 pretty_qr_code 最新版本写入 pubspec.yaml。当然你也可以手动添加 dependencies版本号以你使用时的 pub.dev 最新版本为准dependencies: flutter: sdk: flutter pretty_qr_code: ^2.0.3然后拉取依赖flutter pub get这一步一般不会出问题。不过如果你本地 Flutter 版本比较老偶尔会遇到依赖解析失败优先检查 Flutter SDK 版本升级到稳定版再试。3.2 最小可用示例先跑通最核心的流程确认二维码能显示、能扫。一个最简单页面长这样import package:flutter/material.dart; import package:pretty_qr_code/pretty_qr_code.dart; void main() { runApp(const QrDemoApp()); } class QrDemoApp extends StatelessWidget { const QrDemoApp({super.key}); override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( body: Center( child: PrettyQrCode( imageProvider: QrImageProvider( data: https://flutter.cn, ), qrSize: 300, ), ), ), ); } }这个示例里我直接用了 flutter.cn 做内容扫出来就是官网链接。跑起来之后先别急着美化拿真机扫一下确认基础识别没问题再继续往下叠装饰。3.3 进阶示例圆角眼睛加圆形模块加 Logo确认基础流程没问题之后再上完整的品牌化配置。我拿项目里最常用的一套样式举例class BrandQrCard extends StatelessWidget { const BrandQrCard({super.key}); override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(24), boxShadow: const [ BoxShadow( color: Color(0x22000000), blurRadius: 20, offset: Offset(0, 8), ), ], ), child: PrettyQrCode( imageProvider: QrImageProvider( data: https://flutter.cn, image: const AssetImage(assets/images/logo.png), ), qrSize: 320, eyeStyle: const QrEyeStyle( eyeShape: QrEyeShape.rounded, color: Color(0xFF1E4A7A), ), dataModuleStyle: const QrDataModuleStyle( dataModuleShape: QrDataModuleShape.circle, color: Color(0xFF1E4A7A), ), imageDecoration: const QrImageDecoration( width: 90, height: 90, shape: QrImageShape.rounded, color: Colors.white, padding: EdgeInsets.all(6), ), ), ); } }这一套配置下来视觉上比默认黑方块舒服很多。注意 Logo 资源要在 pubspec.yaml 里声明flutter: assets: - assets/images/logo.png不声明的话真机运行时会直接报资源找不到这个错我在新团队项目里见过太多次了。3.4 导出 PNG把二维码存下来有的场景需要把二维码保存成图片文件比如生成分享卡片、后台物料或者动态二维码变成静态图分发。思路很简单把 PrettyQrCode 包在一个 RepaintBoundary 里然后用 Flutter 自带的渲染能力截取图片。class QrExportPage extends StatefulWidget { const QrExportPage({super.key}); override StateQrExportPage createState() _QrExportPageState(); } class _QrExportPageState extends StateQrExportPage { final GlobalKey _qrKey GlobalKey(); Futurevoid _saveQr() async { final boundary _qrKey.currentContext!.findRenderObject()! as RenderRepaintBoundary; final image await boundary.toImage(pixelRatio: 3); final byteData await image.toByteData(format: ui.ImageByteFormat.png); // byteData 就是 PNG 字节可以写入本地文件也可以交给分享插件 } override Widget build(BuildContext context) { return Scaffold( body: Center( child: RepaintBoundary( key: _qrKey, child: PrettyQrCode( imageProvider: QrImageProvider( data: https://flutter.cn, ), qrSize: 320, ), ), ), ); } }pixelRatio 设置成 3 是为了让导出图比显示尺寸更大适合后续印刷或高清分享。这里有个我的个人习惯导出前不要把二维码放在太靠近屏幕边缘的位置否则 RepaintBoundary 会把周围其他元素也截进去最后裁图很痛苦。4. 真实踩坑记录问题排查清单4.1 扫不出来时的排查顺序做高颜值二维码最尴尬的不是报错而是渲染出来很好看但手机一扫毫无反应。遇到这种情况我强烈建议按照固定顺序排查表现可能原因解决办法。我整理了一个速查表常见表现可能原因解决办法完全扫不出来深色模块与背景对比度太低模块用深色背景用浅色手机要很近才能扫二维码内容太长模块过密换成短链接中间区域扫不到Logo 占比过大遮盖数据模块Logo 控制在 20%-30%边缘扫不出来静区被裁剪二维码贴边外层加 padding保留留白扫码识别不稳定二维码整体尺寸太小qrSize 加大到 280 以上我见过很多新人一上来就选浅蓝色、浅粉色做数据模块结果扫描器在亮度低的场景下就罢工。如果你要做彩色二维码最稳妥的方案是选深色系作为码体颜色比如墨蓝、墨绿、深棕底色保持白色或极浅的米色。4.2 Logo 不显示或加载异常Logo 嵌入后的异常常见有三类Asset 资源找不到、网络图片无法加载、图片尺寸适配问题。Asset 资源找不到九成是 pubspec.yaml 里的 assets 路径写错。路径要和项目里的实际目录完全一致注意大小写。网络图片加载失败则要先确认测试设备有没有网络权限Android 上就是检查 AndroidManifest 里的 INTERNET 权限iOS 上检查 App Transport Security 配置。还有一个隐蔽问题Logo 图片本身尺寸过大。你丢一张 2000 像素的大图给 QrImageProvider组件内部还要做缩放某些低端机型上可能出现绘制卡顿。我的经验是Logo 图片提前缩到 300 像素以内再塞进去性能和显示都更可控。4.3 列表页卡顿与图片重建如果你把 PrettyQrCode 直接塞进 ListView每滚动一个 item 就重建一张二维码图片很快你就会感受到明显的卡顿。原因是二维码生成本身有一定计算量尤其是带 Logo 和复杂装饰的时候。优化方式有三种。第一给单个二维码外层包 RepaintBoundary减少重绘范围。第二把生成好的 ByteData 缓存起来用 Image.memory 渲染彻底跳过重复生成。第三如果二维码内容固定就把整个二维码组件做成 const让 Flutter 框架层直接跳过重建。我在项目里的长期方案是第二种页面加载时预生成一份 ByteData然后统一用 Image.memory 展示。这不仅解决了滑动卡顿后面导出分享图时也能直接复用同一份数据省一次生成开销。4.4 构建打包的连带问题pretty_qr_code 本身依赖很轻基本不存在编译期冲突。但如果你从老版本 Flutter 项目升级过来可能会遇到 Gradle 配置相关的报错这类问题其实跟二维码组件无关是 Flutter Android 构建链的老毛病。遇到构建失败的时候先跑flutter doctor看环境状态再检查项目的 Gradle/AGP 版本是否匹配。我的习惯是新建项目做对比默认模板跑得通老项目跑不通就逐个对齐模板里的配置。别一上来就怀疑库有问题先确认构建链是健康的。5. 高颜值二维码的设计心得5.1 识别优先颜色对比度永远排在第一位不管追求多炫的效果二维码的本质还是给扫描设备读取的。手机的扫码算法对深色模块和浅色底板之间的对比度有硬性要求。浅底浅色、深底深色、或者深色背景上放深色码都会让识别变得不可靠。我的安全设计法则是模块颜色只在深色系里选底板要么纯白要么极浅的中性色。如果你已经给二维码套了一层品牌色渐变背景那就别再让码体直接贴上去中间加一道白色过渡层既保住了颜值也保住了识别率。5.2 一套能直接复制的品牌化参数组合做线下物料项目时我最终沉淀下来一套参数组合实测识别稳定同时视觉效果在线二维码尺寸320眼睛样式rounded 圆角颜色用品牌深色数据模块circle 圆形颜色和定位眼一致Logo宽度 90白色圆角底板圆角 6外圈包裹Container padding 16白底圆角 24加浅阴影按照这套参数换品牌色就能直接套到不同客户的物料上。如果品牌主色特别浅比如柠檬黄那就得把码体改成深灰或深蓝而不是强行用柠檬黄。这个细节很容易被忽略。5.3 适合 pretty_qr_code 的实际应用场景它做出来的二维码图片质量高适合直接放进视觉体系复杂的地方。像是名片背面二维码可以和联系方式卡片融为一体邀请函上圆角二维码配同色系说明文字整体高级很多包装袋上的物料码贴纸直接印这个样式也不会显得突兀。有一点可以延伸的是如果你要生成一批数量不等的动态物料码不要把数据写死在代码里可以维护一个内容列表循环调用同一个组件再批量导出一套 PNG。这个思路配合前面提到的 RepaintBoundary 导出方案能节省大量手工时间。最后再分享一个实操体会。高颜值二维码的难点从来不是库不够强而是“好看”和“能扫”之间做取舍。我在项目里最终的参数其实是克制的深色模块、白色底板、圆角定位眼、圆形数据模块、Logo 加一块白色圆角底板整体看起来干净但所有元素都在二维码的安全范围内。建议你也先从最稳妥的黑白组合起步一点一点加装饰每次改动都用真机扫一遍再继续。工具只是帮你省掉底层绘图的功夫真正决定二维码成品的还是你对识别容错边界的理解。