Flutter for OpenHarmony轻量级开源记事本App实战:标签管理
标签是另一种组织笔记的方式,与文件夹的层级结构不同,标签更加灵活自由。一条笔记可以有多个标签,通过标签可以从不同维度查看和筛选笔记。本文将介绍如何实现一个实用的标签管理系统。
标签页面的整体布局
标签管理页面展示所有标签及其包含的笔记数量,用户可以点击查看详情或删除标签。
class TagPage extends StatelessWidget {
const TagPage({super.key});
Widget build(BuildContext context) {
final controller = Get.find<NoteController>();
return Scaffold(
appBar: AppBar(
title: const Text('标签管理'),
actions: [
IconButton(
icon: const Icon(Icons.add),
onPressed: () => _showCreateTagDialog(context, controller),
),
],
),
页面使用StatelessWidget,状态由GetX控制器管理。AppBar显示标题和添加按钮,点击添加按钮弹出创建标签对话框。这种简洁的设计让用户可以快速创建新标签。标签管理不需要像文件夹那样复杂的层级结构,所以界面更加简单直观。
标签列表的响应式展示
标签列表使用Obx包裹,实现数据变化时自动更新界面。
body: Obx(() {
if (controller.tags.isEmpty) {
return const EmptyState(
icon: Icons.label_outline,
title: '暂无标签',
subtitle: '点击右上角创建标签',
);
}
return ListView.builder(
padding: EdgeInsets.all(12.w),
itemCount: controller.tags.length,
itemBuilder: (context, index) {
final tag = controller.tags[index];
final noteCount = controller.getNotesByTag(tag.name).length;
当标签列表为空时,显示空状态提示,引导用户创建第一个标签。有标签时使用ListView.builder构建列表,只渲染可见的列表项。对于每个标签,通过getNotesByTag方法获取包含该标签的笔记数量。这个数量会显示在卡片上,让用户了解标签的使用情况。
标签卡片的设计
每个标签显示为一个卡片,包含标签名称、笔记数量和操作按钮。
return Card(
margin: EdgeInsets.only(bottom: 8.h),
child: ListTile(
leading: const Icon(Icons.label, color: Color(0xFF2196F3)),
title: Text('#${tag.name}'),
subtitle: Text('$noteCount 篇笔记'),
trailing: IconButton(
icon: const Icon(Icons.delete_outline),
onPressed: () => _confirmDelete(context, controller, tag),
),
onTap: () => Get.to(() => TagDetailPage(tag: tag)),
),
);
},
);
}),
);
}
ListTile是Material Design的标准列表项组件,leading显示标签图标,title显示标签名称并添加#前缀,subtitle显示笔记数量。trailing放置删除按钮,点击后弹出确认对话框。整个卡片可点击,点击后跳转到标签详情页面查看所有带该标签的笔记。这种设计让标签管理变得简单直观。
创建标签对话框
创建标签对话框只需要输入标签名称,比创建文件夹更简单。
void _showCreateTagDialog(BuildContext context, NoteController controller) {
final nameController = TextEditingController();
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('新建标签'),
content: TextField(
controller: nameController,
decoration: const InputDecoration(
labelText: '标签名称',
border: OutlineInputBorder(),
),
),
对话框包含一个TextField用于输入标签名称。使用OutlineInputBorder提供清晰的边框样式,labelText显示输入提示。TextEditingController管理输入内容,可以获取用户输入的文本。标签名称应该简短有意义,方便用户记忆和使用。
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
ElevatedButton(
onPressed: () {
if (nameController.text.isNotEmpty) {
controller.createTag(nameController.text);
Navigator.pop(context);
}
},
child: const Text('创建'),
),
],
),
);
}
对话框底部有取消和创建两个按钮。创建按钮验证名称不为空后调用控制器的createTag方法创建标签,然后关闭对话框。这种简单的验证能够避免创建空名称的标签。标签创建成功后会立即显示在列表中,因为使用了响应式更新。
删除标签的确认
删除标签会影响所有使用该标签的笔记,需要用户确认。
void _confirmDelete(BuildContext context, NoteController controller, tag) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('删除标签'),
content: Text('确定要删除"#${tag.name}"吗?该标签将从所有笔记中移除。'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
确认对话框显示标签名称,并说明删除后的影响。标签会从所有笔记中移除,但笔记本身不会被删除。这种设计确保用户清楚了解操作的后果。content使用字符串插值显示标签名称,让用户确认要删除的是哪个标签。
ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
onPressed: () {
controller.deleteTag(tag.id);
Navigator.pop(context);
},
child: const Text('删除'),
),
],
),
);
}
}
删除按钮使用红色背景,警示用户这是危险操作。点击后调用控制器的deleteTag方法删除标签,然后关闭对话框。控制器会遍历所有笔记,将该标签从笔记的tags列表中移除。这种确认机制能够有效防止误操作。
标签的数据模型
标签模型非常简单,只需要ID和名称两个字段。
class Tag {
final String id;
final String name;
final DateTime createdAt;
Tag({
required this.id,
required this.name,
required this.createdAt,
});
}
Tag类包含三个字段:id是唯一标识,name是标签名称,createdAt记录创建时间。标签不需要像文件夹那样的层级结构,所以模型更加简单。这种扁平的结构让标签管理变得轻量高效。
控制器中的标签管理
控制器负责标签的增删改查和数据持久化。
final RxList<Tag> tags = <Tag>[].obs;
void createTag(String name) {
// 检查是否已存在同名标签
if (tags.any((t) => t.name == name)) {
Get.snackbar('提示', '标签已存在', snackPosition: SnackPosition.BOTTOM);
return;
}
final tag = Tag(
id: DateTime.now().millisecondsSinceEpoch.toString(),
name: name,
createdAt: DateTime.now(),
);
tags.add(tag);
saveData();
}
tags使用RxList实现响应式,任何修改都会自动通知UI更新。createTag方法首先检查是否已存在同名标签,避免重复。如果不存在就创建新标签,使用时间戳作为唯一ID。将新标签添加到列表后调用saveData持久化。这种设计确保标签名称的唯一性。
void deleteTag(String id) {
final tag = tags.firstWhereOrNull((t) => t.id == id);
if (tag == null) return;
tags.removeWhere((t) => t.id == id);
// 从所有笔记中移除该标签
for (var note in notes) {
note.tags.remove(tag.name);
}
saveData();
}
deleteTag方法删除指定ID的标签,然后遍历所有笔记,将该标签从笔记的tags列表中移除。这种级联更新确保数据的一致性,不会留下孤立的标签引用。删除后调用saveData持久化修改。
获取标签下的笔记
提供方法获取包含指定标签的所有笔记。
List<Note> getNotesByTag(String tagName) {
return activeNotes.where((n) => n.tags.contains(tagName)).toList();
}
getNotesByTag方法筛选出tags列表包含指定标签名称的笔记。笔记的tags字段是一个字符串列表,使用contains方法检查是否包含目标标签。这个方法在标签详情页面和统计分析中都会用到。
标签的重命名
用户应该能够修改标签名称,同时更新所有使用该标签的笔记。
void renameTag(String id, String newName) {
final tag = tags.firstWhereOrNull((t) => t.id == id);
if (tag == null) return;
// 检查新名称是否已存在
if (tags.any((t) => t.name == newName && t.id != id)) {
Get.snackbar('提示', '标签名称已存在', snackPosition: SnackPosition.BOTTOM);
return;
}
final oldName = tag.name;
final index = tags.indexWhere((t) => t.id == id);
tags[index] = tag.copyWith(name: newName);
// 更新所有笔记中的标签名称
for (var note in notes) {
final tagIndex = note.tags.indexOf(oldName);
if (tagIndex != -1) {
note.tags[tagIndex] = newName;
}
}
saveData();
}
renameTag方法首先检查新名称是否已存在,避免重复。然后更新标签对象的名称,并遍历所有笔记,将旧标签名称替换为新名称。这种全局更新确保标签名称的一致性。
标签的合并
支持将多个标签合并为一个,整合相关内容。
void mergeTags(List<String> tagIds, String targetName) {
final tagNames = tagIds.map((id) => tags.firstWhereOrNull((t) => t.id == id)?.name).whereType<String>().toList();
// 创建或获取目标标签
if (!tags.any((t) => t.name == targetName)) {
createTag(targetName);
}
// 更新所有笔记
for (var note in notes) {
final hasAnyTag = note.tags.any((t) => tagNames.contains(t));
if (hasAnyTag) {
note.tags.removeWhere((t) => tagNames.contains(t));
if (!note.tags.contains(targetName)) {
note.tags.add(targetName);
}
}
}
// 删除被合并的标签
tags.removeWhere((t) => tagIds.contains(t.id));
saveData();
}
mergeTags方法接收要合并的标签ID列表和目标标签名称。首先确保目标标签存在,然后遍历所有笔记,将被合并的标签替换为目标标签。最后删除被合并的标签。这个功能在整理标签时很有用。
标签的使用频率统计
统计每个标签的使用频率,帮助用户了解标签的重要性。
Map<String, int> getTagUsageStats() {
final stats = <String, int>{};
for (var tag in tags) {
stats[tag.name] = getNotesByTag(tag.name).length;
}
// 按使用频率降序排序
final sorted = Map.fromEntries(
stats.entries.toList()..sort((a, b) => b.value.compareTo(a.value))
);
return sorted;
}
getTagUsageStats方法返回一个Map,键是标签名称,值是使用次数。遍历所有标签,统计每个标签包含的笔记数量。然后按使用次数降序排序,最常用的标签排在前面。这个统计可以在标签管理页面展示。
标签的颜色标记
为标签添加颜色,让用户可以通过颜色快速识别不同类型的标签。
class Tag {
final String id;
final String name;
final String? color;
final DateTime createdAt;
Tag({
required this.id,
required this.name,
this.color,
required this.createdAt,
});
}
在Tag模型中添加可选的color字段,存储十六进制颜色字符串。用户可以为不同主题的标签设置不同颜色,比如工作相关的用蓝色,生活相关的用绿色。这种视觉标记能够提升标签的可识别性。
标签的搜索
支持按名称搜索标签,快速定位目标。
List<Tag> searchTags(String query) {
if (query.isEmpty) return [];
final lowerQuery = query.toLowerCase();
return tags.where((t) => t.name.toLowerCase().contains(lowerQuery)).toList();
}
searchTags方法实现简单的模糊搜索,不区分大小写。返回名称包含查询字符串的所有标签。这个功能在标签数量很多时特别有用,能够帮助用户快速找到目标标签。
标签的自动建议
在用户输入标签时,提供自动建议功能,减少输入工作量。
List<String> getTagSuggestions(String input) {
if (input.isEmpty) return [];
final lowerInput = input.toLowerCase();
final suggestions = tags
.where((t) => t.name.toLowerCase().startsWith(lowerInput))
.map((t) => t.name)
.toList();
// 按使用频率排序
suggestions.sort((a, b) {
final aCount = getNotesByTag(a).length;
final bCount = getNotesByTag(b).length;
return bCount.compareTo(aCount);
});
return suggestions.take(5).toList();
}
getTagSuggestions方法根据用户输入返回匹配的标签建议。使用startsWith而不是contains,只匹配以输入开头的标签。然后按使用频率排序,最常用的排在前面。最多返回5条建议,避免列表过长。
标签的批量操作
支持批量添加或删除标签,提升操作效率。
void addTagsToNotes(List<String> noteIds, List<String> tagNames) {
for (var noteId in noteIds) {
final note = notes.firstWhereOrNull((n) => n.id == noteId);
if (note != null) {
for (var tagName in tagNames) {
if (!note.tags.contains(tagName)) {
note.tags.add(tagName);
}
}
}
}
saveData();
}
void removeTagsFromNotes(List<String> noteIds, List<String> tagNames) {
for (var noteId in noteIds) {
final note = notes.firstWhereOrNull((n) => n.id == noteId);
if (note != null) {
note.tags.removeWhere((t) => tagNames.contains(t));
}
}
saveData();
}
addTagsToNotes方法为多个笔记批量添加标签,removeTagsFromNotes方法批量删除标签。这两个方法在批量编辑笔记时很有用,可以一次性为多条笔记添加或删除相同的标签。
标签的导出
支持导出标签及其关联的笔记,方便备份和分享。
String exportTag(String tagName) {
final notes = getNotesByTag(tagName);
final buffer = StringBuffer();
buffer.writeln('# 标签: $tagName');
buffer.writeln('笔记数量: ${notes.length}');
buffer.writeln('');
for (var note in notes) {
buffer.writeln('## ${note.title}');
buffer.writeln(note.content);
buffer.writeln('');
}
return buffer.toString();
}
exportTag方法将标签及其所有笔记导出为Markdown格式的文本。标签名称作为一级标题,每个笔记的标题作为二级标题,后面跟着笔记内容。这种格式易于阅读和处理,可以分享给其他人或导入到其他应用。
标签的排序
标签列表应该按照一定规则排序,让用户更容易找到目标。
List<Tag> get sortedTags {
final list = List<Tag>.from(tags);
list.sort((a, b) {
// 按使用频率降序排序
final aCount = getNotesByTag(a.name).length;
final bCount = getNotesByTag(b.name).length;
if (aCount != bCount) {
return bCount.compareTo(aCount);
}
// 使用频率相同时按名称排序
return a.name.compareTo(b.name);
});
return list;
}
sortedTags是一个计算属性,返回排序后的标签列表。排序规则是按使用频率降序,最常用的标签排在前面。使用频率相同时按名称字母顺序排列。这种排序让标签列表更加有序,用户可以快速找到常用标签。
标签云的展示
使用标签云的方式展示所有标签,标签大小反映使用频率。
Widget buildTagCloud() {
final stats = getTagUsageStats();
final maxCount = stats.values.isEmpty ? 1 : stats.values.reduce((a, b) => a > b ? a : b);
return Wrap(
spacing: 8.w,
runSpacing: 8.h,
children: stats.entries.map((e) {
final size = 12 + (e.value / maxCount * 12);
return ActionChip(
label: Text(
'#${e.key}',
style: TextStyle(fontSize: size.sp),
),
onPressed: () => Get.to(() => TagDetailPage(tag: tags.firstWhere((t) => t.name == e.key))),
);
}).toList(),
);
}
buildTagCloud方法创建一个标签云组件。首先获取标签使用统计,计算最大使用次数。然后为每个标签创建ActionChip,字体大小根据使用频率动态调整。使用频率越高,字体越大。这种可视化方式让用户可以直观地看到哪些标签最常用。
标签的智能推荐
根据笔记内容智能推荐相关标签,减少手动输入。
List<String> recommendTags(String content) {
final recommendations = <String>[];
final lowerContent = content.toLowerCase();
for (var tag in tags) {
if (lowerContent.contains(tag.name.toLowerCase())) {
recommendations.add(tag.name);
}
}
// 按使用频率排序
recommendations.sort((a, b) {
final aCount = getNotesByTag(a).length;
final bCount = getNotesByTag(b).length;
return bCount.compareTo(aCount);
});
return recommendations.take(5).toList();
}
recommendTags方法分析笔记内容,推荐相关标签。遍历所有标签,如果标签名称出现在内容中就添加到推荐列表。然后按使用频率排序,最多返回5个推荐。这个功能可以在编辑笔记时使用,帮助用户快速添加合适的标签。
标签管理是笔记应用的重要功能,它提供了一种灵活的组织方式。与文件夹的层级结构不同,标签允许一条笔记属于多个分类,从不同角度查看和筛选内容。通过支持创建、删除、搜索、统计等功能,我们实现了一个功能完善的标签管理系统。用户可以根据自己的需求灵活使用标签,建立个性化的笔记组织方式。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐

所有评论(0)