欢迎加入开源鸿蒙跨平台社区:
https://openharmonycrossplatform.csdn.net


项目效果

本文实现的是一个基于 Flutter for OpenHarmony 的个人电子名片二维码生成应用。项目中使用 Flutter 第三方库 qr_flutter 实现二维码生成效果,用于展示个人姓名、职位、电话、邮箱、城市和个人简介等信息,并将这些内容组合成二维码。

最终运行效果如下:

在这里插入图片描述
在这里插入图片描述

页面主要包含以下内容:

  • 顶部标题栏;
  • 个人电子名片概览卡片;
  • 名片模板切换按钮;
  • 个人信息展示区域;
  • 二维码生成区域;
  • 二维码内容预览;
  • 复制信息提示区域;
  • 第三方库使用说明;
  • 页面整体采用 Flutter Material 风格布局。

本文重点是演示如何在 Flutter for OpenHarmony 项目中使用 Flutter 第三方库 qr_flutter。项目代码写在 lib/main.dart 中,依赖配置写在 pubspec.yaml 中,符合 Flutter for OpenHarmony 第三方库实践方向。


前言

在移动应用开发中,二维码是非常常见的功能。例如电子名片、活动签到、网页跳转、WiFi 分享、商品信息、校园通知、支付入口和资料交换等场景,都经常需要使用二维码。

如果用户需要手动输入电话、邮箱、地址和个人简介,体验会非常差。尤其是邮箱这种东西,多一个点、少一个下划线都能让信息传递失败。人类已经够容易出错了,没必要再给手动输入增加戏份。

二维码可以把一段文本、链接或结构化信息编码成图形。用户只需要扫描二维码,就可以快速读取信息。对于移动端应用来说,这是一种非常实用的轻量交互方式。

Flutter 原生并没有直接提供二维码生成组件。如果自己从底层实现二维码编码、纠错、矩阵绘制和图形渲染,工作量会比较大,也没有必要。毕竟只是想生成一个二维码,不是准备重新定义信息时代。

因此本文选择使用 Flutter 第三方库 qr_flutter 来实现二维码生成。它可以通过 QrImageView 组件快速把文本内容渲染为二维码,非常适合 Flutter for OpenHarmony 项目中的电子名片、资料分享和信息展示页面。

本项目以“个人电子名片二维码生成应用”为例,使用 qr_flutter 根据不同名片模板生成不同二维码,并结合信息展示、模板切换和内容预览完成一个完整页面。


一、项目目标

本次实践主要实现以下目标:

  • 创建 Flutter for OpenHarmony 项目;
  • pubspec.yaml 中添加第三方库 qr_flutter
  • 使用 flutter pub get 获取依赖;
  • lib/main.dart 中引入 qr_flutter
  • 使用 QrImageView 构建二维码组件;
  • 使用 QrVersions.auto 自动选择二维码版本;
  • 使用数据模型管理电子名片信息;
  • 实现不同名片模板切换;
  • 实现二维码内容动态更新;
  • 实现二维码内容预览;
  • 使用 Flutter Material 组件构建完整页面;
  • 将应用运行到 OpenHarmony 设备或模拟器中。

二、技术栈

类型 内容
开发方向 Flutter for OpenHarmony
开发语言 Dart
UI 框架 Flutter
第三方库 qr_flutter
功能场景 二维码生成 / 电子名片 / 信息分享
核心组件 QrImageView / QrVersions
项目入口 lib/main.dart
依赖配置 pubspec.yaml
运行平台 OpenHarmony 设备或模拟器

三、为什么选择 qr_flutter

在实际开发中,二维码可以用于很多场景,例如:

  • 个人电子名片;
  • 活动签到码;
  • 学生信息码;
  • WiFi 信息分享;
  • 商品信息展示;
  • 网页链接跳转;
  • 校园通知分享;
  • 文件资料入口;
  • 项目演示入口;
  • 用户邀请码展示。

如果只是展示一段普通文字,用户还需要手动复制或输入。而二维码可以让用户通过扫描快速获取内容,适合手机端信息传递。

qr_flutter 已经封装好了二维码生成能力,可以直接通过 Flutter Widget 渲染二维码。

在本项目中,qr_flutter 主要完成以下工作:

  • 根据电子名片文本生成二维码;
  • 自动处理二维码版本;
  • 在页面中直接展示二维码;
  • 支持切换不同名片模板后更新二维码;
  • 减少手写二维码绘制逻辑;
  • 让 Flutter for OpenHarmony 项目具备二维码信息分享能力。

