【flutter for open harmony】第三方库 Flutter 二维码生成的鸿蒙化适配与实战指南
这篇文章介绍了如何在Flutter应用中实现二维码生成功能。主要内容包括: 二维码应用场景:商品码、联系码、支付码、WiFi码等常见用途 技术选型:推荐使用qr_flutter插件,功能强大且样式丰富 环境配置:添加qr_flutter、share_plus等依赖项 核心实现: 创建可自定义内容的二维码生成器 支持多种颜色选择 包含分享二维码图片功能 提供复制到剪贴板功能 文章提供了完整的Dart
·
Flutter 二维码生成的鸿蒙化适配与实战指南
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
各位小伙伴们好呀!👋 我是那个上海某高校的大一计算机学生,继续来给大家分享 Flutter for OpenHarmony 开发的学习心得!
今天要聊的是 二维码生成功能!📱📱📱
二维码现在真的太常见了:
- 🛒 商品码:扫一扫了解商品详情
- 📲 分享码:分享 App 给朋友
- 🔐 支付码:扫码支付
- 📶 WiFi 码:扫一扫连 WiFi
今天就给大家详细分享一下如何用 Flutter 生成漂亮的二维码!
一、功能引入介绍 📱
1.1 二维码的应用场景
| 场景 | 说明 | 示例 |
|---|---|---|
| 商品码 | 标识商品信息 | 淘宝商品码 |
| 联系码 | 分享联系方式 | 微信名片 |
| 支付码 | 扫码支付 | 支付宝/微信支付 |
| WiFi 码 | 分享 WiFi | 餐厅 WiFi |
| 小程序码 | 打开小程序 | 微信小程序 |
| URL 码 | 跳转网页 | 活动链接 |
1.2 Flutter 二维码方案
| 插件 | 优点 | 缺点 |
|---|---|---|
| qr_flutter | 功能强大、样式丰富 ✅ | - |
| qr_code_scanner | 支持扫描 | 需要相机权限 |
| mobile_scanner | 新版相机插件 | 配置复杂 |
我们选择 qr_flutter!
二、环境与依赖配置 🔧
2.1 pubspec.yaml 依赖
dependencies:
flutter:
sdk: flutter
# ========== 二维码 ==========
qr_flutter: ^4.1.0
# ========== 分享 ==========
share_plus: ^10.1.4
# ========== 文件 ==========
path_provider: ^2.1.4
# ========== 动画 ==========
flutter_animate: ^4.5.0
三、分步实现完整代码 🚀
3.1 二维码页面完整实现
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:share_plus/share_plus.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:io';
import 'package:flutter_animate/flutter_animate.dart';
/// 二维码生成页面
///
/// 支持:
/// - 生成任意内容的二维码
/// - 自定义颜色
/// - 复制链接
/// - 分享二维码图片
class QRCodePage extends StatefulWidget {
final String? initialData;
final String? title;
const QRCodePage({
super.key,
this.initialData,
this.title,
});
State<QRCodePage> createState() => _QRCodePageState();
}
class _QRCodePageState extends State<QRCodePage> {
/// 文本控制器
final TextEditingController _dataController = TextEditingController();
/// 当前二维码内容
String _currentData = '';
/// 选中的颜色
Color _selectedColor = const Color(0xFF6366F1);
/// 可选颜色列表
final List<Color> _availableColors = [
const Color(0xFF6366F1), // 靛蓝
const Color(0xFF8B5CF6), // 紫色
const Color(0xFFEC4899), // 粉色
const Color(0xFFEF4444), // 红色
const Color(0xFFF97316), // 橙色
const Color(0xFF10B981), // 绿色
const Color(0xFF06B6D4), // 青色
const Color(0xFF3B82F6), // 蓝色
const Color(0xFF1E293B), // 深灰
Colors.black,
];
void initState() {
super.initState();
if (widget.initialData != null) {
// 有初始数据,使用初始数据
_currentData = widget.initialData!;
_dataController.text = widget.initialData!;
} else {
// 使用默认数据
_currentData = 'https://my-ohos-app.com';
_dataController.text = _currentData;
}
}
void dispose() {
_dataController.dispose();
super.dispose();
}
/// 更新二维码
void _updateQRCode() {
setState(() {
_currentData = _dataController.text.trim();
if (_currentData.isEmpty) {
_currentData = 'https://my-ohos-app.com';
}
});
}
/// 分享二维码
void _shareQRCode() async {
if (_currentData.isEmpty) return;
try {
// 创建二维码图片
final qrPainter = QrPainter(
data: _currentData,
version: QrVersions.auto,
// 眼睛样式
eyeStyle: QrEyeStyle(
eyeShape: QrEyeShape.square,
color: _selectedColor,
),
// 数据模块样式
dataModuleStyle: QrDataModuleStyle(
dataModuleShape: QrDataModuleShape.square,
color: _selectedColor,
),
);
// 转换为图片数据
final picData = await qrPainter.toImageData(300);
if (picData == null) return;
// 保存到临时文件
final tempDir = await getTemporaryDirectory();
final file = File('${tempDir.path}/qr_code.png');
await file.writeAsBytes(picData.buffer.asUint8List());
// 分享
await Share.shareXFiles(
[XFile(file.path)],
text: '扫描二维码: $_currentData',
);
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('分享失败: $e')),
);
}
}
}
/// 复制到剪贴板
void _copyToClipboard() {
Clipboard.setData(ClipboardData(text: _currentData));
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('已复制到剪贴板'),
behavior: SnackBarBehavior.floating,
duration: Duration(seconds: 1),
),
);
}
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF8FAFC),
// AppBar
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 1,
title: Text(
widget.title ?? '生成二维码',
style: const TextStyle(
color: Color(0xFF1E293B),
fontWeight: FontWeight.w600,
),
),
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Color(0xFF1E293B)),
onPressed: () => Navigator.pop(context),
),
actions: [
IconButton(
icon: const Icon(Icons.share, color: Color(0xFF1E293B)),
onPressed: _shareQRCode,
),
],
),
// 页面主体
body: SingleChildScrollView(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// ============ 1. 二维码预览 ============
_buildQRPreview()
.animate()
.fadeIn(duration: 300.ms)
.scale(begin: const Offset(0.9, 0.9), end: const Offset(1, 1)),
const SizedBox(height: 24),
// ============ 2. 内容输入 ============
_buildDataInput()
.animate()
.fadeIn(delay: 100.ms, duration: 300.ms)
.slideY(begin: 0.2, end: 0),
const SizedBox(height: 24),
// ============ 3. 颜色选择器 ============
_buildColorPicker()
.animate()
.fadeIn(delay: 200.ms, duration: 300.ms)
.slideY(begin: 0.2, end: 0),
const SizedBox(height: 24),
// ============ 4. 快捷操作 ============
_buildQuickActions()
.animate()
.fadeIn(delay: 300.ms, duration: 300.ms)
.slideY(begin: 0.2, end: 0),
],
),
),
);
}
/// 二维码预览组件
Widget _buildQRPreview() {
return Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.05),
blurRadius: 20,
offset: const Offset(0, 4),
),
],
),
child: Column(
children: [
// 二维码容器
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.grey.shade200),
),
child: QrImageView(
data: _currentData,
version: QrVersions.auto, // 自动版本
size: 200, // 二维码大小
backgroundColor: Colors.white,
// 眼睛样式(四个角落的方块)
eyeStyle: QrEyeStyle(
eyeShape: QrEyeShape.square, // 方形眼睛
color: _selectedColor,
),
// 数据模块样式(小方块)
dataModuleStyle: QrDataModuleStyle(
dataModuleShape: QrDataModuleShape.square,
color: _selectedColor,
),
),
),
const SizedBox(height: 16),
// 内容摘要
Text(
_currentData.length > 30
? '${_currentData.substring(0, 30)}...'
: _currentData,
style: TextStyle(
color: Colors.grey[600],
fontSize: 12,
),
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
);
}
/// 数据输入组件
Widget _buildDataInput() {
return Container(
padding: const EdgeInsets.all(20),
decoration: _cardDecoration(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 标题
const Row(
children: [
Icon(Icons.edit_note, color: Color(0xFF6366F1), size: 20),
SizedBox(width: 8),
Text(
'内容',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Color(0xFF1E293B),
),
),
],
),
const SizedBox(height: 12),
// 输入框
TextField(
controller: _dataController,
maxLines: 3,
decoration: InputDecoration(
hintText: '输入文本、链接或任意数据',
hintStyle: TextStyle(color: Colors.grey[400]),
filled: true,
fillColor: const Color(0xFFF8FAFC),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(
color: Color(0xFF6366F1),
width: 2,
),
),
contentPadding: const EdgeInsets.all(16),
),
onChanged: (_) => _updateQRCode(),
),
const SizedBox(height: 12),
// 快捷输入按钮
_buildQuickDataButtons(),
],
),
);
}
/// 快捷数据按钮
Widget _buildQuickDataButtons() {
return Wrap(
spacing: 8,
runSpacing: 8,
children: [
_buildQuickChip('商品链接', () {
_dataController.text = 'https://shop.example.com/product/12345';
_updateQRCode();
}),
_buildQuickChip('邀请码', () {
_dataController.text = 'INVITE:ABC123XYZ';
_updateQRCode();
}),
_buildQuickChip('联系方式', () {
_dataController.text = 'TEL:13800138000';
_updateQRCode();
}),
_buildQuickChip('WiFi', () {
_dataController.text = 'WIFI:T:WPA;P:password;;';
_updateQRCode();
}),
],
);
}
/// 快捷标签按钮
Widget _buildQuickChip(String label, VoidCallback onTap) {
return GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: const Color(0xFF6366F1).withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(20),
),
child: Text(
label,
style: const TextStyle(
color: Color(0xFF6366F1),
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
),
);
}
/// 颜色选择器
Widget _buildColorPicker() {
return Container(
padding: const EdgeInsets.all(20),
decoration: _cardDecoration(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Row(
children: [
Icon(Icons.palette, color: Color(0xFF6366F1), size: 20),
SizedBox(width: 8),
Text(
'颜色',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Color(0xFF1E293B),
),
),
],
),
const SizedBox(height: 16),
// 颜色网格
Wrap(
spacing: 12,
runSpacing: 12,
children: _availableColors.map((color) {
final isSelected = _selectedColor == color;
return GestureDetector(
onTap: () => setState(() => _selectedColor = color),
child: Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: color,
shape: BoxShape.circle,
border: Border.all(
color: isSelected ? Colors.white : Colors.transparent,
width: 3,
),
boxShadow: isSelected
? [
BoxShadow(
color: color.withValues(alpha: 0.5),
blurRadius: 8,
spreadRadius: 2,
),
]
: null,
),
child: isSelected
? const Icon(Icons.check, color: Colors.white, size: 20)
: null,
),
);
}).toList(),
),
],
),
);
}
/// 快捷操作
Widget _buildQuickActions() {
return Container(
padding: const EdgeInsets.all(20),
decoration: _cardDecoration(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Row(
children: [
Icon(Icons.flash_on, color: Color(0xFF6366F1), size: 20),
SizedBox(width: 8),
Text(
'快捷操作',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Color(0xFF1E293B),
),
),
],
),
const SizedBox(height: 16),
Row(
children: [
// 复制按钮
Expanded(
child: _buildActionButton(
icon: Icons.copy,
label: '复制',
onTap: _copyToClipboard,
),
),
const SizedBox(width: 12),
// 分享按钮
Expanded(
child: _buildActionButton(
icon: Icons.share,
label: '分享',
onTap: _shareQRCode,
isPrimary: true,
),
),
],
),
],
),
);
}
/// 操作按钮
Widget _buildActionButton({
required IconData icon,
required String label,
required VoidCallback onTap,
bool isPrimary = false,
}) {
return GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.symmetric(vertical: 14),
decoration: BoxDecoration(
color: isPrimary
? const Color(0xFF6366F1)
: const Color(0xFFF8FAFC),
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
icon,
size: 20,
color: isPrimary ? Colors.white : const Color(0xFF6366F1),
),
const SizedBox(width: 8),
Text(
label,
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
color: isPrimary ? Colors.white : const Color(0xFF6366F1),
),
),
],
),
),
);
}
/// 卡片装饰
BoxDecoration _cardDecoration() {
return BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
);
}
}
3.2 二维码数据模型
/// 二维码数据类型
enum QRCodeType {
text, // 普通文本
url, // 网址链接
wifi, // WiFi 信息
phone, // 电话号码
sms, // 短信
email, // 邮箱
vcard, // 电子名片
}
/// 二维码数据模型
class QRCodeData {
/// 二维码内容
final String data;
/// 数据类型
final QRCodeType type;
/// 创建时间
final DateTime createdAt;
QRCodeData({
required this.data,
required this.type,
DateTime? createdAt,
}) : createdAt = createdAt ?? DateTime.now();
/// 转换为 JSON
Map<String, dynamic> toJson() => {
'data': data,
'type': type.name,
'createdAt': createdAt.toIso8601String(),
};
/// 从 JSON 创建
factory QRCodeData.fromJson(Map<String, dynamic> json) => QRCodeData(
data: json['data'] as String,
type: QRCodeType.values.firstWhere(
(e) => e.name == json['type'],
orElse: () => QRCodeType.text,
),
createdAt: DateTime.parse(json['createdAt'] as String),
);
}
/// 二维码辅助工具
class QRCodeHelper {
/// 生成 WiFi 二维码
///
/// [ssid] WiFi 名称
/// [password] WiFi 密码
/// [encryption] 加密类型 (WPA, WEP, nopass)
static String generateWiFi({
required String ssid,
String? password,
String encryption = 'WPA',
}) {
if (password == null || password.isEmpty) {
return 'WIFI:T:nopass;S:$ssid;;';
}
return 'WIFI:T:$encryption;S:$ssid;P:$password;;';
}
/// 生成电话二维码
static String generatePhone(String phoneNumber) {
return 'TEL:$phoneNumber';
}
/// 生成短信二维码
static String generateSMS(String phoneNumber, {String? message}) {
if (message == null || message.isEmpty) {
return 'SMSTO:$phoneNumber';
}
return 'SMSTO:$phoneNumber:$message';
}
/// 生成邮箱二维码
static String generateEmail(String email, {String? subject, String? body}) {
String result = 'mailto:$email';
final params = <String>[];
if (subject != null) params.add('subject=${Uri.encodeComponent(subject)}');
if (body != null) params.add('body=${Uri.encodeComponent(body)}');
if (params.isNotEmpty) {
result += '?${params.join('&')}';
}
return result;
}
/// 生成 vCard 名片
static String generateVCard({
String? name,
String? phone,
String? email,
String? org,
}) {
final buffer = StringBuffer('BEGIN:VCARD\n');
buffer.writeln('VERSION:3.0');
if (name != null) buffer.writeln('FN:$name');
if (phone != null) buffer.writeln('TEL:$phone');
if (email != null) buffer.writeln('EMAIL:$email');
if (org != null) buffer.writeln('ORG:$org');
buffer.write('END:VCARD');
return buffer.toString();
}
}
四,开发踩坑与挫折 😤
4.1 踩坑一:二维码太复杂无法识别
问题描述:
生成的二维码用手机扫不出来。
原因分析:
数据内容太多,二维码太密集。
解决方案:
- 使用短链接
- 减少数据量
- 使用更高的 QR 版本
// 指定更高的版本
QrImageView(
data: _currentData,
version: QrVersions.auto, // 自动选择最合适的版本
// 或者手动指定
// version: 5, // 版本 1-40
)
4.2 踩坑二:白色边框不够
问题描述:
二维码边缘被截断。
解决方案:
确保二维码周围有足够的空白区域:
QrImageView(
data: _currentData,
size: 200,
// QrImageView 默认有 4 个模块的边距
// 如果还是不够,可以在外面加 Container 设置 padding
)
4.3 踩坑三:深色背景上用深色二维码
问题描述:
深色背景上生成的深色二维码扫不出来。
解决方案:
确保二维码颜色和背景有足够对比度:
Container(
color: Colors.black, // 深色背景
child: QrImageView(
data: _currentData,
backgroundColor: Colors.black,
eyeStyle: QrEyeStyle(color: Colors.white), // 白色眼睛
dataModuleStyle: QrDataModuleStyle(color: Colors.white), // 白色数据点
),
)
五、最终实现效果 📸
(此处附鸿蒙设备上成功运行的截图)
—
六、个人学习总结 📝
通过二维码功能的学习,我收获了很多:
- ✅ 学会了 qr_flutter 的各种配置
- ✅ 学会了自定义二维码样式
- ✅ 学会了分享二维码图片
二维码虽小,但功能很强大!这个小功能真的很有用!
💡 提示:完整代码已开源至 AtomGit,欢迎 Star 和 Fork!
🔗 仓库地址:https://atomgit.com
作者:上海某高校大一学生,Flutter 爱好者
发布时间:2026年4月
更多推荐




所有评论(0)