Flutter for OpenHarmony 第三方库实战:使用 qr_flutter 构建个人电子名片二维码生成应用
欢迎加入开源鸿蒙跨平台社区:
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.yaml、lib/main.dart、import 'package:qr_flutter/qr_flutter.dart';,这才是正确方向。不是在文章标题里写 Flutter,代码就会自己变成 Flutter,世界还没这么配合。
七、核心实现思路
本项目的核心流程如下:
- 在
pubspec.yaml中添加qr_flutter; - 在
main.dart中引入第三方库; - 定义电子名片数据模型;
- 准备多个不同类型的名片模板;
- 使用
ChoiceChip实现模板切换; - 根据当前选中名片生成二维码文本;
- 使用
QrImageView渲染二维码; - 使用信息卡片展示当前名片内容;
- 使用内容预览区域展示二维码原始文本;
- 使用 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 | 电话 |
| 邮箱 | |
| 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;
});
还要检查 QrImageView 的 data 是否使用了当前名片内容:
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 中第三方库依赖配置、二维码组件使用、数据模型设计和页面状态更新之间的基本关系。
更多推荐


所有评论(0)