四、创建 Flutter for OpenHarmony 项目

在已经配置好 Flutter for OpenHarmony 开发环境的前提下,可以创建一个 Flutter 项目。

示例项目名称:

flutter create qr_business_card_demo

进入项目目录:

cd qr_business_card_demo

项目创建完成后,主要关注两个文件:

qr_business_card_demo
 ├── pubspec.yaml
 └── lib
     └── main.dart

其中:

文件 作用
pubspec.yaml 配置 Flutter 项目依赖
lib/main.dart 编写 Flutter 页面和业务逻辑

五、添加 qr_flutter 第三方库

打开项目根目录下的 pubspec.yaml 文件,在 dependencies 中添加 qr_flutter

示例配置如下:

dependencies:
  flutter:
    sdk: flutter

  qr_flutter: ^4.1.0

完整结构大致如下:

name: qr_business_card_demo
description: A Flutter for OpenHarmony qr_flutter demo.
publish_to: 'none'

version: 1.0.0+1

environment:
  sdk: '>=3.4.0 <4.0.0'

dependencies:
  flutter:
    sdk: flutter

  qr_flutter: ^4.1.0

dev_dependencies:
  flutter_test:
    sdk: flutter

flutter:
  uses-material-design: true

添加完成后,在终端执行:

flutter pub get

执行成功后,就可以在 Dart 代码中使用 qr_flutter 了。


六、项目结构

本项目主要修改 lib/main.dart 文件:

lib
 └── main.dart

本项目不需要编写 OpenHarmony 原生 ArkTS 页面,也不需要修改 Index.ets

因为这是 Flutter for OpenHarmony 项目,页面主体应该是 Flutter 代码。审核重点会看:

  • 是否使用 pubspec.yaml 添加 Flutter 第三方库;
  • 是否在 Dart 文件中 import package
  • 是否在 lib/main.dart 中实际调用第三方库;
  • 是否属于 Flutter for OpenHarmony 项目。

看到 pubspec.yamllib/main.dartimport 'package:qr_flutter/qr_flutter.dart';,这才是正确方向。不是在文章标题里写 Flutter,代码就会自己变成 Flutter,世界还没这么配合。


七、核心实现思路

本项目的核心流程如下:

  1. pubspec.yaml 中添加 qr_flutter
  2. main.dart 中引入第三方库;
  3. 定义电子名片数据模型;
  4. 准备多个不同类型的名片模板;
  5. 使用 ChoiceChip 实现模板切换;
  6. 根据当前选中名片生成二维码文本;
  7. 使用 QrImageView 渲染二维码;
  8. 使用信息卡片展示当前名片内容;
  9. 使用内容预览区域展示二维码原始文本;
  10. 使用 Flutter Material 组件构建完整页面。

第三方库引入代码如下:

import 'package:qr_flutter/qr_flutter.dart';

二维码核心代码如下:

QrImageView(
  data: currentCard.qrContent,
  version: QrVersions.auto,
  size: 220,
  backgroundColor: Colors.white,
)

这段代码是本文重点,说明项目确实使用了 Flutter 第三方库实现二维码生成效果。


八、main.dart 完整代码

打开文件:

lib/main.dart

将其中内容替换为下面代码:

import 'package:flutter/material.dart';
import 'package:qr_flutter/qr_flutter.dart';

void main() {
  runApp(const QrBusinessCardApp());
}

class QrBusinessCardApp extends StatelessWidget {
  const QrBusinessCardApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'QR Business Card Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.indigo,
          brightness: Brightness.light,
        ),
        useMaterial3: true,
      ),
      home: const QrBusinessCardHomePage(),
    );
  }
}

class BusinessCardInfo {
  const BusinessCardInfo({
    required this.id,
    required this.templateName,
    required this.name,
    required this.role,
    required this.phone,
    required this.email,
    required this.city,
    required this.description,
    required this.icon,
    required this.color,
  });

  final int id;
  final String templateName;
  final String name;
  final String role;
  final String phone;
  final String email;
  final String city;
  final String description;
  final IconData icon;
  final Color color;

  String get qrContent {
    return '''
姓名:$name
身份:$role
电话:$phone
邮箱:$email
城市:$city
简介:$description
''';
  }
}

class QrBusinessCardHomePage extends StatefulWidget {
  const QrBusinessCardHomePage({super.key});

  
  State<QrBusinessCardHomePage> createState() => _QrBusinessCardHomePageState();
}

