Flutter三方库适配OpenHarmony【qr_generator】二维码样式生成器项目完整实战
Flutter三方库适配OpenHarmony【qr_generator】二维码样式生成器项目完整实战
前言
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
qr_generator 是一个基于 Flutter 的二维码样式生成器项目,核心代码位于 lib/main.dart。项目通过 TextField 接收文本或 URL,通过 ActionChip 提供 https://、tel:、mailto:、wifi:、sms: 快捷前缀,点击按钮后将输入内容保存到生成状态,并使用 CustomPaint 和 QrPainter 绘制 21 x 21 网格风格的二维码样式图案。
需要先明确当前项目边界:源码没有接入标准二维码编码库,也没有实现二维码纠错、掩码、模式编码、版本选择等标准流程。因此当前绘制结果是 二维码视觉样式演示图案,适合学习 Flutter 自定义绘制、网格坐标、条件渲染和 OpenHarmony Canvas 渲染验证,不应被描述为标准可扫码二维码。

图片说明:本文围绕 Flutter 输入控件、自定义绘制和 OpenHarmony 承载工程展开,所有关键代码均来自 qr_generator 的真实源码。
工具类应用必须准确表达能力边界。当前项目画出了二维码样式,但没有实现标准二维码编码,这是源码事实,也是适配分析的关键前提。
一、项目背景与目标
1.1 项目定位
qr_generator 是一个轻量级 Flutter 绘图工具。用户输入文本或 URL 后,页面会用 CustomPainter 根据文本 hash 绘制一个二维码风格图案。它更适合作为 Flutter 自定义绘制和 OpenHarmony 渲染适配案例,而不是完整二维码业务库案例。
当前项目真实支持的功能包括:
- 多行输入文本或 URL。
- 输入框提示示例为
https://example.com。 - 支持
https://快捷前缀。 - 支持
tel:快捷前缀。 - 支持
mailto:快捷前缀。 - 支持
wifi:快捷前缀。 - 支持
sms:快捷前缀。 - 点击快捷前缀后自动移动光标到文本末尾。
- 点击按钮后把输入内容写入
_generatedText。 - 未生成内容时展示 200 x 200 的提示框。
- 有生成内容时使用
CustomPaint绘制二维码样式图案。 - 结果图案下方显示生成文本。
- 生成文本最多显示 2 行并自动省略。
- 底部展示快捷前缀说明。
1.2 技术目标
本文围绕真实源码拆解以下内容:
- Flutter 应用入口和黑色 Material 3 主题。
TextEditingController如何管理输入文本。_generatedText如何区分编辑态和生成态。ActionChip如何实现快捷前缀填入。TextSelection.fromPosition如何把光标放到末尾。- 条件渲染如何切换提示框和绘制结果。
CustomPaint如何挂载自定义绘制器。QrPainter如何绘制 21 x 21 网格。_drawPositionMarker如何绘制三个位标记。shouldRepaint如何控制文本变化时重绘。- OpenHarmony 侧如何验证输入、绘制和滚动布局。
1.3 核心实现速览
| 能力 | 当前实现 | 适配关注点 |
|---|---|---|
| 应用入口 | runApp(const QrGeneratorApp()) |
确认首屏加载 |
| 主题 | ColorScheme.fromSeed(seedColor: Colors.black) |
确认黑白视觉 |
| 输入 | TextField + TextEditingController |
确认多行输入 |
| 快捷前缀 | ActionChip |
确认点击和光标位置 |
| 生成状态 | _generatedText |
确认生成态刷新 |
| 绘制区域 | CustomPaint(size: Size(200, 200)) |
确认 Canvas 渲染 |
| 绘制器 | QrPainter |
确认自定义绘制 |
| 位标记 | _drawPositionMarker |
确认三个定位方块 |
| 数据块 | text.hashCode.abs() |
确认样式随文本变化 |
| 重绘控制 | shouldRepaint |
确认文本变化触发重绘 |
二、环境准备与工程结构
2.1 工程结构
项目保持 Flutter 标准结构,同时包含 OpenHarmony 平台工程。
| 文件或目录 | 作用 |
|---|---|
lib/main.dart |
应用入口、输入、状态、自定义绘制和 UI |
pubspec.yaml |
SDK 约束、Flutter 依赖和 Material 图标配置 |
analysis_options.yaml |
Flutter lint 规则 |
test/ |
Flutter 测试目录 |
ohos/ |
OpenHarmony 平台承载工程 |
README.md |
项目说明文件 |
当前项目没有引入二维码第三方库,所有图案绘制都在 QrPainter 中完成。
2.2 依赖配置
项目使用 Dart SDK ^3.9.2,依赖 Flutter SDK。
environment:
sdk: ^3.9.2
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.8
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
flutter:
uses-material-design: true
由于没有标准二维码库,当前实现重点是 Flutter Canvas 绘制,而不是二维码协议编码。
2.3 常用命令
flutter pub get
flutter analyze
flutter test
flutter run
| 命令 | 用途 |
|---|---|
flutter pub get |
获取依赖 |
flutter analyze |
执行静态分析 |
flutter test |
执行测试 |
flutter run |
在目标设备运行 |
OpenHarmony 调试时,还需要结合本地 Flutter OpenHarmony 工具链完成构建、安装和运行。
三、应用入口与主题配置
3.1 import 依赖
项目只引入 Flutter Material。
import 'package:flutter/material.dart';
material.dart 提供页面、输入框、芯片、按钮、卡片、自定义绘制相关基础类型和图标。
3.2 main 函数
入口函数启动根组件。
void main() {
runApp(const QrGeneratorApp());
}
3.3 QrGeneratorApp
根组件创建 MaterialApp。
class QrGeneratorApp extends StatelessWidget {
const QrGeneratorApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'QR Generator',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.black),
useMaterial3: true,
),
home: const QrGeneratorHomePage(title: 'QR Generator'),
);
}
}
这段代码包含三个关键点:
- 应用标题为
QR Generator。 - 使用黑色作为主题种子色。
- 首页为
QrGeneratorHomePage。
四、页面状态设计
4.1 StatefulWidget
输入内容和生成结果都会变化,因此页面使用 StatefulWidget。
class QrGeneratorHomePage extends StatefulWidget {
const QrGeneratorHomePage({super.key, required this.title});
final String title;
State<QrGeneratorHomePage> createState() => _QrGeneratorHomePageState();
}
4.2 核心状态字段
状态类中包含输入控制器和生成结果。
class _QrGeneratorHomePageState extends State<QrGeneratorHomePage> {
final TextEditingController _textController = TextEditingController();
String _generatedText = '';
}
| 字段 | 类型 | 作用 |
|---|---|---|
_textController |
TextEditingController |
管理输入框内容 |
_generatedText |
String |
保存点击生成后的文本 |
这种设计让“正在编辑的文本”和“已经生成的结果”分开。用户改输入框但不点击生成按钮时,绘制结果不会立即变化。
4.3 生命周期释放
输入控制器在页面销毁时释放。
void dispose() {
_textController.dispose();
super.dispose();
}
TextEditingController 属于资源型对象,当前项目已经正确处理生命周期。
五、快捷前缀设计
5.1 quickOptions 列表
项目定义了五个快捷前缀。
final List<String> _quickOptions = [
'https://',
'tel:',
'mailto:',
'wifi:',
'sms:',
];
| 前缀 | 用途 |
|---|---|
https:// |
网站 URL |
tel: |
电话号码 |
mailto: |
邮箱地址 |
wifi: |
WiFi 信息 |
sms: |
短信内容 |
这些前缀对应二维码常见内容类型。
5.2 ActionChip 渲染
快捷前缀使用 Wrap 和 ActionChip 渲染。
Wrap(
spacing: 8,
children: _quickOptions.map((option) {
return ActionChip(
label: Text(option, style: const TextStyle(fontSize: 12)),
onPressed: () {
_textController.text = option;
_textController.selection = TextSelection.fromPosition(
TextPosition(offset: _textController.text.length),
);
},
);
}).toList(),
)
Wrap 可以在小屏设备上自动换行,ActionChip 适合作为轻量快捷操作入口。
5.3 光标移动到末尾
点击前缀后,代码把光标放到文本末尾。
_textController.selection = TextSelection.fromPosition(
TextPosition(offset: _textController.text.length),
);
这样用户点击 https:// 后,可以直接在后面继续输入域名。
六、输入框实现
6.1 多行 TextField
输入框支持三行文本。
TextField(
controller: _textController,
maxLines: 3,
decoration: InputDecoration(
labelText: 'Enter text or URL',
hintText: 'https://example.com',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
filled: true,
),
)
多行输入适合 URL、邮箱、短信内容和 WiFi 配置等较长文本。
6.2 输入框样式
输入框使用圆角边框和填充背景。
decoration: InputDecoration(
labelText: 'Enter text or URL',
hintText: 'https://example.com',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
filled: true,
)
labelText 表示字段用途,hintText 给出 URL 示例。
6.3 OpenHarmony 输入验证
OpenHarmony 上应验证:
- 点击输入框后软键盘正常弹出。
- 多行输入不会破坏布局。
- 点击快捷前缀后文本能写入输入框。
- 光标停留在前缀末尾。
- 滚动页面时输入框和生成区域都可访问。
七、生成按钮与状态更新
7.1 _generateQr 方法
生成方法只做一件事:把输入框文本写入 _generatedText。
void _generateQr() {
setState(() {
_generatedText = _textController.text;
});
}
点击生成按钮后,页面根据 _generatedText 是否为空切换显示内容。
7.2 生成按钮
按钮使用 ElevatedButton.icon。
ElevatedButton.icon(
onPressed: _generateQr,
icon: const Icon(Icons.qr_code),
label: const Text('Generate QR Code'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.all(16),
backgroundColor: Colors.black,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
)
黑色按钮和二维码黑白视觉一致。
7.3 编辑态与生成态
| 状态 | _textController.text |
_generatedText |
页面表现 |
|---|---|---|---|
| 初始 | 空 | 空 | 显示提示框 |
| 输入未生成 | 非空 | 空 | 仍显示提示框 |
| 点击生成 | 非空 | 非空 | 显示绘制图案 |
| 清空后生成 | 空 | 空 | 回到提示框 |
这种设计让用户可以编辑输入内容,而不会在未点击按钮时立即改变绘制结果。
八、结果卡片与条件渲染
8.1 结果 Card
结果区域使用白色卡片。
Card(
elevation: 8,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
child: Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: Colors.white,
),
child: Column(
children: [
// 提示框或 CustomPaint
],
),
),
)
白色背景符合二维码黑白图案的展示习惯。
8.2 空状态提示框
未生成内容时显示提示框。
_generatedText.isEmpty
? Container(
width: 200,
height: 200,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade300, width: 2),
borderRadius: BorderRadius.circular(12),
),
child: const Center(
child: Text(
'QR Code will appear here',
textAlign: TextAlign.center,
style: TextStyle(color: Colors.grey),
),
),
)
: CustomPaint(
size: const Size(200, 200),
painter: QrPainter(text: _generatedText),
)
空状态明确告诉用户生成区域的位置。
8.3 结果文本
当 _generatedText 不为空时,图案下方显示文本。
if (_generatedText.isNotEmpty) ...[
const SizedBox(height: 16),
Text(
_generatedText,
style: const TextStyle(fontSize: 14, color: Colors.grey),
maxLines: 2,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
),
]
maxLines: 2 和 TextOverflow.ellipsis 可以避免长文本撑开页面。
九、CustomPaint 绘制入口
9.1 CustomPaint 使用
生成内容后,页面创建 CustomPaint。
CustomPaint(
size: const Size(200, 200),
painter: QrPainter(text: _generatedText),
)
绘制区域固定为 200 x 200。
9.2 QrPainter 类
绘制器继承 CustomPainter。
class QrPainter extends CustomPainter {
final String text;
QrPainter({required this.text});
void paint(Canvas canvas, Size size) {
// 绘制逻辑
}
bool shouldRepaint(covariant QrPainter oldDelegate) {
return oldDelegate.text != text;
}
}
text 是绘制图案的输入来源。文本变化时,shouldRepaint 返回 true。
9.3 shouldRepaint
bool shouldRepaint(covariant QrPainter oldDelegate) {
return oldDelegate.text != text;
}
只有文本变化时才需要重绘,这能避免不必要的 Canvas 绘制。
十、网格绘制逻辑
10.1 单元格尺寸
绘制区域被分成 21 格。
final cellSize = size.width / 21;
final paint = Paint()..color = Colors.black;
当绘制区域宽度为 200 时,每个单元格约为 9.52 像素。
10.2 位标记绘制
代码先绘制三个定位方块。
_drawPositionMarker(canvas, paint, cellSize, 0, 0, 7);
_drawPositionMarker(canvas, paint, cellSize, 14, 0, 7);
_drawPositionMarker(canvas, paint, cellSize, 0, 14, 7);
三个位置分别是左上、右上、左下。这符合二维码视觉结构中的定位标记布局。
10.3 数据块绘制
数据块根据文本 hash 绘制。
final hash = text.hashCode.abs();
for (int i = 0; i < 64; i++) {
final row = (i ~/ 8) + 2;
final col = (i % 8) + 2;
if ((hash >> i) & 1 == 1) {
canvas.drawRect(
Rect.fromLTWH(col * cellSize, row * cellSize, cellSize, cellSize),
paint,
);
}
}
这段代码最多绘制 64 个数据方块。它让不同文本产生不同图案,但这不是标准二维码编码过程。
10.4 绘制流程表
| 步骤 | 代码 | 作用 |
|---|---|---|
| 1 | cellSize = size.width / 21 |
计算网格尺寸 |
| 2 | 创建黑色 Paint |
设置绘制颜色 |
| 3 | 绘制左上位标记 | 增加二维码样式特征 |
| 4 | 绘制右上位标记 | 增加二维码样式特征 |
| 5 | 绘制左下位标记 | 增加二维码样式特征 |
| 6 | 计算 text.hashCode.abs() |
得到样式来源 |
| 7 | 遍历 64 位 | 判断是否绘制数据块 |
十一、位标记绘制函数
11.1 方法签名
void _drawPositionMarker(
Canvas canvas,
Paint paint,
double cellSize,
int startX,
int startY,
int size,
) {
// 绘制外框、内部白块和中心黑块
}
参数控制起始网格位置和标记尺寸。
11.2 外框绘制
外框通过四条边绘制。
for (int i = 0; i < size; i++) {
canvas.drawRect(
Rect.fromLTWH((startX + i) * cellSize, startY * cellSize, cellSize, cellSize),
paint,
);
canvas.drawRect(
Rect.fromLTWH(startX * cellSize, (startY + i) * cellSize, cellSize, cellSize),
paint,
);
canvas.drawRect(
Rect.fromLTWH((startX + i) * cellSize, (startY + size - 1) * cellSize, cellSize, cellSize),
paint,
);
canvas.drawRect(
Rect.fromLTWH((startX + size - 1) * cellSize, (startY + i) * cellSize, cellSize, cellSize),
paint,
);
}
这会绘制 7 x 7 方块的黑色外框。
11.3 内部白块
内部使用白色覆盖。
final innerSize = size - 2;
final innerPaint = Paint()..color = Colors.white;
for (int i = 1; i < innerSize; i++) {
for (int j = 1; j < innerSize; j++) {
canvas.drawRect(
Rect.fromLTWH((startX + i) * cellSize, (startY + j) * cellSize, cellSize, cellSize),
innerPaint,
);
}
}
内部白块让定位标记呈现黑白嵌套结构。
11.4 中心黑块
中心再绘制黑色方块。
final centerPaint = Paint()..color = Colors.black;
final centerSize = size - 4;
for (int i = 2; i < centerSize + 2; i++) {
for (int j = 2; j < centerSize + 2; j++) {
canvas.drawRect(
Rect.fromLTWH((startX + i) * cellSize, (startY + j) * cellSize, cellSize, cellSize),
centerPaint,
);
}
}
外黑、内白、中心黑的结构是二维码视觉识别中的典型形态。
十二、底部提示卡片
12.1 Quick Tips 区域
页面底部用卡片列出前缀说明。
Card(
color: Colors.grey.shade100,
child: const Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Quick Tips:',
style: TextStyle(fontWeight: FontWeight.bold),
),
SizedBox(height: 8),
Text('• https:// - Website URL'),
Text('• tel: - Phone number'),
Text('• mailto: - Email address'),
Text('• wifi: - WiFi credentials'),
Text('• sms: - SMS message'),
],
),
),
)
这一区域直接告诉用户每个前缀的用途。
12.2 前缀说明表
| 前缀 | 说明 |
|---|---|
https:// |
Website URL |
tel: |
Phone number |
mailto: |
Email address |
wifi: |
WiFi credentials |
sms: |
SMS message |
当前代码只负责文本填入,不会自动拼接完整协议内容。例如 WiFi 字符串仍需要用户继续输入完整内容。
12.3 OpenHarmony 可读性验证
OpenHarmony 上要确认:
- 灰色卡片背景可见。
- 文本不会挤压或截断。
- 小屏幕下可以滚动到底部。
- 圆点文本显示正常。
十三、OpenHarmony 适配要点
13.1 基础组件验证
当前项目使用的 Flutter 组件包括:
| 组件 | 作用 | OpenHarmony 关注点 |
|---|---|---|
MaterialApp |
应用根组件 | 首屏加载 |
Scaffold |
页面骨架 | AppBar 与 Body |
TextField |
输入文本 | 多行输入和软键盘 |
ActionChip |
快捷前缀 | 点击响应和换行 |
ElevatedButton.icon |
生成按钮 | 点击响应 |
Card |
分组区域 | 圆角、阴影、背景 |
CustomPaint |
绘制图案 | Canvas 渲染 |
Text |
文案展示 | 省略和换行 |
SingleChildScrollView |
页面滚动 | 小屏可访问 |
13.2 Canvas 绘制验证
OpenHarmony 侧应重点观察:
- 200 x 200 绘制区域是否完整显示。
- 三个位标记是否位于左上、右上、左下。
- 黑白方块边界是否清晰。
- 不同输入文本生成的图案是否变化。
- 页面滚动时图案是否保持稳定。
13.3 输入与快捷前缀验证
输入链路要覆盖:
- 点击输入框后软键盘正常弹出。
- 输入 URL 后点击生成,绘制区域更新。
- 点击
https://后输入框内容变为该前缀。 - 点击
tel:、mailto:、wifi:、sms:后内容分别更新。 - 光标位于前缀末尾,便于继续输入。
13.4 能力边界验证
当前项目没有标准二维码库,因此验证重点不是扫码识别,而是:
- 输入是否能驱动状态变化。
CustomPainter是否正常绘制。- 绘制结果是否随文本变化。
- 页面是否能稳定展示输入内容。
- OpenHarmony Canvas 渲染是否与 Flutter 预期一致。
如果业务目标是生成可扫码二维码,需要接入标准二维码编码库或实现完整编码流程。当前项目的工程价值主要在自定义绘制和交互适配。
十四、测试与验证
14.1 初始页面测试
Widget 测试可以验证首屏结构。
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('qr generator shows initial widgets', (tester) async {
await tester.pumpWidget(const QrGeneratorApp());
expect(find.text('QR Generator'), findsWidgets);
expect(find.text('Enter text or URL'), findsOneWidget);
expect(find.text('Generate QR Code'), findsOneWidget);
expect(find.text('QR Code will appear here'), findsOneWidget);
expect(find.text('Quick Tips:'), findsOneWidget);
});
}
这类测试关注页面结构,不依赖自定义绘制像素。
14.2 快捷前缀测试
可以测试点击 https:// 后输入框内容变化。
testWidgets('quick option fills input text', (tester) async {
await tester.pumpWidget(const QrGeneratorApp());
await tester.tap(find.text('https://'));
await tester.pump();
final field = tester.widget<TextField>(find.byType(TextField));
expect(field.controller?.text, 'https://');
});
该测试覆盖 ActionChip 和 TextEditingController。
14.3 生成状态测试
点击生成后,页面应展示输入文本。
testWidgets('generate button shows generated text', (tester) async {
await tester.pumpWidget(const QrGeneratorApp());
await tester.enterText(find.byType(TextField), 'https://example.com');
await tester.tap(find.text('Generate QR Code'));
await tester.pump();
expect(find.text('https://example.com'), findsOneWidget);
});
这可以验证 _generatedText 已经更新。
14.4 手工验证矩阵
| 场景 | 操作 | 预期 |
|---|---|---|
| 首次打开 | 启动应用 | 显示输入框、快捷前缀、按钮和提示框 |
| 点击前缀 | 点击 https:// |
输入框写入 https:// |
| 输入生成 | 输入 URL 后点击按钮 | 显示二维码样式图案 |
| 长文本 | 输入长 URL | 下方文本最多两行并省略 |
| 空内容 | 清空输入后点击按钮 | 回到提示框 |
| 图案变化 | 输入不同文本生成 | 数据块样式变化 |
| 页面滚动 | 滚动到底部 | Quick Tips 可见 |
十五、常见问题与优化建议
15.1 为什么说当前不是标准二维码
标准二维码需要编码模式、版本、纠错级别、数据编码、纠错码、掩码和格式信息。当前项目只是绘制三个位标记,并用文本 hash 生成 64 个方块。
final hash = text.hashCode.abs();
这能产生视觉差异,但不能替代标准二维码编码。
15.2 为什么使用 CustomPainter
CustomPainter 适合演示网格绘制、Canvas 坐标和自定义图案。
class QrPainter extends CustomPainter {
void paint(Canvas canvas, Size size) {}
}
相比使用图片或普通 Widget 堆叠,Canvas 绘制更接近实际二维码渲染方式。
15.3 为什么要区分输入文本和生成文本
_textController.text 是输入框当前内容,_generatedText 是点击按钮后确认生成的内容。
setState(() {
_generatedText = _textController.text;
});
这种区分让页面不会在用户编辑时立即改变结果。
15.4 如何接入标准二维码能力
如果要生成可扫码二维码,可以接入标准二维码库,并用库输出的矩阵替换当前 hash 绘制逻辑。
class QrMatrix {
final int size;
final List<List<bool>> modules;
QrMatrix({
required this.size,
required this.modules,
});
}
绘制层仍然可以保留 CustomPainter,只是数据来源从 hash 改为标准二维码矩阵。
15.5 如何增加保存图片能力
当前项目只在页面上绘制图案。后续可以把绘制区域导出为图片。
class QrExportResult {
final List<int> bytes;
final String mimeType;
QrExportResult({
required this.bytes,
required this.mimeType,
});
}
导出图片时需要结合 Flutter 渲染树截图能力和 OpenHarmony 文件权限策略。
15.6 如何提高输入格式体验
快捷前缀可以继续扩展为模板。
final List<String> quickTemplates = [
'https://example.com',
'tel:+8613800000000',
'mailto:name@example.com',
'sms:+8613800000000?body=Hello',
];
模板比单纯前缀更适合新用户理解格式。
十六、工程扩展方向
16.1 抽取输入模板
快捷前缀可以建模。
class QrQuickOption {
final String label;
final String value;
final String description;
const QrQuickOption({
required this.label,
required this.value,
required this.description,
});
}
这样页面可以同时展示标签、填充值和说明。
16.2 抽取绘制配置
绘制参数可以抽成配置。
class QrPaintConfig {
final int gridSize;
final double canvasSize;
final Color foreground;
final Color background;
const QrPaintConfig({
required this.gridSize,
required this.canvasSize,
required this.foreground,
required this.background,
});
}
配置化后,可以支持不同尺寸、颜色和边距。
16.3 增加前景色和背景色
当前图案固定黑白。可以增加颜色配置。
final foregroundPaint = Paint()..color = Colors.black;
final backgroundPaint = Paint()..color = Colors.white;
如果后续生成标准二维码,颜色对比度仍应保持足够高。
16.4 增加历史记录
可以保存最近生成过的文本。
final List<String> history = [];
void addHistory(String value) {
history.insert(0, value);
if (history.length > 10) {
history.removeLast();
}
}
历史记录适合二维码工具类应用,用户可以快速恢复最近内容。
总结
qr_generator 是一个适合学习 Flutter 输入交互和自定义绘制的项目。它通过 TextEditingController 管理输入内容,通过 ActionChip 提供常见二维码内容前缀,通过 _generatedText 区分编辑态和生成态,通过 CustomPaint 挂载 QrPainter,再用 Canvas 按 21 x 21 网格绘制三个位标记和基于文本 hash 的数据方块。
从 OpenHarmony 适配角度看,这个项目适合验证 Flutter 多行输入、快捷芯片、按钮点击、条件渲染、卡片布局、Canvas 绘制、文本省略和页面滚动。由于当前图案不是标准二维码,适配验证应重点放在交互链路和绘制稳定性上,而不是扫码识别。
掌握这个项目后,可以继续接入标准二维码编码库、导出图片、增加历史记录、配置前景色和背景色、支持模板化输入,让它从二维码样式绘制演示逐步演进为完整的跨平台二维码工具。
如果这篇文章对你有帮助,欢迎点赞、收藏、关注,你的支持是我持续创作的动力!
相关资源:
更多推荐



所有评论(0)