一、应用概述
标签管理(Tag Management)是一个功能完整的标签 CRUD 应用,支持创建标签(指定名称和颜色)、切换选中状态、删除标签,并实时显示已选标签列表。标签以圆角「药丸」形状展示,不同颜色区分不同类别。
在 HarmonyOS 开发中,标签管理是理解 Flex 流式布局 、多状态交互 和 计算属性 的典型场景。与便签本的 Grid 固定网格不同,标签使用 Flex 的流式布局实现自动换行,每个标签同时承载「选中/未选中」和「正常/删除」两种交互状态------这种多状态设计在移动应用中非常常见。
二、技术架构
2.1 应用结构
┌─────────────────────────────────────┐
│ ‹ 返回 🏷️ 标签管理 已选 N │
├─────────────────────────────────────┤
│ [输入框...] ○○○○○○○○ [+] │ ← 输入+颜色选择+添加
├─────────────────────────────────────┤
│ ┌──────────┐ ┌──────┐ ┌──────┐ │
│ │ ⚫ 工作 ✕ │ │ 🔵 学习 ✕ │ │ 🟢 生活 ✕ │ ← Flex 流式标签
│ └──────────┘ └──────┘ └──────┘ │
├─────────────────────────────────────┤
│ 已选标签: │
│ #工作 #学习 │ ← 选中结果显示
└─────────────────────────────────────┘
2.2 数据模型
arkts
interface Tag {
id: number;
name: string;
color: string;
selected: boolean; // 选中状态
}
2.3 状态变量
| 变量 | 类型 | 用途 |
|---|---|---|
tags |
Tag[] |
标签数组,驱动所有渲染 |
inputText |
string |
新建标签的输入 |
currentColor |
string |
当前选中颜色 |
nextId |
number |
自增 ID |
selectedTags |
Tag[] (getter) |
计算属性,过滤已选标签 |
三、核心代码深度解析
3.1 标签选中切换
标签的选中/未选中状态切换是应用的核心交互:
arkts
toggleTag(id: number): void {
const newTags: Tag[] = this.tags.concat();
for (let i = 0; i < newTags.length; i++) {
if (newTags[i].id === id) {
newTags[i] = {
id: newTags[i].id,
name: newTags[i].name,
color: newTags[i].color,
selected: !newTags[i].selected
};
}
}
this.tags = newTags;
}
为什么需要创建新对象?
注意 newTags[i] = { ...selected: !newTags[i].selected } 这里创建了一个新对象 替代原对象,而不是直接修改 newTags[i].selected = !newTags[i].selected。这是因为:
@State是浅层响应式的------它只检测数组引用变化和对象引用变化- 如果只修改
selected属性(对象内部的字段),而对象引用不变,ArkUI 不会检测到这个变化 - 所以必须创建新对象(
{ ... }),产生新引用,触发 UI 更新
3.2 计算属性:已选标签
arkts
get selectedTags(): Tag[] {
const result: Tag[] = [];
for (let i = 0; i < this.tags.length; i++) {
if (this.tags[i].selected) {
result.push(this.tags[i]);
}
}
return result;
}
这是一个 getter 计算属性,特点:
- 每次访问都重新计算
- 依赖于
tags数组 - 当
tags变化时,所有引用了selectedTags的 UI 部分自动刷新
UI 中显示已选标签数量:
arkts
Text('已选 ' + this.selectedTags.length).fontSize(13).fontColor('#888')
实时显示选中数量,提供即时反馈。
已选标签详情展示:
arkts
if (this.selectedTags.length > 0) {
Column() {
Text('已选标签:').fontSize(14).fontColor('#888').margin({ bottom: 4 })
Text(this.selectedTags.map(t => '#' + t.name).join(' '))
.fontSize(16).fontWeight(FontWeight.Medium).fontColor('#333')
}
.padding(16).backgroundColor('#fff').borderRadius(12).width('92%').margin({ top: 16 })
.alignItems(HorizontalAlign.Start)
}
使用 map + join 将标签数组转换为 #工作 #学习 #生活 的字符串格式,模仿社交媒体的标签展示方式。
3.3 Flex 流式标签渲染
标签列表使用 Flex({ wrap: FlexWrap.Wrap }) 实现流式排列:
arkts
Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.Start }) {
ForEach(this.tags, (tag: Tag) => {
Row({ space: 4 }) {
Text(tag.name).fontSize(15)
Text('✕').fontSize(13).fontColor('#ffffffaa')
.onClick(() => this.deleteTag(tag.id))
}
.padding({ top: 6, bottom: 6, left: 14, right: 10 })
.backgroundColor(tag.color)
.borderRadius(20).margin(4)
.opacity(tag.selected ? 1 : 0.7)
.onClick(() => this.toggleTag(tag.id))
}, (tag: Tag) => tag.id.toString())
}.width('92%')
标签交互事件冒泡处理:
这里有一个重要的交互细节------事件冒泡:
- 点击标签主体(
Row)→ 触发toggleTag(切换选中状态) - 点击删除按钮(
Text('✕'))→ 触发deleteTag(删除标签)
由于删除按钮是 Row 的子组件,用户点击 ✕ 时,会先触发子组件的 onClick(删除),是否会触发父组件的 onClick?在 ArkUI 中,事件不会自动冒泡------点击子组件的 ✕ 只触发删除,不会触发标签的 toggle。这避免了「点击删除却切换了选中状态」的冲突。
3.4 颜色选择器
arkts
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(TAG_COLORS, (c: string) => {
Column() {
Circle().width(22).height(22).fill(c)
.opacity(this.currentColor === c ? 1 : 0.5)
.stroke(this.currentColor === c ? '#333' : 'transparent').strokeWidth(2)
}
.padding(2).onClick(() => { this.currentColor = c })
})
}.width(200)
与便签本的颜色选择器类似,但有两个不同点:
- 固定宽度
200:颜色选择区占固定宽度,输入框用layoutWeight(1)占剩余空间 - 外层 Column 包裹 :多了一层
Column容器,为 Circle 提供点击区域,同时padding(2)增加触控热区
选中状态指示:
- 透明度区分:选中 1.0,未选中 0.5
- 边框区分:选中时黑色 2px 边框,未选中时透明
四、HarmonyOS 特色功能深度解析
4.1 Flex 流式标签布局
Flex 流式布局是实现标签云(Tag Cloud)场景的首选方案:
arkts
Flex({
wrap: FlexWrap.Wrap,
justifyContent: FlexAlign.Start
})
与传统 Row 的区别:
| 特性 | Row | Flex + Wrap |
|---|---|---|
| 换行能力 | 不换行 | 自动换行 |
| 子项溢出行为 | 缩小或溢出 | 折行显示 |
| 适用场景 | 固定数量图标 | 动态数量标签 |
| 对齐灵活性 | 主轴对齐 | 主轴+交叉轴对齐 |
justifyContent: FlexAlign.Start :标签从左到右排列,换行后新行也从左开始。如果设为 Center,则每行居中对齐,视觉效果不同。
4.2 margin 与 padding 的巧用
标签的「药丸」形状通过内外边距的配合实现:
arkts
.padding({ top: 6, bottom: 6, left: 14, right: 10 })
.borderRadius(20)
.margin(4)
padding(14, 10):左右不对称(左 14、右 10),因为右侧有 ✕ 图标需要额外空间.borderRadius(20):超过高度一半(高度约 36px),形成圆角矩形(药丸形状).margin(4):标签之间的间距
药丸形状公式 :当 borderRadius >= height / 2 时,矩形的两端变为半圆,形成药丸(pill)形状。这里高度 ≈ 36px(6+6+字体高度),borderRadius=20 > 18,所以两端是完美的半圆形。
4.3 透明度状态切换
arkts
.opacity(tag.selected ? 1 : 0.7)
使用透明度(而非颜色变化)来表示选中状态:
- 选中时完全不透明(1.0),色彩饱满
- 未选中时轻微透明(0.7),视觉上「后退」一些
这种设计的好处是:保持颜色辨识度的同时提供状态区分。如果使用灰度变化,颜色信息会丢失;如果使用边框变化,会增加视觉杂乱度。透明度变化是最优雅的方案。
五、UI/UX 设计分析
5.1 色彩体系
8 种预设颜色覆盖了完整的色相范围:
| 颜色 | 色值 | 心理联想 |
|---|---|---|
| 红色 | #F44336 |
紧急、重要、工作 |
| 橙色 | #FF9800 |
创意、活力 |
| 金色 | #FFD700 |
珍贵、重要 |
| 绿色 | #4CAF50 |
完成、生活 |
| 蓝色 | #0077FF |
专业、学习 |
| 紫色 | #9C27B0 |
灵感、艺术 |
| 棕色 | #795548 |
稳重、日常 |
| 灰蓝 | #607D8B |
中性、其他 |
每个标签的背景色直接使用其颜色,文字为白色,形成清晰的对比。
5.2 交互反馈
- 添加标签:输入框 → 选择颜色 → 点击 ➕,标签出现在列表末尾
- 选中/取消:点击标签切换选中,透明度变化反馈
- 删除标签:点击 ✕ 立即删除,无二次确认
- 实时计数:导航栏右侧「已选 N」实时更新
5.3 信息架构
导航栏:返回 + 标题 + 计数
输入区:输入框 + 颜色选择器 + 添加按钮
标签区:Flex 流式排列的所有标签
详情区:已选标签的文字展示(条件渲染)
六、最佳实践与优化建议
6.1 当前代码亮点
- 计算属性分离 :
selectedTags将「筛选逻辑」从 UI 中抽离,职责清晰 - 事件不冒泡:利用 ArkUI 事件不冒泡的特性,避免删除和切换互相干扰
- 不可变更新模式:所有数组和对象修改都创建新引用
- 触控热区优化 :
padding(2)扩大颜色圆点的点击区域
6.2 可优化方向
arkts
// 优化建议 1:标签编辑功能
editTag(id: number, newName: string): void {
const newTags = this.tags.map(tag =>
tag.id === id ? { ...tag, name: newName } : tag
);
this.tags = newTags;
}
// 优化建议 2:标签分组/分类
interface TagGroup {
groupName: string;
tags: Tag[];
}
// 优化建议 3:标签搜索筛选
get filteredTags(): Tag[] {
if (!this.searchTerm) return this.tags;
return this.tags.filter(t => t.name.includes(this.searchTerm));
}
// 优化建议 4:批量操作
selectAll(): void {
this.tags = this.tags.map(t => ({ ...t, selected: true }));
}
clearSelection(): void {
this.tags = this.tags.map(t => ({ ...t, selected: false }));
}
deleteSelected(): void {
this.tags = this.tags.filter(t => !t.selected);
}
// 优化建议 5:数据持久化
import { preferences } from '@kit.ArkData';
async persistTags(): Promise<void> {
const prefs = await preferences.getPreferences(this.context, 'tagStore');
await prefs.put('tags', JSON.stringify(this.tags));
await prefs.flush();
}
6.3 性能优化
- 标签数量控制 :虽然 Flex 流式布局性能良好,但当标签超过 200 个时,应考虑使用
LazyForEach搭配Scroll实现虚拟化 - 颜色选择器渲染:8 种颜色始终固定,不存在性能问题。如果扩展为 50+ 颜色,应考虑网格布局 + 懒加载
- 计算属性缓存 :
selectedTags每次访问都重新遍历,在大型数据集下可以用@Monitor装饰器手动缓存
七、总结与扩展
标签管理应用是理解 ArkUI 多状态交互 和 Flex 流式布局 的经典案例:
| 知识点 | 应用位置 | 重要程度 |
|---|---|---|
| Flex + Wrap 流式布局 | 标签列表 & 颜色选择器 | ★★★★★ |
| 不可变对象更新 | toggleTag 方法 | ★★★★★ |
| 计算属性 getter | selectedTags | ★★★★☆ |
| 透明度状态切换 | 选中/未选中视觉反馈 | ★★★★☆ |
| 事件不冒泡特性 | 删除与切换共存 | ★★★☆☆ |
| 药丸形状设计 | borderRadius 技巧 | ★★★☆☆ |
扩展思路:
- 标签云可视化:根据标签使用频率调整字体大小,生成标签云(Tag Cloud)
- 关联内容:标签不只是字符串,可以关联笔记、照片等内容
- 颜色主题 :支持亮色/暗色主题切换,使用
@ohos.configuration监听系统主题变化 - 智能建议:根据输入历史推荐已有标签名称
- 标签导入导出:支持从 JSON/CSV 导入导出标签配置
- 多选删除:长按进入编辑模式,支持批量删除标签
标签管理应用展示了在有限空间内处理「创建-读取-更新-删除」完整数据流的能力。它的核心设计模式------流式布局 + 多状态交互------可以复用到筛选器、分类选择、关键词标记等多种场景中。