Flutter for OpenHarmony:构建一个 Flutter ASCII 艺术生成器,深入解析字符映射、动态文本渲染与等宽字体实践
Flutter for OpenHarmony:构建一个 Flutter ASCII 艺术生成器,深入解析字符映射、动态文本渲染与等宽字体实践
Flutter for OpenHarmony:构建一个 Flutter ASCII 艺术生成器,深入解析字符映射、动态文本渲染与等宽字体实践
发布时间:2026年1月28日
技术栈:Flutter 3.22+、Dart 3.4+、Material Design 3
适用读者:熟悉 Flutter 基础,希望掌握字符处理、动态文本布局、剪贴板集成及等宽字体应用的开发者
在数字艺术的长河中,ASCII 艺术(ASCII Art)是一种用可打印字符(如 █, ░, ● 等)构建图像的独特表达形式。它诞生于早期终端时代,却因其极简、怀旧与创意性,在今日仍被广泛用于签名、聊天、代码注释甚至 NFT 创作。
全文代码
```dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
runApp(const AsciiArtApp());
}
class AsciiArtApp extends StatelessWidget {
const AsciiArtApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '🔤 ASCII 艺术',
debugShowCheckedModeBanner: false,
theme: ThemeData(useMaterial3: true),
home: const AsciiArtScreen(),
);
}
}
class AsciiArtScreen extends StatefulWidget {
const AsciiArtScreen({super.key});
@override
State createState() => _AsciiArtScreenState();
}
class _AsciiArtScreenState extends State {
final TextEditingController _textController = TextEditingController(text: 'HELLO');
String _asciiOutput = '';
// ASCII 字体数据(block style,5行高)
static const Map> _fontMap = {
'A': [' ██ ', '███ ', '████', '██ █', '██ █'],
'B': ['███ ', '██ █', '███ ', '██ █', '███ '],
'C': [' ██ ', '██ ', '██ ', '██ ', ' ██ '],
'D': ['███ ', '██ █', '██ █', '██ █', '███ '],
'E': ['████', '██ ', '███ ', '██ ', '████'],
'F': ['████', '██ ', '███ ', '██ ', '██ '],
'G': [' ██ ', '██ ', '██ █', '██ █', ' ███'],
'H': ['██ █', '██ █', '████', '██ █', '██ █'],
'I': ['███', ' █ ', ' █ ', ' █ ', '███'],
'J': [' ██', ' ██', ' ██', '███ ', '██ '],
'K': ['██ █', '███ ', '██ ', '███ ', '██ █'],
'L': ['██ ', '██ ', '██ ', '██ ', '████'],
'M': ['█ █', '██ ██', '█ █ █', '█ █', '█ █'],
'N': ['██ █', '███ ', '████', '█ ██', '█ ██'],
'O': [' ██ ', '██ █', '██ █', '██ █', ' ██ '],
'P': ['███ ', '██ █', '███ ', '██ ', '██ '],
'Q': [' ██ ', '██ █', '██ █', '██ █', ' ███'],
'R': ['███ ', '██ █', '███ ', '██ █', '██ █'],
'S': [' ██ ', '██ ', ' ██ ', ' ██', '██ '],
'T': ['████', ' █ ', ' █ ', ' █ ', ' █ '],
'U': ['██ █', '██ █', '██ █', '██ █', ' ███'],
'V': ['██ █', '██ █', '██ █', ' █ █', ' █ █'],
'W': ['█ █', '█ █', '█ █ █', '██ ██', '█ █'],
'X': ['█ █', ' █ █ ', ' █ ', ' █ █ ', '█ █'],
'Y': ['█ █', ' █ █ ', ' █ ', ' █ ', ' █ '],
'Z': ['████', ' █', ' █ ', ' █ ', '████'],
'0': [' ██ ', '█ █', '█ ██', '██ █', ' ██ '],
'1': [' █ ', '██ ', ' █ ', ' █ ', '███'],
'2': [' ██ ', ' █', ' ██ ', '█ ', '████'],
'3': ['███ ', ' █', ' ██ ', ' █', '███ '],
'4': [' █ ', ' ██ ', '████', ' █ ', ' █ '],
'5': ['████', '█ ', '███ ', ' █', '███ '],
'6': [' ██ ', '█ ', '███ ', '█ ██', ' ██ '],
'7': ['████', ' █', ' █ ', ' █ ', '█ '],
'8': [' ██ ', '█ ██', ' ██ ', '██ █', ' ██ '],
'9': [' ██ ', '█ ██', ' ███', ' █', ' ██ '],
'?': [' ██ ', ' █', ' █ ', ' ', ' █ '],
' ': [' ', ' ', ' ', ' ', ' '],
};
String _generateAsciiArt(String input) {
if (input.isEmpty) return '';
final normalized = input.toUpperCase();
final lines = List.generate(5, (_) => '');
for (int i = 0; i < normalized.length; i++) {
final char = normalized[i];
final charLines = _fontMap[char] ?? _fontMap['?']!;
for (int lineIndex = 0; lineIndex < 5; lineIndex++) {
lines[lineIndex] = '${lines[lineIndex]}${charLines[lineIndex]} ';
}
}
return lines.join('\n');
}
void _updateAscii() {
final text = _textController.text;
final output = _generateAsciiArt(text);
setState(() {
_asciiOutput = output;
});
}
Future _copyToClipboard() async {
if (_asciiOutput.isEmpty) return;
await Clipboard.setData(ClipboardData(text: _asciiOutput));
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('已复制 ASCII 艺术到剪贴板 ✅')),
);
}
@override
void initState() {
super.initState();
_updateAscii();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('🔤 ASCII 艺术生成器'),
centerTitle: true,
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
actions: [
IconButton(
icon: const Icon(Icons.copy),
onPressed: _copyToClipboard,
),
],
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
TextField(
controller: _textController,
decoration: const InputDecoration(
labelText: '输入文字(英文/数字)',
border: OutlineInputBorder(),
hintText: '例如:FLUTTER',
),
onChanged: (_) => _updateAscii(),
),
const SizedBox(height: 20),
const Text(
'点击右上角复制按钮分享你的 ASCII 艺术!',
style: TextStyle(color: Colors.grey, fontSize: 14),
),
const SizedBox(height: 20),
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: SelectableText(
_asciiOutput,
style: const TextStyle(
fontFamily: 'monospace',
fontSize: 16,
height: 1.2,
),
textAlign: TextAlign.center,
),
),
),
],
),
),
);
}
}
```
今天,我们将深入剖析一个用 Flutter 实现的 实时 ASCII 艺术生成器,重点探讨其如何通过 静态字符映射表、多行文本拼接算法、等宽字体渲染 以及 可选择文本交互,打造一个轻量但功能完整的创意工具。
🔤 功能需求与核心挑战
我们的 ASCII 艺术生成器需满足以下体验目标:
- 实时预览:用户输入时立即生成对应艺术字
- 支持英文与数字:覆盖 A–Z、0–9 及空格
- 固定高度字体:确保字符对齐不偏移
- 一键复制:将生成结果复制到剪贴板
- 响应式布局:适配手机竖屏与横屏
- 纯 Dart 实现:无网络请求、无外部依赖
这些需求背后隐藏着几个关键技术决策点:
- 如何高效表示每个字符的“像素”结构?
- 如何将多个字符的多行数据横向拼接成完整图案?
- 如何确保在不同设备上字符严格对齐?
接下来,我们将逐层拆解。
🧠 数据模型:静态字符映射表设计
字体定义:5 行高 Block 风格
static const Map<String, List<String>> _fontMap = {
'A': [' ██ ', '███ ', '████', '██ █', '██ █'],
'B': ['███ ', '██ █', '███ ', '██ █', '███ '],
// ...
' ': [' ', ' ', ' ', ' ', ' '],
};