class _QrBusinessCardHomePageState extends State<QrBusinessCardHomePage> {
  int _selectedCardId = 1;
  bool _showRawContent = true;

  final List<BusinessCardInfo> _cards = const [
    BusinessCardInfo(
      id: 1,
      templateName: '学生名片',
      name: 'Lin Chen',
      role: 'Computer Science Student',
      phone: '138-0000-1001',
      email: 'linchen@example.com',
      city: 'Shanghai',
      description: '关注 Flutter for OpenHarmony、前端开发和移动应用实践。',
      icon: Icons.school,
      color: Colors.indigo,
    ),
    BusinessCardInfo(
      id: 2,
      templateName: '开发者名片',
      name: 'Ming Xu',
      role: 'Flutter Developer',
      phone: '138-0000-2002',
      email: 'mingxu@example.com',
      city: 'Hangzhou',
      description: '擅长 Flutter 页面开发、跨平台适配和项目组件封装。',
      icon: Icons.code,
      color: Colors.teal,
    ),
    BusinessCardInfo(
      id: 3,
      templateName: '设计师名片',
      name: 'Yuki Wang',
      role: 'UI Designer',
      phone: '138-0000-3003',
      email: 'yukiwang@example.com',
      city: 'Suzhou',
      description: '关注移动端界面设计、色彩搭配和用户体验优化。',
      icon: Icons.brush,
      color: Colors.deepOrange,
    ),
    BusinessCardInfo(
      id: 4,
      templateName: '社团名片',
      name: 'Open Source Club',
      role: 'Campus Tech Community',
      phone: '138-0000-4004',
      email: 'club@example.com',
      city: 'Shanghai',
      description: '面向校园开发者的开源学习、项目实践和技术分享社群。',
      icon: Icons.groups,
      color: Colors.purple,
    ),
  ];

  BusinessCardInfo get _currentCard {
    return _cards.firstWhere(
      (card) {
        return card.id == _selectedCardId;
      },
      orElse: () {
        return _cards.first;
      },
    );
  }

  int get _contentLength {
    return _currentCard.qrContent.length;
  }

  void _selectCard(BusinessCardInfo card) {
    setState(() {
      _selectedCardId = card.id;
    });

    _showMessage('已切换到${card.templateName}');
  }

  void _toggleRawContent() {
    setState(() {
      _showRawContent = !_showRawContent;
    });
  }

  void _showMessage(String text) {
    ScaffoldMessenger.of(context)
      ..clearSnackBars()
      ..showSnackBar(
        SnackBar(
          content: Text(text),
          behavior: SnackBarBehavior.floating,
          duration: const Duration(milliseconds: 1200),
        ),
      );
  }

  String get _statusText {
    if (_contentLength < 80) {
      return '二维码内容较短,扫描识别压力较小';
    }

    if (_contentLength < 160) {
      return '二维码内容适中,适合作为电子名片展示';
    }

    return '二维码内容较长,建议精简简介信息';
  }

  
  Widget build(BuildContext context) {
    final ThemeData theme = Theme.of(context);
    final BusinessCardInfo currentCard = _currentCard;

    return Scaffold(
      appBar: AppBar(
        title: const Text('电子名片二维码'),
        centerTitle: true,
      ),
      body: SafeArea(
        child: ListView(
          padding: const EdgeInsets.all(16),
          children: [
            _buildOverviewCard(theme, currentCard),
            const SizedBox(height: 16),
            _buildTemplateCard(theme),
            const SizedBox(height: 16),
            _buildQrCard(theme, currentCard),
            const SizedBox(height: 16),
            _buildInfoCard(theme, currentCard),
            const SizedBox(height: 16),
            _buildPreviewCard(theme, currentCard),
            const SizedBox(height: 16),
            _buildLibraryCard(theme),
          ],
        ),
      ),
    );
  }

