Flutter for OpenHarmony 文件转换助手App实战 - 文本转换
模式用枚举定义,UI 展示用labelupper('大写'),lower('小写'),title('标题格式'),reverse('反转'),removeSpaces('去空格'),camel('驼峰命名');这一章做完之后,工具页上“文本转换”就能当成一个标准的小模块复用:UI 负责交互,负责纯逻辑。后面你继续做其它“工具类功能”(比如正则、JSON 格式化),照这个拆分方式会轻松很多。

文本转换是日常最常用的一类“小工具”:做笔记、改文案、对齐接口字段命名的时候,经常需要临时把一段文本变成大写/小写/驼峰/去空格。
这一章我把“文本转换”做成一个独立的工具弹窗:
- 输入即转换:不需要点按钮,输入框内容变化就实时更新输出。
- 模式可扩展:新增模式只需要补一处枚举和一处转换分发。
- 带统计信息:输出区域下面顺手显示字数/单词/行数,方便校对。
文本转换工具的应用
文本转换在内容编辑和数据处理中非常有用。用户可以将文本转换为大写、小写、标题格式等。在数据处理中,文本转换可以帮助统一数据格式。
本章相关代码我放在当前目录下(方便你对照阅读):
- 入口页:
lib/pages/tools_page.dart - 弹窗组件:
lib/widgets/text_convert_dialog.dart - 转换服务:
lib/services/text_converter.dart
工具入口(工具列表里挂一个“文本转换”)
工具列表里我直接用 ListTile 做了一个入口,点一下弹出转换对话框:
_ToolTile(
icon: Icons.text_fields,
title: '文本转换',
subtitle: '大小写、标题格式、去空格、反转、驼峰命名等',
onTap: () => showDialog(
context: context,
builder: (_) => const TextConvertDialog(),
),
),
这一段在项目里解决什么问题?
- 入口清晰:工具页本身只关心“有哪些工具”和“点了怎么打开”,不塞具体业务逻辑。
- 弹窗复用:
TextConvertDialog是一个独立组件,后面你想在其它页面(比如编辑器页)复用,也不用复制 UI。
文本转换对话框的实现
对话框我用 AlertDialog 包起来,内部放三块:输入、模式选择、输出。
return AlertDialog(
title: const Text('文本转换'),
content: SizedBox(
width: 560,
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
为什么这里用了 SizedBox(width: 560)?
这不是“必须的”,但在桌面端(OpenHarmony 设备或模拟器窗口较宽)时,不给宽度容易让输入框显得很窄。
这里给一个偏舒适的宽度,文本类工具用起来会顺手不少。
输入即转换:控制器 + 统一的刷新方法
我这里没有加“转换”按钮,而是把转换逻辑收敛到一个 _applyConvert() 方法里,输入变化和模式变化都走它:
final TextEditingController _inputController = TextEditingController();
final TextEditingController _outputController = TextEditingController();
void _applyConvert() {
final input = _inputController.text;
final output = TextConverter.convert(input, _mode);
setState(() {
_outputController.text = output;
_stats = TextConverter.stats(output);
});
}
这样写的好处
- 更新路径单一:后面你再加“清空”“粘贴”“历史记录”等功能,只要在合适的地方调用
_applyConvert()就行。 - 顺便做统计:把统计信息绑定在输出结果上,不会出现“转换了但统计没变”的小毛病。
输入框:边输入边更新输出
输入框里直接监听 onChanged,把体验做成“输入即反馈”:
TextField(
controller: _inputController,
maxLines: 5,
decoration: const InputDecoration(
hintText: '输入文本',
border: OutlineInputBorder(),
),
onChanged: (_) => _applyConvert(),
),
一个实际的取舍
onChanged 会比较频繁触发,但这个转换逻辑都是纯字符串处理,量不大时问题不大。
如果你后面把它扩展成“格式化 JSON”“正则批量替换”这种更重的操作,再考虑加 debounce(比如 150ms)会更稳。
模式选择:枚举驱动下拉列表
模式不是用字符串硬编码,而是用枚举 TextConvertMode 驱动:
DropdownButtonFormField<TextConvertMode>(
value: _mode,
items: TextConvertMode.values
.map(
(m) => DropdownMenuItem(
value: m,
child: Text(m.label),
),
)
.toList(),
onChanged: (value) {
if (value == null) return;
setState(() => _mode = value);
_applyConvert();
},
decoration: const InputDecoration(
labelText: '转换模式',
border: OutlineInputBorder(),
),
),
为什么我更喜欢“枚举 + label”
- 少写一份列表:下拉框直接从
values来,新增/删除模式时不容易漏改 UI。 - 类型安全:
convert的参数是TextConvertMode,不会再遇到“字符串拼错导致 default 分支吞掉”的情况。
复制:把结果塞进剪贴板
复制按钮我做成一个 FilledButton,复制成功后给一个 SnackBar 提示:
FilledButton(
onPressed: () async {
final messenger = ScaffoldMessenger.of(context);
await Clipboard.setData(ClipboardData(text: _outputController.text));
messenger.showSnackBar(const SnackBar(content: Text('已复制')));
},
child: const Text('复制'),
),
这里为什么要拿 messenger
因为这个按钮在 Dialog 里,直接用 ScaffoldMessenger.of(context) 能保证消息能显示出来;不然你用错 context,有时候 SnackBar 会弹不出来。
文本转换的实现
转换逻辑我放在 lib/services/text_converter.dart,弹窗不直接写字符串处理,避免后期 UI 越堆越乱。
模式定义:TextConvertMode
模式用枚举定义,UI 展示用 label,逻辑分发用枚举本身:
enum TextConvertMode {
upper('大写'),
lower('小写'),
title('标题格式'),
reverse('反转'),
removeSpaces('去空格'),
camel('驼峰命名');
const TextConvertMode(this.label);
final String label;
}
我为什么不直接用字符串列表
- 少一份维护成本:
DropdownButton直接从values构建,新增模式时不需要再去改 UI 列表。 - 更不容易写错:参数是
TextConvertMode,不会出现“拼错字符串但程序还跑着”的情况。
统一分发:convert()
把所有模式都集中在一个 switch 里,弹窗只传入 text 和 mode:
static String convert(String text, TextConvertMode mode) {
switch (mode) {
case TextConvertMode.upper:
return text.toUpperCase();
case TextConvertMode.lower:
return text.toLowerCase();
case TextConvertMode.title:
return _toTitleCase(text);
case TextConvertMode.reverse:
return text.split('').reversed.join();
case TextConvertMode.removeSpaces:
return text.replaceAll(RegExp(r'\s+'), '');
case TextConvertMode.camel:
return _toCamelCase(text);
}
}
两个我自己用着比较爽的点
- 去空格用
\s+:不仅去掉空格,也会把换行、tab 一起处理掉。 - 反转直接
reversed.join():简单粗暴,做工具够用了。
标题格式:尽量不破坏原排版
标题格式这里我刻意保留了原来的空白段(比如多个空格、换行),避免转换后版面乱掉:
static String _toTitleCase(String text) {
final parts = text.split(RegExp(r'(\s+)'));
return parts.map((p) {
if (p.trim().isEmpty) return p;
final first = p.substring(0, 1).toUpperCase();
final rest = p.length > 1 ? p.substring(1).toLowerCase() : '';
return '$first$rest';
}).join();
}
这段写法比较“笨”,但更像工具该有的行为
只改大小写,不动用户原来的格式。
驼峰命名:给字段改名用
我把分隔符按 空格 / 下划线 / 中横线 统一处理,平时改接口字段名基本就覆盖到了:
final words = text
.trim()
.split(RegExp(r'[\s_-]+'))
.where((w) => w.isNotEmpty)
.toList();
这段为什么不直接 split(' ')
因为真实场景里经常是 user_name / user-name / user name 混着来,用一个正则统一切开更省事。
文本统计:输出区下面那一行数字
统计我只做三项:字数、单词数、行数,够用就行:
static TextStats stats(String text) {
final chars = text.runes.length;
final words = text
.trim()
.split(RegExp(r'\s+'))
.where((w) => w.isNotEmpty)
.length;
final lines = text.isEmpty ? 0 : text.split('\n').length;
return (chars: chars, words: words, lines: lines);
}
一个小细节
chars 用了 runes.length,比 text.length 更贴近“人看到的字符数”。如果你想抠得更细(比如 emoji),可以再单独升级这块。
更多转换模式的支持
后面你要扩展模式,路径很固定:
- 加枚举:在
TextConvertMode里加一项。 - 加分支:在
TextConverter.convert()里补一个case。
下拉框来自 TextConvertMode.values,所以 UI 基本不用改。
总结
这一章做完之后,工具页上“文本转换”就能当成一个标准的小模块复用:UI 负责交互,TextConverter 负责纯逻辑。后面你继续做其它“工具类功能”(比如正则、JSON 格式化),照这个拆分方式会轻松很多。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)