设计亮点
-
static const编译期常量:- 内存占用低,启动快
- 无运行时解析开销
-
每字符 5 行 × 4 列:
- 固定高度保证垂直对齐
- 宽度统一(含尾部空格)便于横向拼接
-
使用空格 + 全角块字符:
█(U+2588 FULL BLOCK)作为“像素”- 空格作为背景,形成黑白对比
-
默认兜底字符
?:final charLines = _fontMap[char] ?? _fontMap['?']!;- 输入非支持字符(如中文、符号)时显示问号图案
- 避免程序崩溃或空白
💡 为何不用矢量或图片?
ASCII 艺术的本质是“文本”,必须保持纯字符属性,才能被复制、粘贴、嵌入代码或终端。
🧩 核心算法:多字符横向拼接
输入 → 输出转换逻辑
String _generateAsciiArt(String input) {
if (input.isEmpty) return '';
final normalized = input.toUpperCase();
final lines = List.generate(5, (_) => '');
for (int i = 0; i < normalized.length; i++) {
final char = normalized[i];
final charLines = _fontMap[char] ?? _fontMap['?']!;
for (int lineIndex = 0; lineIndex < 5; lineIndex++) {
lines[lineIndex] = '${lines[lineIndex]}${charLines[lineIndex]} ';
}
}
return lines.join('\n');
}