  Widget _buildOverviewCard(
    ThemeData theme,
    BusinessCardInfo currentCard,
  ) {
    return Card(
      elevation: 3,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(22),
      ),
      child: Padding(
        padding: const EdgeInsets.all(24),
        child: Column(
          children: [
            Container(
              width: 76,
              height: 76,
              decoration: BoxDecoration(
                color: currentCard.color.withOpacity(0.15),
                borderRadius: BorderRadius.circular(24),
              ),
              child: Icon(
                currentCard.icon,
                size: 42,
                color: currentCard.color,
              ),
            ),
            const SizedBox(height: 18),
            Text(
              'Flutter for OpenHarmony',
              style: theme.textTheme.headlineSmall?.copyWith(
                fontWeight: FontWeight.bold,
              ),
              textAlign: TextAlign.center,
            ),
            const SizedBox(height: 8),
            Text(
              '使用 qr_flutter 构建可以动态切换模板的个人电子名片二维码生成页面',
              style: theme.textTheme.bodyMedium?.copyWith(
                color: theme.colorScheme.onSurfaceVariant,
                height: 1.5,
              ),
              textAlign: TextAlign.center,
            ),
            const SizedBox(height: 18),
            Text(
              _statusText,
              style: theme.textTheme.titleMedium?.copyWith(
                color: currentCard.color,
                fontWeight: FontWeight.bold,
              ),
              textAlign: TextAlign.center,
            ),
            const SizedBox(height: 18),
            Row(
              children: [
                _buildStatItem(
                  theme,
                  title: '模板数',
                  value: '${_cards.length}',
                  icon: Icons.dashboard_customize,
                  color: currentCard.color,
                ),
                _buildStatItem(
                  theme,
                  title: '当前模板',
                  value: currentCard.templateName,
                  icon: Icons.badge,
                  color: currentCard.color,
                ),
                _buildStatItem(
                  theme,
                  title: '字符数',
                  value: '$_contentLength',
                  icon: Icons.text_fields,
                  color: currentCard.color,
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildStatItem(
    ThemeData theme, {
    required String title,
    required String value,
    required IconData icon,
    required Color color,
  }) {
    return Expanded(
      child: Column(
        children: [
          Icon(
            icon,
            color: color,
          ),
          const SizedBox(height: 6),
          Text(
            value,
            style: theme.textTheme.titleMedium?.copyWith(
              fontWeight: FontWeight.bold,
              color: color,
            ),
            overflow: TextOverflow.ellipsis,
            textAlign: TextAlign.center,
          ),
          const SizedBox(height: 2),
          Text(
            title,
            style: theme.textTheme.bodySmall?.copyWith(
              color: theme.colorScheme.onSurfaceVariant,
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildTemplateCard(ThemeData theme) {
    return Card(
      elevation: 2,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(18),
      ),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Wrap(
          spacing: 10,
          runSpacing: 10,
          children: _cards.map((card) {
            final bool selected = card.id == _selectedCardId;

            return ChoiceChip(
              avatar: Icon(
                card.icon,
                size: 18,
                color: selected ? card.color : theme.colorScheme.onSurface,
              ),
              label: Text(card.templateName),
              selected: selected,
              selectedColor: card.color.withOpacity(0.18),
              labelStyle: TextStyle(
                color: selected ? card.color : theme.colorScheme.onSurface,
                fontWeight: selected ? FontWeight.bold : FontWeight.normal,
              ),
              onSelected: (_) {
                _selectCard(card);
              },
            );
          }).toList(),
        ),
      ),
    );
  }

  Widget _buildQrCard(
    ThemeData theme,
    BusinessCardInfo currentCard,
  ) {
    return Card(
      elevation: 2,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(22),
      ),
      child: Padding(
        padding: const EdgeInsets.all(24),
        child: Column(
          children: [
            Row(
              children: [
                Icon(
                  Icons.qr_code_2,
                  color: currentCard.color,
                ),
                const SizedBox(width: 10),
                Expanded(
                  child: Text(
                    '二维码名片',
                    style: theme.textTheme.titleLarge?.copyWith(
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ),
              ],
            ),
            const SizedBox(height: 22),
            Container(
              padding: const EdgeInsets.all(16),
              decoration: BoxDecoration(
                color: Colors.white,
                borderRadius: BorderRadius.circular(24),
                border: Border.all(
                  color: currentCard.color.withOpacity(0.35),
                  width: 2,
                ),
                boxShadow: [
                  BoxShadow(
                    color: currentCard.color.withOpacity(0.12),
                    blurRadius: 18,
                    offset: const Offset(0, 8),
                  ),
                ],
              ),
              child: QrImageView(
                data: currentCard.qrContent,
                version: QrVersions.auto,
                size: 220,
                backgroundColor: Colors.white,
                foregroundColor: Colors.black,
                errorCorrectionLevel: QrErrorCorrectLevel.M,
              ),
            ),
            const SizedBox(height: 18),
            Text(
              '扫描二维码可以读取当前电子名片信息',
              style: theme.textTheme.bodyMedium?.copyWith(
                color: theme.colorScheme.onSurfaceVariant,
              ),
              textAlign: TextAlign.center,
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildInfoCard(
    ThemeData theme,
    BusinessCardInfo currentCard,
  ) {
    return Card(
      elevation: 2,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(18),
      ),
      child: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          children: [
            Row(
              children: [
                Icon(
                  currentCard.icon,
                  color: currentCard.color,
                ),
                const SizedBox(width: 10),
                Expanded(
                  child: Text(
                    '当前名片信息',
                    style: theme.textTheme.titleLarge?.copyWith(
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ),
              ],
            ),
            const SizedBox(height: 16),
            _buildInfoRow(
              theme,
              title: '姓名',
              value: currentCard.name,
              color: currentCard.color,
            ),
            _buildInfoRow(
              theme,
              title: '身份',
              value: currentCard.role,
              color: currentCard.color,
            ),
            _buildInfoRow(
              theme,
              title: '电话',
              value: currentCard.phone,
              color: currentCard.color,
            ),
            _buildInfoRow(
              theme,
              title: '邮箱',
              value: currentCard.email,
              color: currentCard.color,
            ),
            _buildInfoRow(
              theme,
              title: '城市',
              value: currentCard.city,
              color: currentCard.color,
            ),
            _buildInfoRow(
              theme,
              title: '简介',
              value: currentCard.description,
              color: currentCard.color,
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildPreviewCard(
    ThemeData theme,
    BusinessCardInfo currentCard,
  ) {
    return Card(
      elevation: 2,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(18),
      ),
      child: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          children: [
            Row(
              children: [
                Icon(
                  Icons.subject,
                  color: currentCard.color,
                ),
                const SizedBox(width: 10),
                Expanded(
                  child: Text(
                    '二维码内容预览',
                    style: theme.textTheme.titleLarge?.copyWith(
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ),
                TextButton.icon(
                  onPressed: _toggleRawContent,
                  icon: Icon(
                    _showRawContent ? Icons.visibility_off : Icons.visibility,
                  ),
                  label: Text(_showRawContent ? '收起' : '展开'),
                ),
              ],
            ),
            if (_showRawContent) ...[
              const SizedBox(height: 14),
              Container(
                width: double.infinity,
                padding: const EdgeInsets.all(14),
                decoration: BoxDecoration(
                  color: currentCard.color.withOpacity(0.08),
                  borderRadius: BorderRadius.circular(14),
                ),
                child: Text(
                  currentCard.qrContent,
                  style: theme.textTheme.bodyMedium?.copyWith(
                    color: theme.colorScheme.onSurfaceVariant,
                    height: 1.5,
                  ),
                ),
              ),
            ],
          ],
        ),
      ),
    );
  }

  Widget _buildInfoRow(
    ThemeData theme, {
    required String title,
    required String value,
    required Color color,
  }) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 12),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Container(
            width: 7,
            height: 7,
            margin: const EdgeInsets.only(top: 8),
            decoration: BoxDecoration(
              color: color,
              shape: BoxShape.circle,
            ),
          ),
          const SizedBox(width: 10),
          SizedBox(
            width: 58,
            child: Text(
              title,
              style: theme.textTheme.bodyMedium?.copyWith(
                fontWeight: FontWeight.bold,
              ),
            ),
          ),
          Expanded(
            child: Text(
              value,
              style: theme.textTheme.bodyMedium?.copyWith(
                color: theme.colorScheme.onSurfaceVariant,
                height: 1.5,
              ),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildLibraryCard(ThemeData theme) {
    return Card(
      elevation: 2,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(18),
      ),
      child: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              '第三方库说明',
              style: theme.textTheme.titleLarge?.copyWith(
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 12),
            _buildLibraryInfoRow(
              theme,
              title: '库名称',
              value: 'qr_flutter',
            ),
            _buildLibraryInfoRow(
              theme,
              title: '配置文件',
              value: 'pubspec.yaml',
            ),
            _buildLibraryInfoRow(
              theme,
              title: '导入方式',
              value: "import 'package:qr_flutter/qr_flutter.dart';",
            ),
            _buildLibraryInfoRow(
              theme,
              title: '核心组件',
              value: 'QrImageView / QrVersions / QrErrorCorrectLevel',
            ),
            _buildLibraryInfoRow(
              theme,
              title: '核心能力',
              value: '根据文本内容生成二维码,并以 Flutter Widget 方式展示',
            ),
            _buildLibraryInfoRow(
              theme,
              title: '应用场景',
              value: '电子名片、活动签到、资料分享、链接跳转、校园通知',
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildLibraryInfoRow(
    ThemeData theme, {
    required String title,
    required String value,
  }) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 10),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          SizedBox(
            width: 82,
            child: Text(
              title,
              style: theme.textTheme.bodyMedium?.copyWith(
                fontWeight: FontWeight.bold,
              ),
            ),
          ),
          Expanded(
            child: Text(
              value,
              style: theme.textTheme.bodyMedium?.copyWith(
                color: theme.colorScheme.onSurfaceVariant,
                height: 1.5,
              ),
            ),
          ),
        ],
      ),
    );
  }
}

九、代码实现说明

1. 引入 qr_flutter 第三方库

代码开头引入第三方库:

import 'package:qr_flutter/qr_flutter.dart';

这说明项目确实使用了 Flutter 第三方库,而不是 OpenHarmony 原生库。

本项目中主要使用以下内容:

QrImageView
QrVersions
QrErrorCorrectLevel

其中:

组件 作用
QrImageView 将文本内容渲染为二维码 Widget
QrVersions 设置二维码版本
QrErrorCorrectLevel 设置二维码纠错等级

2. 使用 QrImageView 生成二维码

二维码核心代码如下:

QrImageView(
  data: currentCard.qrContent,
  version: QrVersions.auto,
  size: 220,
  backgroundColor: Colors.white,
  foregroundColor: Colors.black,
  errorCorrectionLevel: QrErrorCorrectLevel.M,
)

参数说明如下:

参数 作用
data 要编码成二维码的文本内容
version 二维码版本
size 二维码显示大小
backgroundColor 二维码背景颜色
foregroundColor 二维码前景颜色
errorCorrectionLevel 二维码纠错等级

其中 data 是最关键的参数。只要 data 内容发生变化,生成出来的二维码也会随之变化。


3. 使用 QrVersions.auto 自动选择二维码版本

项目中使用:

version: QrVersions.auto

这表示由库自动选择合适的二维码版本。

二维码内容越长,需要的二维码容量越大。如果手动指定版本,容易出现内容过长导致生成失败的问题。自动选择版本比较适合普通应用开发。先让库干它该干的活,别什么都自己扛,人类已经够累了。


4. 定义电子名片数据模型

项目中定义了 BusinessCardInfo

class BusinessCardInfo {
  const BusinessCardInfo({
    required this.id,
    required this.templateName,
    required this.name,
    required this.role,
    required this.phone,
    required this.email,
    required this.city,
    required this.description,
    required this.icon,
    required this.color,
  });

  final int id;
  final String templateName;
  final String name;
  final String role;
  final String phone;
  final String email;
  final String city;
  final String description;
  final IconData icon;
  final Color color;
}

字段说明如下:

字段 作用
id 名片编号
templateName 名片模板名称
name 姓名或组织名称
role 身份或职位
phone 电话
email 邮箱
city 城市
description 简介
icon 模板图标
color 模板主题色

通过数据模型,可以统一管理名片内容。页面展示和二维码生成都可以从同一个数据模型中读取信息。


5. 生成二维码文本内容

BusinessCardInfo 中定义了 qrContent

String get qrContent {
  return '''
姓名:$name
身份:$role
电话:$phone
邮箱:$email
城市:$city
简介:$description
''';
}

这里将名片中的多个字段组合成一段文本。

这段文本会传给:

data: currentCard.qrContent

所以二维码中保存的就是当前电子名片的完整信息。


6. 实现名片模板切换

页面中准备了多个名片模板:

final List<BusinessCardInfo> _cards = const [
  BusinessCardInfo(...),
  BusinessCardInfo(...),
  BusinessCardInfo(...),
  BusinessCardInfo(...),
];

当前选中的模板通过 _selectedCardId 记录:

int _selectedCardId = 1;

切换模板的方法如下:

void _selectCard(BusinessCardInfo card) {
  setState(() {
    _selectedCardId = card.id;
  });

  _showMessage('已切换到${card.templateName}');
}

当用户点击不同模板按钮时,页面会切换名片信息,二维码也会同步更新。


7. 获取当前名片数据

当前名片通过 _currentCard 获取:

BusinessCardInfo get _currentCard {
  return _cards.firstWhere(
    (card) {
      return card.id == _selectedCardId;
    },
    orElse: () {
      return _cards.first;
    },
  );
}

这样页面中的所有区域都可以基于当前名片显示内容。

例如:

  • 顶部概览卡片;
  • 二维码生成区域;
  • 当前名片信息区域;
  • 二维码内容预览区域。

这些区域都使用同一个 currentCard,可以避免数据不一致。


8. 使用 ChoiceChip 构建模板选择按钮

模板切换区域使用 ChoiceChip 实现:

ChoiceChip(
  avatar: Icon(card.icon),
  label: Text(card.templateName),
  selected: selected,
  onSelected: (_) {
    _selectCard(card);
  },
)

每个模板按钮显示:

  • 模板图标;
  • 模板名称;
  • 是否选中;
  • 选中后的主题色。

这样用户可以直观看到当前选择的是哪一种名片模板。


9. 展示当前名片信息

名片信息区域展示当前模板的数据:

_buildInfoRow(
  theme,
  title: '姓名',
  value: currentCard.name,
  color: currentCard.color,
)

页面中依次展示:

  • 姓名;
  • 身份;
  • 电话;
  • 邮箱;
  • 城市;
  • 简介。

这些内容和二维码中的内容保持一致。用户看到什么,二维码里就编码什么,终于不再上演“界面一套,数据一套”的传统灾难。


10. 展示二维码内容预览

项目中增加了二维码内容预览区域:

Text(
  currentCard.qrContent,
)

这样用户可以直接看到二维码内部编码的原始文本。

预览区域可以展开和收起:

void _toggleRawContent() {
  setState(() {
    _showRawContent = !_showRawContent;
  });
}

如果用户不想看原始文本,可以点击“收起”。


11. 根据内容长度显示状态提示

项目中通过 _contentLength 获取二维码内容长度:

int get _contentLength {
  return _currentCard.qrContent.length;
}

然后根据长度显示提示:

String get _statusText {
  if (_contentLength < 80) {
    return '二维码内容较短,扫描识别压力较小';
  }

  if (_contentLength < 160) {
    return '二维码内容适中,适合作为电子名片展示';
  }

  return '二维码内容较长,建议精简简介信息';
}

这样可以提醒用户不要在二维码中塞入太多内容。二维码不是仓库,别什么都往里面塞。


12. 使用 SnackBar 显示操作反馈

项目中封装了提示方法:

void _showMessage(String text) {
  ScaffoldMessenger.of(context)
    ..clearSnackBars()
    ..showSnackBar(
      SnackBar(
        content: Text(text),
        behavior: SnackBarBehavior.floating,
        duration: const Duration(milliseconds: 1200),
      ),
    );
}

当用户切换名片模板时,页面底部会显示提示信息。

这样可以让用户明确知道模板已经切换成功。


十、运行项目

完成代码后,在终端执行:

flutter pub get

然后连接 OpenHarmony 设备或启动 OpenHarmony 模拟器。

查看设备:

flutter devices

运行项目:

flutter run

如果环境配置正确,应用会运行到 OpenHarmony 设备或模拟器中。

运行成功后,页面会显示“电子名片二维码”。用户可以切换不同的名片模板,页面中的二维码会根据当前名片内容自动更新。


十一、开发中遇到的问题

1. qr_flutter 依赖没有生效

如果代码中出现找不到 qr_flutter 的问题,可以检查 pubspec.yaml 中是否添加了:

qr_flutter: ^4.1.0

然后重新执行:

flutter pub get

如果还是不行,可以重启编辑器。编辑器有时候像没睡醒,依赖装好了还装作看不见,软件行业的经典节目。


2. import 导入报错

如果下面代码报错:

import 'package:qr_flutter/qr_flutter.dart';

通常有几种原因:

  • pubspec.yaml 中没有添加依赖;
  • 没有执行 flutter pub get
  • YAML 缩进错误;
  • 包名写错;
  • 编辑器没有刷新依赖。

其中 YAML 缩进最容易出问题。依赖必须写在 dependencies 下面,并且缩进要正确。一个空格就能毁掉运行结果,编程世界真是优雅得像陷阱。


3. QrImageView 不显示

如果二维码没有显示,可以检查:

  • data 是否为空;
  • size 是否设置过小;
  • 背景色和前景色是否太接近;
  • 是否正确添加 qr_flutter 依赖;
  • 是否执行了 flutter pub get
  • 页面是否成功运行。

基础写法如下:

QrImageView(
  data: 'Hello Flutter',
  version: QrVersions.auto,
  size: 200,
)

如果这段可以显示,说明库本身没有问题,再去检查自己的数据内容。


4. 二维码内容过长

如果二维码内容太长,可能会导致二维码变得复杂,扫描识别难度增加。

可以减少二维码内容,例如只保留:

姓名
电话
邮箱
个人主页链接

不建议把很长的文章、说明或复杂 JSON 全部塞进二维码。二维码不是行李箱,硬塞只会让它更难扫。


5. 切换模板后二维码没有变化

如果切换模板后二维码没有变化,需要检查是否调用了 setState()

setState(() {
  _selectedCardId = card.id;
});

还要检查 QrImageViewdata 是否使用了当前名片内容:

data: currentCard.qrContent

如果 data 写死了,二维码当然不会变化。代码没有读心术,很遗憾。


6. 二维码颜色看不清

二维码建议使用较高对比度,例如:

backgroundColor: Colors.white
foregroundColor: Colors.black

如果使用浅色前景色或复杂背景,可能会影响扫描识别。

好看的颜色很重要,但二维码首先要能扫。不能扫的二维码就像没门把手的门,看着存在,实际很碍事。


7. QrVersions.auto 的作用不清楚

QrVersions.auto 用于让库根据内容长度自动选择二维码版本。

如果手动指定版本,可能出现内容容量不足的问题。

普通项目中建议优先使用:

version: QrVersions.auto

这样更稳。


8. 运行不到 OpenHarmony 设备

如果项目无法运行到 OpenHarmony 设备或模拟器,可以检查:

  • Flutter for OpenHarmony 环境是否配置完成;
  • 设备是否连接成功;
  • flutter devices 是否能识别设备;
  • 是否执行了 flutter pub get
  • 是否选择了正确的运行设备;
  • 项目是否为 Flutter 项目,而不是原生鸿蒙项目。

如果 flutter devices 都识别不到设备,那应该先处理环境问题,而不是盯着 qr_flutter 怀疑人生。二维码库没有权限把设备藏起来。


十二、本文和原生鸿蒙项目的区别

本文是 Flutter for OpenHarmony 第三方库实践,不是 OpenHarmony 原生 ArkTS 项目。

主要区别如下:

对比项 本文写法 原生鸿蒙写法
UI 技术 Flutter ArkUI
主要语言 Dart ArkTS
页面入口 lib/main.dart Index.ets
依赖配置 pubspec.yaml oh-package.json5
依赖安装 flutter pub get ohpm install
第三方库 qr_flutter OpenHarmony 原生库
页面组件 MaterialApp / Scaffold / QrImageView @Entry / @Component

因此本文符合 Flutter for OpenHarmony 第三方库实践方向。


十三、总结

本篇完成了一个基于 qr_flutter 的 Flutter for OpenHarmony 个人电子名片二维码生成应用。项目通过 Flutter 第三方库实现了根据文本内容生成二维码,并结合名片模板切换、信息展示、二维码内容预览和操作提示完成了一个完整页面。

通过本次实践,我主要完成了以下内容:

  • 创建 Flutter for OpenHarmony 项目;
  • pubspec.yaml 中添加 qr_flutter 依赖;
  • 使用 flutter pub get 获取第三方库;
  • lib/main.dart 中引入 qr_flutter
  • 使用 QrImageView 构建二维码;
  • 使用 QrVersions.auto 自动选择二维码版本;
  • 使用 QrErrorCorrectLevel.M 设置二维码纠错等级;
  • 使用数据模型管理电子名片信息;
  • 使用 ChoiceChip 实现名片模板切换;
  • 使用 setState() 实现二维码内容动态更新;
  • 使用 SnackBar 显示操作反馈;
  • 使用 Flutter Material 组件构建完整页面;
  • 将项目运行到 OpenHarmony 设备或模拟器中。

这个项目虽然只是一个基础电子名片页面,但完整展示了 Flutter for OpenHarmony 项目中第三方库的使用流程。

后续可以在这个基础上继续扩展,例如:

  • 添加用户自定义输入;
  • 添加头像展示;
  • 添加个人主页链接;
  • 添加二维码保存为图片;
  • 添加二维码分享功能;
  • 添加不同二维码样式;
  • 添加联系人 vCard 格式;
  • 添加暗色主题;
  • 添加本地数据保存;
  • 添加多个名片管理功能。

整体来看,qr_flutter 可以帮助 Flutter 开发者快速实现二维码生成效果。通过这个项目,可以理解 Flutter for OpenHarmony 中第三方库依赖配置、二维码组件使用、数据模型设计和页面状态更新之间的基本关系。

更多推荐