Flutter 框架跨平台鸿蒙开发 - 打造二维码名片应用
vCard标准:符合国际标准的电子名片格式二维码技术:qr_flutter生成、mobile_scanner扫描数据持久化:SharedPreferences本地存储JSON序列化:数据模型的序列化和反序列化相机操作:调用相机扫描二维码图片分享:RepaintBoundary转换和分享通过本项目,你不仅学会了如何实现二维码名片应用,还掌握了Flutter中二维码处理、相机操作、数据存储的核心技术。
Flutter实战:打造二维码名片应用
前言
二维码名片是一款现代化的名片管理应用,通过二维码技术实现快速交换联系方式。本文将带你从零开始,使用Flutter开发一个功能完整的二维码名片应用,支持个人信息展示、二维码生成、扫码添加联系人等功能。
应用特色
- 📇 个人名片:创建和编辑个人名片信息
- 🎨 多彩主题:8种名片颜色主题可选
- 📱 二维码生成:自动生成vCard格式二维码
- 📷 扫码添加:扫描二维码快速添加联系人
- 💾 本地存储:名片和联系人本地保存
- 📤 分享功能:分享二维码图片
- 📋 信息复制:一键复制名片信息
- 👥 联系人管理:查看、删除联系人
- 🔍 详情查看:查看联系人完整信息
- 🎯 vCard标准:符合vCard 3.0标准
效果展示


