# 44号应用:标签管理 — Flex 流式标签与交互状态设计

一、应用概述

标签管理(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%')

标签交互事件冒泡处理

这里有一个重要的交互细节------事件冒泡

  1. 点击标签主体(Row)→ 触发 toggleTag(切换选中状态)
  2. 点击删除按钮(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)

与便签本的颜色选择器类似,但有两个不同点:

  1. 固定宽度 200 :颜色选择区占固定宽度,输入框用 layoutWeight(1) 占剩余空间
  2. 外层 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 交互反馈

  1. 添加标签:输入框 → 选择颜色 → 点击 ➕,标签出现在列表末尾
  2. 选中/取消:点击标签切换选中,透明度变化反馈
  3. 删除标签:点击 ✕ 立即删除,无二次确认
  4. 实时计数:导航栏右侧「已选 N」实时更新

5.3 信息架构

复制代码
导航栏:返回 + 标题 + 计数
输入区:输入框 + 颜色选择器 + 添加按钮
标签区:Flex 流式排列的所有标签
详情区:已选标签的文字展示(条件渲染)

六、最佳实践与优化建议

6.1 当前代码亮点

  1. 计算属性分离selectedTags 将「筛选逻辑」从 UI 中抽离,职责清晰
  2. 事件不冒泡:利用 ArkUI 事件不冒泡的特性,避免删除和切换互相干扰
  3. 不可变更新模式:所有数组和对象修改都创建新引用
  4. 触控热区优化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 技巧 ★★★☆☆

扩展思路

  1. 标签云可视化:根据标签使用频率调整字体大小,生成标签云(Tag Cloud)
  2. 关联内容:标签不只是字符串,可以关联笔记、照片等内容
  3. 颜色主题 :支持亮色/暗色主题切换,使用 @ohos.configuration 监听系统主题变化
  4. 智能建议:根据输入历史推荐已有标签名称
  5. 标签导入导出:支持从 JSON/CSV 导入导出标签配置
  6. 多选删除:长按进入编辑模式,支持批量删除标签

标签管理应用展示了在有限空间内处理「创建-读取-更新-删除」完整数据流的能力。它的核心设计模式------流式布局 + 多状态交互------可以复用到筛选器、分类选择、关键词标记等多种场景中。

相关推荐
想你依然心痛15 小时前
ArkTS 布局系统概述——从线性到网格,掌握声明式 UI 的骨架艺术
harmonyos·arkts·flex弹性布局·声明式布局·grid网格布局·响应式适配·布局性能优化
程序员黑豆18 小时前
鸿蒙应用开发:@Computed 装饰器详解与实战
前端·harmonyos
ldsweet18 小时前
HarmonyOS NEXT 音频播放器开发:AVPlayer 封装、播放列表与后台播放实战
华为·音视频·harmonyos
2601_9609067220 小时前
华为MateBook Pro S首发搭载麒麟XE90
华为·postgresql·sqlite·时序数据库·tdengine
哎呦喂我去去去20 小时前
HarmonyOS SDK助力讯飞听见App能力建设
华为·harmonyos
lilian2331 天前
# Harmony os 技术实战|拼豆制图05:让 50 张本地图纸搜得准、排得稳
华为·harmonyos
想你依然心痛1 天前
Slider 滑块组件深度解析与交互定制全攻略
harmonyos·arkui·slider·双向绑定·sliderchange·sliderange·contentmodifier
速石科技1 天前
速石科技FAAP全面适配华为昇腾,构建下一代自主创新AI基础设施
人工智能·科技·华为
义一1 天前
网关简介和图示解析
网络·华为