Flutter 鸿蒙名片:三方库构建可分享的电子名片
摘要: 本文介绍如何开发一个基于Flutter的鸿蒙跨平台电子名片应用。项目使用qr_flutter生成vCard二维码,通过share_plus_ohos实现分享功能。开发环境需配置Flutter 3.16+、DevEco Studio 6.0+和鸿蒙6.0+SDK。关键步骤包括:创建项目、配置依赖(特别注意UTF-8编码)、设置鸿蒙网络权限,以及实现包含表单输入、二维码生成和分享功能的核心界面
欢迎加入开源鸿蒙跨平台社区: https://openharmonycrossplatform.csdn.net
项目概览
本实践将带你一步一步构建一个Flutter 鸿蒙电子名片应用。用户填写姓名、电话、邮箱等信息后,点击按钮即可生成一张包含 vCard 信息的二维码,其他手机扫码就能直接保存联系人;同时支持将名片文本一键分享到微信、QQ 等应用。
涉及的三方库:
-
qr\_flutter: 纯 Dart 二维码生成,无需平台桥接,鸿蒙端完美运行 -
share\_plus+share\_plus\_ohos: 调用系统原生分享面板
准备工作:搭建 Flutter 鸿蒙开发环境
环境要求:
-
Flutter SDK ≥ 3.16.0
-
DevEco Studio 6.0.0 及以上
-
HarmonyOS SDK API 20 及以上 (对应鸿蒙 6.0+)
-
Java JDK 17
在终端验证环境:
flutter --version
flutter doctor -v
确保 ohos 相关项无报错。
步骤一:创建 Flutter 鸿蒙项目
打开终端,执行:
flutter create --platforms ohos flutter_harmony_card
cd flutter_harmony_card
项目根目录下会生成 ohos/ 文件夹,即鸿蒙原生工程。
步骤二:配置pubspec\.yaml 并安装依赖
⚠️ 编码警告:请务必使用 VS Code 或 DevEco Studio 打开文件,并确认保存为 UTF-8 (无 BOM) 编码,否则 flutter pub get 会报错!
VS Code:右下角点击编码 → 通过编码保存→ UTF-8
DevEco Studio:File → File Properties → File Encoding → UTF-8
用编辑器打开根目录下的 pubspec\.yaml,清空全部内容,粘贴以下代码:
name: flutter_harmony_card
description: Flutter 鸿蒙电子名片分享应用
publish_to: 'none'
version: 1.0.0+1
environment:
sdk: '>=3.2.0 <4.0.0'
dependencies:
flutter:
sdk: flutter
# 二维码生成 - 纯 Dart 实现,鸿蒙端无适配负担
qr_flutter: ^4.1.0
# 分享接口 (跨平台通用接口)
share_plus: ^10.1.2
# 分享功能鸿蒙专属实现 (必需)
share_plus_ohos:
git:
url: https://atomgit.com/Chyuning/share_plus_ohos.git
dev_dependencies:
flutter_test:
sdk: flutter
flutter:
uses-material-design: true
保存文件后,在终端执行:
flutter pub get
看到 Got dependencies\! 即表示安装成功。
步骤三:配置鸿蒙权限 (已修复资源引用问题)
用编辑器打开 ohos/entry/src/main/module\.json5,找到 requestPermissions 字段,将其修改为以下内容(直接使用字面量,不依赖字符串资源,避免找不到符号):
"requestPermissions": [
{
"name": "ohos.permission.INTERNET",
"reason": "用于生成二维码和分享名片",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "always"
}
}
]
如文件中原本已有其他权限,请合并保留。
步骤四:编写应用代码
打开 lib/main\.dart,同样确保编码为 UTF-8 后,清空文件,粘贴以下完整代码:
import 'package:flutter/material.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:share_plus/share_plus.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: '鸿蒙电子名片',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorSchemeSeed: const Color(0xFF007BFF),
useMaterial3: true,
brightness: Brightness.light,
),
home: const CardHomePage(),
);
}
}
class CardHomePage extends StatefulWidget {
const CardHomePage({super.key});
State<CardHomePage> createState() => _CardHomePageState();
}
class _CardHomePageState extends State<CardHomePage> {
final _nameController = TextEditingController();
final _phoneController = TextEditingController();
final _emailController = TextEditingController();
final _orgController = TextEditingController();
String _qrData = '';
void dispose() {
_nameController.dispose();
_phoneController.dispose();
_emailController.dispose();
_orgController.dispose();
super.dispose();
}
String _buildVCard() {
final name = _nameController.text.trim();
final phone = _phoneController.text.trim();
final email = _emailController.text.trim();
final org = _orgController.text.trim();
return '''
BEGIN:VCARD
VERSION:3.0
N:;$name
FN:$name
TEL;TYPE=CELL:$phone
EMAIL:$email
ORG:$org
END:VCARD'''
.split('\n')
.map((line) => line.trim())
.join('\n');
}
Future<void> _shareCard() async {
final vCardText = _buildVCard();
final shareText = '这是我的电子名片:\n\n$vCardText';
try {
await Share.share(
shareText,
subject: '${_nameController.text.trim()} 的电子名片',
);
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('分享失败:$e')),
);
}
}
}
void _generateQR() {
setState(() {
_qrData = _buildVCard();
});
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('鸿蒙电子名片'),
centerTitle: true,
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'📇 名片信息',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 12),
TextField(
controller: _nameController,
decoration: const InputDecoration(
labelText: '姓名',
hintText: '请输入您的姓名',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.person),
),
),
const SizedBox(height: 12),
TextField(
controller: _phoneController,
keyboardType: TextInputType.phone,
decoration: const InputDecoration(
labelText: '电话',
hintText: '请输入您的手机号',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.phone),
),
),
const SizedBox(height: 12),
TextField(
controller: _emailController,
keyboardType: TextInputType.emailAddress,
decoration: const InputDecoration(
labelText: '邮箱',
hintText: '请输入您的电子邮箱',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.email),
),
),
const SizedBox(height: 12),
TextField(
controller: _orgController,
decoration: const InputDecoration(
labelText: '公司/组织',
hintText: '请输入您的公司或组织名称',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.business),
),
),
const SizedBox(height: 16),
SizedBox(
width: double.infinity,
child: FilledButton.icon(
onPressed: _generateQR,
icon: const Icon(Icons.qr_code_2),
label: const Text('生成二维码'),
),
),
],
),
),
),
const SizedBox(height: 24),
if (_qrData.isNotEmpty) ...[
Card(
elevation: 3,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
children: [
Text(
'📱 扫描保存联系人',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: Colors.grey.shade300,
),
),
child: QrImageView(
data: _qrData,
version: QrVersions.auto,
size: 220.0,
eyeStyle: const QrEyeStyle(
eyeShape: QrEyeShape.circle,
color: Color(0xFF007BFF),
),
dataModuleStyle: const QrDataModuleStyle(
dataModuleShape: QrDataModuleShape.circle,
color: Color(0xFF007BFF),
),
),
),
const SizedBox(height: 16),
Text(
'使用手机相机或微信扫码即可保存',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Colors.grey,
),
),
const SizedBox(height: 16),
SizedBox(
width: double.infinity,
child: OutlinedButton.icon(
onPressed: _shareCard,
icon: const Icon(Icons.share),
label: const Text('分享名片给好友'),
),
),
],
),
),
),
],
],
),
),
);
}
}
步骤五:运行与测试
1. 启动鸿蒙设备
-
模拟器:DevEco Studio → Tools → Device Manager,启动 API 20+ 的模拟器
-
真机:USB 连接,开启开发者模式
2. 查看设备
flutter devices
3. 运行应用
flutter run -d <device_id>
4. 功能测试
-
打开应用,填写姓名、电话、邮箱、公司
-
点击 生成二维码,下方出现圆形二维码
-
用另一台手机扫码,应弹出新建联系人界面
-
点击 分享名片给好友,测试分享至微信或 QQ




扫码后,直接出现刚刚分享的信息。

点击保存后,跳转到联系人,直接开始新建。整个过程方便快捷。
核心原理补充
-
qr_flutter 完全使用 Dart 的
CustomPainter绘制二维码矩阵,不依赖原生代码,因此鸿蒙端无需额外适配。 -
share_plus_ohos 将 Flutter 分享调用映射为鸿蒙原生 Want 机制,开发者无需编写一行 ArkTS 即可调用系统分享。
延展学习方向
-
嵌入头像:使用
embeddedImage参数在二维码中央加入 Logo -
反向扫码:集成鸿蒙适配版
mobile\_scanner,实现扫他人二维码直接保存联系人 -
名片持久化:用
shared\_preferences保存上次输入 -
多张名片管理:引入
sqflite,实现名片增删改查
更多推荐


所有评论(0)