欢迎加入开源鸿蒙跨平台社区: 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,实现名片增删改查

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