Flutter三方库适配OpenHarmony【qr_generator】二维码样式生成器项目完整实战

前言

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

qr_generator 是一个基于 Flutter 的二维码样式生成器项目,核心代码位于 lib/main.dart。项目通过 TextField 接收文本或 URL,通过 ActionChip 提供 https://tel:mailto:wifi:sms: 快捷前缀,点击按钮后将输入内容保存到生成状态,并使用 CustomPaintQrPainter 绘制 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 技术目标

本文围绕真实源码拆解以下内容:

  1. Flutter 应用入口和黑色 Material 3 主题。
  2. TextEditingController 如何管理输入文本。
  3. _generatedText 如何区分编辑态和生成态。
  4. ActionChip 如何实现快捷前缀填入。
  5. TextSelection.fromPosition 如何把光标放到末尾。
  6. 条件渲染如何切换提示框和绘制结果。
  7. CustomPaint 如何挂载自定义绘制器。
  8. QrPainter 如何绘制 21 x 21 网格。
  9. _drawPositionMarker 如何绘制三个位标记。
  10. shouldRepaint 如何控制文本变化时重绘。
  11. 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 渲染

快捷前缀使用 WrapActionChip 渲染。

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 上应验证:

  1. 点击输入框后软键盘正常弹出。
  2. 多行输入不会破坏布局。
  3. 点击快捷前缀后文本能写入输入框。
  4. 光标停留在前缀末尾。
  5. 滚动页面时输入框和生成区域都可访问。

七、生成按钮与状态更新

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: 2TextOverflow.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 侧应重点观察:

  1. 200 x 200 绘制区域是否完整显示。
  2. 三个位标记是否位于左上、右上、左下。
  3. 黑白方块边界是否清晰。
  4. 不同输入文本生成的图案是否变化。
  5. 页面滚动时图案是否保持稳定。

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://');
});

该测试覆盖 ActionChipTextEditingController

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 绘制、文本省略和页面滚动。由于当前图案不是标准二维码,适配验证应重点放在交互链路和绘制稳定性上,而不是扫码识别。

掌握这个项目后,可以继续接入标准二维码编码库、导出图片、增加历史记录、配置前景色和背景色、支持模板化输入,让它从二维码样式绘制演示逐步演进为完整的跨平台二维码工具。

如果这篇文章对你有帮助,欢迎点赞、收藏、关注,你的支持是我持续创作的动力!


相关资源:

更多推荐