算法步骤解析
- 归一化:
toUpperCase()统一大小写(因只定义大写) - 初始化 5 行空字符串:
List.generate(5, (_) => '') - 逐字符遍历:
- 获取该字符的 5 行图案
- 逐行追加:将第
lineIndex行内容拼接到lines[lineIndex] - 添加列间距:每字符后加一个空格
' ',避免粘连
- 合并为单字符串:
lines.join('\n')生成最终多行文本
示例:输入 "AB"
| 行 | A 的行 | B 的行 | 拼接结果 |
|---|---|---|---|
| 0 | ' ██ ' |
'███ ' |
' ██ ███ ' |
| 1 | '███ ' |
'██ █' |
'███ ██ █' |
| … | … | … | … |
✅ 关键技巧:先按行组织,再横向扩展,而非先按字符再纵向堆叠——这是实现对齐的核心。
🖼️ UI 渲染:等宽字体与可选择文本
为什么必须用等宽字体?
ASCII 艺术依赖 每个字符占据相同宽度,否则会出现错位。例如:
- 非等宽字体(如默认系统字体):
i和W宽度不同 → 图案扭曲 - 等宽字体(monospace):所有字符等宽 → 图案精准对齐
SelectableText(
_asciiOutput,
style: const TextStyle(
fontFamily: 'monospace', // 关键!
fontSize: 16,
height: 1.2,
),
)
使用 SelectableText 而非 Text
- 允许用户长按选择文本:方便手动复制或分享
- 保留原始换行与空格:
Text可能压缩空白,SelectableText严格保留 - 无障碍友好:支持 TalkBack 朗读(尽管是乱码 😅)
布局优化
Expanded+SingleChildScrollView:- 确保长文本可滚动
- 不挤压上方输入框
textAlign: TextAlign.center:居中显示,视觉更平衡
📋 剪贴板集成:安全复制与用户反馈
Future<void> _copyToClipboard() async {
if (_asciiOutput.isEmpty) return;
await Clipboard.setData(ClipboardData(text: _asciiOutput));
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('已复制 ASCII 艺术到剪贴板 ✅')),
);
}

关键细节
- 空值保护:
if (_asciiOutput.isEmpty) return; mounted检查:防止异步回调时页面已销毁- 即时反馈:
SnackBar确认操作成功
📱 平台行为:iOS 首次访问剪贴板会弹出权限提示(系统级,无法绕过)
⌨️ 交互设计:实时更新与输入引导
实时响应输入
TextField(
onChanged: (_) => _updateAscii(),
)

- 无需提交按钮:输入即更新,提升流畅度
- 性能安全:算法 O(n),即使长文本也毫秒级完成
输入提示
hintText: '例如:FLUTTER':引导用户输入有效内容labelText:明确说明支持范围(英文/数字)
🎨 视觉与体验细节
1. AppBar 集成复制按钮
- 右上角
IconButton符合 Material Design 操作惯例 - 图标直观(
Icons.copy)
2. 色彩与排版
- 使用
Theme.of(context).colorScheme.primaryContainer作为 AppBar 背景 - 灰色提示文字降低干扰
3. 空状态处理
- 初始显示
"HELLO",避免空白尴尬 - 即使清空输入,也会生成空格图案(因
_fontMap[' ']存在)
🚀 扩展方向:从工具到创作平台
当前实现可轻松升级为更强大的 ASCII 创作工具:
1. 多字体支持
- 添加
slant、small、banner等风格 - 下拉菜单切换字体
2. 自定义字符集
- 允许用户上传或绘制自己的 5×5 字符
- 保存为本地模板
3. 导出为图片
- 使用
screenshot或repaint_boundary将SelectableText转为 PNG - 支持分享到社交平台
4. 动画效果
- 输入时字符逐个“绘制”出现
- 使用
AnimatedSwitcher实现淡入
5. 支持 Unicode 艺术
- 扩展至使用
●,■,▲,◆等符号 - 实现更细腻的灰度效果
✅ 总结:小工具,大创意
这个 ASCII 艺术生成器仅约 130 行代码,却完整体现了 文本处理、布局算法与用户体验设计的精妙结合:
| 技术点 | 实现方式 | 价值 |
|---|---|---|
| 字符映射 | Map<String, List<String>> |
结构清晰,易于维护 |
| 横向拼接算法 | 按行累积拼接 | 保证图案对齐 |
| 等宽字体 | fontFamily: 'monospace' |
视觉精准还原 |
| 实时交互 | onChanged + setState |
流畅响应 |
| 可复制输出 | Clipboard + SelectableText |
提升实用性 |
它证明了:优秀的工具类应用,不在功能繁多,而在能否精准解决一个具体问题,并带来一丝创造的乐趣。
Happy Coding with Flutter! 🐦
愿你的每一行代码,都能成为别人眼中的艺术。
欢迎加入开源鸿蒙跨平台社区: https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)