数据模型设计
名片信息模型
class BusinessCard {
String id;
String name;
String title;
String company;
String phone;
String email;
String wechat;
String address;
String website;
String note;
DateTime createdAt;
int cardColor;
BusinessCard({
required this.id,
required this.name,
this.title = '',
this.company = '',
this.phone = '',
this.email = '',
this.wechat = '',
this.address = '',
this.website = '',
this.note = '',
required this.createdAt,
this.cardColor = 0xFF2196F3,
});
}
字段说明:
id:唯一标识符name:姓名(必填)title:职位company:公司phone:电话email:邮箱wechat:微信号address:地址website:网站note:备注createdAt:创建时间cardColor:名片颜色
核心功能实现
1. vCard格式转换
vCard是电子名片的国际标准格式:
String toVCard() {
return '''BEGIN:VCARD
VERSION:3.0
FN:$name
TITLE:$title
ORG:$company
TEL:$phone
EMAIL:$email
X-WECHAT:$wechat
ADR:$address
URL:$website
NOTE:$note
END:VCARD''';
}
vCard标准字段:
BEGIN:VCARD/END:VCARD:开始和结束标记VERSION:3.0:vCard版本FN:全名(Full Name)TITLE:职位ORG:组织/公司TEL:电话EMAIL:邮箱X-WECHAT:自定义字段(微信)ADR:地址URL:网站NOTE:备注
2. vCard解析
从二维码扫描结果解析vCard:
factory BusinessCard.fromVCard(String vcard) {
final lines = vcard.split('\n');
String name = '';
String title = '';
String company = '';
String phone = '';
String email = '';
String wechat = '';
String address = '';
String website = '';
String note = '';
for (var line in lines) {
if (line.startsWith('FN:')) {
name = line.substring(3);
} else if (line.startsWith('TITLE:')) {
title = line.substring(6);
} else if (line.startsWith('ORG:')) {
company = line.substring(4);
} else if (line.startsWith('TEL:')) {
phone = line.substring(4);
} else if (line.startsWith('EMAIL:')) {
email = line.substring(6);
} else if (line.startsWith('X-WECHAT:')) {
wechat = line.substring(9);
} else if (line.startsWith('ADR:')) {
address = line.substring(4);
} else if (line.startsWith('URL:')) {
website = line.substring(4);
} else if (line.startsWith('NOTE:')) {
note = line.substring(5);
}
}
return BusinessCard(
id: DateTime.now().millisecondsSinceEpoch.toString(),
name: name,
title: title,
company: company,
phone: phone,
email: email,
wechat: wechat,
address: address,
website: website,
note: note,
createdAt: DateTime.now(),
);
}
解析流程:
- 按行分割vCard文本
- 遍历每一行,识别字段前缀
- 提取字段值(去除前缀)
- 创建BusinessCard对象
3. 二维码生成
使用qr_flutter生成二维码:
QrImageView(
data: card.toVCard(),
version: QrVersions.auto,
size: 200,
backgroundColor: Colors.white,
)
参数说明:
data:要编码的数据(vCard字符串)version:二维码版本(auto自动选择)size:二维码尺寸backgroundColor:背景颜色
4. 二维码扫描
使用mobile_scanner扫描二维码:
MobileScanner(
controller: cameraController,
onDetect: _onDetect,
)
void _onDetect(BarcodeCapture capture) {
if (_isProcessing) return;
final List<Barcode> barcodes = capture.barcodes;
if (barcodes.isEmpty) return;
final barcode = barcodes.first;
final String? code = barcode.rawValue;
if (code != null && code.isNotEmpty) {
_isProcessing = true;
try {
if (code.startsWith('BEGIN:VCARD')) {
final card = BusinessCard.fromVCard(code);
widget.onScanned(card);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('已添加 ${card.name}')),
);
Navigator.pop(context);
}
} else {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('无效的名片二维码')),
);
}
_isProcessing = false;
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('解析失败: $e')),
);
}
_isProcessing = false;
}
}
}
扫描流程:
- 检测到二维码后触发
onDetect - 获取二维码内容
- 验证是否为vCard格式
- 解析vCard创建名片
- 添加到联系人列表
- 显示提示并返回
防重复处理:
- 使用
_isProcessing标志防止重复扫描 - 扫描成功后立即返回页面
5. 数据持久化
使用SharedPreferences保存数据:
Future<void> _loadData() async {
final prefs = await SharedPreferences.getInstance();
// 加载我的名片
final myCardJson = prefs.getString('my_card');
if (myCardJson != null) {
setState(() {
_myCard = BusinessCard.fromJson(json.decode(myCardJson));
});
}
// 加载联系人
final contactsJson = prefs.getString('contacts');
if (contactsJson != null) {
final List<dynamic> decoded = json.decode(contactsJson);
setState(() {
_contacts = decoded.map((item) => BusinessCard.fromJson(item)).toList();
});
}
}
Future<void> _saveMyCard(BusinessCard card) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('my_card', json.encode(card.toJson()));
setState(() {
_myCard = card;
});
}
Future<void> _saveContacts() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString(
'contacts',
json.encode(_contacts.map((c) => c.toJson()).toList()),
);
}
UI组件设计
1. 名片预览
Widget _buildCardPreview(BuildContext context, BusinessCard card) {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Color(card.cardColor),
Color(card.cardColor).withValues(alpha: 0.7),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.2),
blurRadius: 10,
offset: const Offset(0, 5),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
card.name,
style: const TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
if (card.title.isNotEmpty) ...[
const SizedBox(height: 8),
Text(
card.title,
style: const TextStyle(
fontSize: 16,
color: Colors.white,
),
),
],
// ... 其他信息
],
),
);
}
设计要点:
- 渐变背景:使用LinearGradient创建渐变效果
- 圆角卡片:BorderRadius.circular(16)
- 阴影效果:BoxShadow增加立体感
- 条件显示:只显示非空字段
2. 二维码展示
Widget _buildQRCode(BuildContext context, BusinessCard card) {
final qrKey = GlobalKey();
return Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: Theme.of(context).colorScheme.outline,
),
),
child: Column(
children: [
const Text(
'扫描二维码添加我',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
RepaintBoundary(
key: qrKey,
child: Container(
padding: const EdgeInsets.all(16),
color: Colors.white,
child: QrImageView(
data: card.toVCard(),
version: QrVersions.auto,
size: 200,
backgroundColor: Colors.white,
),
),
),
const SizedBox(height: 16),
FilledButton.icon(
onPressed: () => _shareQRCode(context, qrKey, card),
icon: const Icon(Icons.share),
label: const Text('分享二维码'),
),
],
),
);
}
RepaintBoundary的作用:
- 创建独立渲染层
- 可以转换为图片
- 用于分享二维码
3. 编辑表单
Form(
key: _formKey,
child: ListView(
padding: const EdgeInsets.all(16),
children: [
TextFormField(
controller: _nameController,
decoration: const InputDecoration(
labelText: '姓名 *',
prefixIcon: Icon(Icons.person),
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入姓名';
}
return null;
},
),
const SizedBox(height: 16),
TextFormField(
controller: _titleController,
decoration: const InputDecoration(
labelText: '职位',
prefixIcon: Icon(Icons.work),
border: OutlineInputBorder(),
),
),
// ... 其他字段
],
),
)
表单验证:
- 姓名为必填字段
- 使用validator进行验证
- 其他字段为可选
4. 颜色选择器
Wrap(
spacing: 12,
runSpacing: 12,
children: _cardColors.map((color) {
return InkWell(
onTap: () {
setState(() {
_selectedColor = color;
});
},
child: Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: Color(color),
shape: BoxShape.circle,
border: Border.all(
color: _selectedColor == color
? Colors.black
: Colors.transparent,
width: 3,
),
),
child: _selectedColor == color
? const Icon(Icons.check, color: Colors.white)
: null,
),
);
}).toList(),
)
8种预设颜色:
- Blue (蓝色)
- Green (绿色)
- Orange (橙色)
- Pink (粉色)
- Purple (紫色)
- Cyan (青色)
- Deep Orange (深橙)
- Blue Grey (蓝灰)
5. 扫描界面
Stack(
children: [
MobileScanner(
controller: cameraController,
onDetect: _onDetect,
),
Center(
child: Container(
width: 250,
height: 250,
decoration: BoxDecoration(
border: Border.all(
color: Colors.white,
width: 2,
),
borderRadius: BorderRadius.circular(12),
),
),
),
Positioned(
bottom: 50,
left: 0,
right: 0,
child: Center(
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 12,
),
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.7),
borderRadius: BorderRadius.circular(24),
),
child: const Text(
'将二维码放入框内',
style: TextStyle(
color: Colors.white,
fontSize: 16,
),
),
),
),
),
],
)
界面元素:
- 相机预览:MobileScanner
- 扫描框:白色边框提示扫描区域
- 提示文字:底部提示信息
技术要点详解
1. vCard标准
vCard是电子名片的国际标准格式,广泛应用于:
- 通讯录导入导出
- 邮件签名
- 二维码名片
- NFC名片
vCard 3.0主要字段:
| 字段 | 说明 | 示例 |
|---|---|---|
| FN | 全名 | FN:张三 |
| N | 姓名结构 | N:张;三;;; |
| TITLE | 职位 | TITLE:产品经理 |
| ORG | 组织 | ORG:某某公司 |
| TEL | 电话 | TEL:13800138000 |
| 邮箱 | EMAIL:zhangsan@example.com | |
| ADR | 地址 | ADR:;;北京市朝阳区;;100000;中国 |
| URL | 网站 | URL:https://example.com |
| NOTE | 备注 | NOTE:欢迎交流 |
2. 二维码容量
二维码版本与容量关系:
| 版本 | 模块数 | 数字容量 | 字母容量 | 字节容量 |
|---|---|---|---|---|
| 1 | 21×21 | 41 | 25 | 17 |
| 10 | 57×57 | 652 | 395 | 271 |
| 20 | 97×97 | 1,852 | 1,125 | 773 |
| 40 | 177×177 | 7,089 | 4,296 | 2,953 |
vCard二维码建议:
- 控制信息长度在500字节以内
- 使用Version 10-15即可
- 过大的二维码难以扫描
3. JSON序列化
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
'title': title,
'company': company,
'phone': phone,
'email': email,
'wechat': wechat,
'address': address,
'website': website,
'note': note,
'createdAt': createdAt.toIso8601String(),
'cardColor': cardColor,
};
factory BusinessCard.fromJson(Map<String, dynamic> json) {
return BusinessCard(
id: json['id'],
name: json['name'],
title: json['title'] ?? '',
company: json['company'] ?? '',
phone: json['phone'] ?? '',
email: json['email'] ?? '',
wechat: json['wechat'] ?? '',
address: json['address'] ?? '',
website: json['website'] ?? '',
note: json['note'] ?? '',
createdAt: DateTime.parse(json['createdAt']),
cardColor: json['cardColor'] ?? 0xFF2196F3,
);
}
注意事项:
- DateTime使用ISO 8601格式
- 可选字段使用
??提供默认值 - Color使用int值存储
4. 相机权限
在AndroidManifest.xml中添加:
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
在Info.plist中添加:
<key>NSCameraUsageDescription</key>
<string>需要使用相机扫描二维码</string>
应用场景
1. 商务交流
class BusinessMeeting {
String meetingId;
List<BusinessCard> attendees;
DateTime meetingTime;
void exchangeCards(BusinessCard card1, BusinessCard card2) {
// 交换名片逻辑
}
}
2. 展会活动
class ExhibitionBooth {
String boothNumber;
BusinessCard exhibitorCard;
List<BusinessCard> visitors;
void collectVisitorCard(BusinessCard card) {
visitors.add(card);
}
}
3. 社交网络
class SocialProfile {
BusinessCard card;
String avatar;
List<String> socialLinks;
String generateProfileQR() {
// 生成包含社交链接的二维码
return card.toVCard();
}
}
功能扩展建议
1. NFC名片
import 'package:nfc_manager/nfc_manager.dart';
Future<void> writeNFC(BusinessCard card) async {
await NfcManager.instance.startSession(onDiscovered: (NfcTag tag) async {
var ndef = Ndef.from(tag);
if (ndef == null || !ndef.isWritable) {
return;
}
NdefMessage message = NdefMessage([
NdefRecord.createText(card.toVCard()),
]);
await ndef.write(message);
});
}
2. 名片模板
class CardTemplate {
String id;
String name;
String layout;
Map<String, dynamic> style;
Widget buildCard(BusinessCard card) {
// 根据模板渲染名片
return Container();
}
}
3. 批量导入
Future<List<BusinessCard>> importFromCSV(String csvPath) async {
final file = File(csvPath);
final lines = await file.readAsLines();
List<BusinessCard> cards = [];
for (var line in lines.skip(1)) {
final fields = line.split(',');
cards.add(BusinessCard(
id: DateTime.now().millisecondsSinceEpoch.toString(),
name: fields[0],
title: fields[1],
company: fields[2],
phone: fields[3],
email: fields[4],
createdAt: DateTime.now(),
));
}
return cards;
}
4. 云端同步
class CloudSync {
Future<void> uploadCard(BusinessCard card) async {
// 上传到云端
}
Future<List<BusinessCard>> downloadContacts() async {
// 从云端下载
return [];
}
Future<void> syncAll() async {
// 双向同步
}
}
5. 名片识别
import 'package:google_ml_kit/google_ml_kit.dart';
Future<BusinessCard> recognizeCard(String imagePath) async {
final inputImage = InputImage.fromFilePath(imagePath);
final textRecognizer = TextRecognizer();
final recognizedText = await textRecognizer.processImage(inputImage);
// 解析识别的文本
String name = '';
String phone = '';
String email = '';
for (var block in recognizedText.blocks) {
final text = block.text;
if (text.contains('@')) {
email = text;
} else if (RegExp(r'\d{11}').hasMatch(text)) {
phone = text;
}
}
return BusinessCard(
id: DateTime.now().millisecondsSinceEpoch.toString(),
name: name,
phone: phone,
email: email,
createdAt: DateTime.now(),
);
}
6. 统计分析
class CardAnalytics {
int getTotalContacts() {
return contacts.length;
}
Map<String, int> getContactsByCompany() {
Map<String, int> stats = {};
for (var contact in contacts) {
stats[contact.company] = (stats[contact.company] ?? 0) + 1;
}
return stats;
}
List<BusinessCard> getRecentContacts(int days) {
final cutoff = DateTime.now().subtract(Duration(days: days));
return contacts.where((c) => c.createdAt.isAfter(cutoff)).toList();
}
}
性能优化
1. 二维码缓存
class QRCodeCache {
static final Map<String, Uint8List> _cache = {};
static Future<Uint8List> getQRCode(String data) async {
if (_cache.containsKey(data)) {
return _cache[data]!;
}
// 生成二维码
final qrImage = await _generateQRImage(data);
_cache[data] = qrImage;
return qrImage;
}
static void clearCache() {
_cache.clear();
}
}
2. 图片压缩
Future<Uint8List> compressQRCode(Uint8List bytes) async {
final image = img.decodeImage(bytes);
if (image == null) return bytes;
final resized = img.copyResize(image, width: 500);
return Uint8List.fromList(img.encodePng(resized));
}
3. 懒加载
ListView.builder(
itemCount: contacts.length,
itemBuilder: (context, index) {
final contact = contacts[index];
return ListTile(
leading: CircleAvatar(
backgroundColor: Color(contact.cardColor),
child: Text(contact.name[0]),
),
title: Text(contact.name),
subtitle: Text(contact.company),
);
},
)
常见问题解答
Q1: 如何提高二维码扫描成功率?
A:
- 保持二维码清晰完整
- 控制信息长度
- 使用适当的纠错级别
- 保持良好的光线条件
Q2: vCard格式兼容性如何?
A: vCard 3.0是广泛支持的标准,兼容大多数通讯录应用。
Q3: 如何处理特殊字符?
A: 在vCard中,特殊字符需要转义,如逗号、分号等。
项目结构
lib/
├── main.dart # 主程序入口
├── models/
│ └── business_card.dart # 名片模型
├── screens/
│ ├── home_page.dart # 主页
│ ├── my_card_page.dart # 我的名片页
│ ├── edit_card_page.dart # 编辑名片页
│ ├── contacts_page.dart # 联系人页
│ └── qr_scanner_page.dart # 扫描页
├── widgets/
│ ├── card_preview.dart # 名片预览组件
│ ├── qr_code_widget.dart # 二维码组件
│ └── contact_item.dart # 联系人项组件
└── utils/
├── vcard_parser.dart # vCard解析工具
└── storage_helper.dart # 存储辅助工具
总结
本文实现了一个功能完整的二维码名片应用,涵盖了以下核心技术:
- vCard标准:符合国际标准的电子名片格式
- 二维码技术:qr_flutter生成、mobile_scanner扫描
- 数据持久化:SharedPreferences本地存储
- JSON序列化:数据模型的序列化和反序列化
- 相机操作:调用相机扫描二维码
- 图片分享:RepaintBoundary转换和分享
通过本项目,你不仅学会了如何实现二维码名片应用,还掌握了Flutter中二维码处理、相机操作、数据存储的核心技术。这些知识可以应用到更多需要二维码功能的应用开发。
扫码交换,快速建立联系!
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐





所有评论(0)