☰
Flutter高颜值二维码实战:pretty_qr_code自定义样式与Logo嵌入
2026/10/5 3:21:33 网站建设 项目流程

接了个需求,要给线下物料做一批二维码,甲方提了句:能不能别用那种黑底白点的方块了?说实话,市面上大多数二维码确实实用大于颜值——豆腐块、三个定位角、密密麻麻的模块,放在宣传海报上就像贴了张补丁。项目是 Flutter 的技术栈,我就顺手把主流的二维码方案都翻了一遍,最终锁定了 pretty_qr_code。

这个库能解决什么问题?简单说,就是让 Flutter 开发者不用自己去写 Canvas 绘制逻辑,也不用在样式上反复调半天。它支持自定义颜色、圆角定位眼、圆形数据模块、嵌入 Logo、加载网络图片,甚至通过 ImageProvider 把这些能力统一管理,最后产出一张可以直接展示、也能导出的二维码图片。适合谁看?Flutter 开发者,尤其是那些要在名片、海报、邀请函、包装码、活动物料里放一个不丢面子的二维码的人。

这篇文章不是 API 文档的复读,更多是我在实际接入过程中对参数取舍、识别率踩坑以及最终效果如何平衡的经验汇总。你可以把它当成一份“用 pretty_qr_code 做高颜值二维码”的实操笔记。

1. 为什么偏偏是 pretty_qr_code

1.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 嵌入 Logo:QrImageDecoration 的正确姿势

品牌二维码的核心需求就是放 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 State<QrExportPage> createState() => _QrExportPageState(); } class _QrExportPageState extends State<QrExportPage> { final GlobalKey _qrKey = GlobalKey(); Future<void> _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 加一块白色圆角底板,整体看起来干净,但所有元素都在二维码的安全范围内。建议你也先从最稳妥的黑白组合起步,一点一点加装饰,每次改动都用真机扫一遍再继续。工具只是帮你省掉底层绘图的功夫,真正决定二维码成品的,还是你对识别容错边界的理解。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询