HarmonyOS时光胶囊——时间线的搜索、视图切换与空状态设计 时间线 Tab 从纯列表筛选升级成了搜索双视图五维筛选空状态引导的完整信息架构。新增的搜索框支持关键词实时过滤视图切换让卡片在列表和网格间自由切换收藏筛选独立于原有四种状态筛选空状态从没有数据变成了引导用户创建第一个胶囊。这些改动让时间线从能用变成了好用。完整效果一、时间线的区块演进版本区块筛选视图基础版标题 筛选 列表4 种仅列表优化版标题 搜索 筛选 内容区5 种列表/网格新增了三个能力搜索框、第五种筛选收藏、列表/网格双视图。二、搜索框的实现搜索 UI 结构Row(){Text().fontSize(FontSize.md).margin({right:Spacing.xs})TextInput({placeholder:搜索...}).layoutWeight(1).height(36).fontSize(FontSize.sm).fontColor(this.getColors().text).placeholderColor(this.getColors().textSecondary).backgroundColor(transparent).onChange((value:string)this.searchKeywordvalue)// 清除按钮if(this.searchKeyword||this.activeTag){Text(✕).fontSize(FontSize.md).fontColor(this.getColors().textSecondary).padding(Spacing.xs).onClick(()this.clearFilters())}// 计数显示if(!this.searchKeyword!this.activeTag){Text(String(this.statsSummary.total)个).fontSize(FontSize.xs).fontColor(this.getColors().textSecondary).margin({left:Spacing.sm})}}.width(100%).height(40).padding({left:Spacing.sm,right:Spacing.sm}).margin({left:Spacing.md,right:Spacing.md,bottom:Spacing.sm}).backgroundColor(this.getColors().surface).borderRadius(BorderRadius.lg).border({width:1,color:this.getColors().border})搜索框的三种状态状态条件显示内容默认无搜索词 无标签筛选搜索图标 “搜索…” 总数搜索中有搜索词搜索图标 输入内容 ✕ 清除标签筛选中activeTag 非空搜索图标 “搜索…” ✕ 清除搜索框同时承载关键词搜索和标签筛选两种功能——✕ 按钮同时清除两种筛选。这是一框多用的设计TextInput 处理自由文本activeTag 处理标签点击两者叠加过滤。TextInput 的透明背景TextInput({placeholder:搜索...}).backgroundColor(transparent)搜索框背景透明——外层 Row 的 surface 色作为背景。这样搜索框和外层容器视觉上融为一体不会出现白色输入框嵌在灰色容器里的双层背景问题。onChange 实时过滤.onChange((value:string)this.searchKeywordvalue)输入时实时更新 State——不需要按搜索按钮。每次输入变化都触发getFiltered()重新计算列表立即更新。这是移动端搜索的标准交互——用户边输边看结果。三、五维筛选系统筛选标签的扩展ForEach([all,sealed,ready,opened,favorites],(key:string){Text(this.getFilterLabel(key)).fontColor(this.activeFilterkey?#FFFFFF:this.getColors().text).backgroundColor(this.activeFilterkey?this.getColors().primary:this.getColors().surface).border({width:1,color:this.activeFilterkey?this.getColors().primary:this.getColors().border}).onClick(()this.activeFilterkey)})五种筛选条件key标签筛选逻辑all全部显示所有胶囊sealed等待未开启 未到期ready✨可开未开启 已到期opened已开已开启favorites❤️收藏favorited true新增收藏筛选——独立于状态筛选。用户可能想看所有收藏的胶囊不管它是等待中还是已开启。收藏是一个跨状态的维度不能用 isOpened 和 unlockAt 判断。getFiltered 的筛选叠加privategetFiltered():Capsule[]{constnowDate.now()letlist:Capsule[][]if(this.activeFilterall){listthis.capsules.slice()}elseif(this.activeFiltersealed){listthis.capsules.filter((c:Capsule)!c.isOpenedc.unlockAtnow)}elseif(this.activeFilterready){listthis.capsules.filter((c:Capsule)!c.isOpenedc.unlockAtnow)}elseif(this.activeFilteropened){listthis.capsules.filter((c:Capsule)c.isOpened)}elseif(this.activeFilterfavorites){listthis.capsules.filter((c:Capsule)c.favorited)}// 关键词搜索叠加if(this.searchKeyword){constkwthis.searchKeyword.toLowerCase()listlist.filter((c:Capsule)c.content.toLowerCase().includes(kw)||c.tags.some((t:string)t.toLowerCase().includes(kw)))}// 标签筛选叠加if(this.activeTag){listlist.filter((c:Capsule)c.tags.some((t:string)tthis.activeTag))}list.sort((a:Capsule,b:Capsule)b.createdAt-a.createdAt)returnlist}三层筛选叠加状态筛选 → 关键词搜索 → 标签筛选。每一层都在上一层的结果上进一步过滤。用户可以同时使用收藏搜索关键词——先筛选收藏再在收藏中搜索。clearFilters 的统一清除privateclearFilters():void{this.searchKeywordthis.activeTagthis.activeFilterall}✕ 按钮一键清除所有筛选——恢复到默认状态。不需要用户分别清除搜索词和标签。四、双视图切换视图切换按钮Text(this.viewModelist?⊞:☰).fontSize(20).width(40).height(40).textAlign(TextAlign.Center).onClick(()this.viewModethis.viewModelist?grid:list)用文字符号作为图标——⊞ 代表网格☰ 代表列表。不需要加载图片资源纯文本渲染。点击在两种视图间切换。列表视图 vs 网格视图特性列表视图网格视图布局Column 垂直排列Flex 2×2 网格卡片宽度100%48%信息密度高完整内容预览低仅图标短文字适用场景阅读内容详情快速浏览状态网格视图的卡片设计Column(){if(getCapsuleState(capsule)CapsuleState.SEALED){Text().fontSize(36)}else{Text(capsule.mood).fontSize(36)}if(getCapsuleState(capsule)CapsuleState.SEALED){Text(this.countdownText(capsule.unlockAt)).fontSize(11).fontColor(this.getColors().primary).fontWeight(FontWeight.Bold).margin({top:4})}elseif(getCapsuleState(capsule)CapsuleState.READY){Text(✨可开启).fontSize(11).fontColor(#F59E0B).fontWeight(FontWeight.Bold).margin({top:4})}else{Text(capsule.content.length15?capsule.content.substring(0,15)…:capsule.content).fontSize(10).fontColor(this.getColors().textSecondary).maxLines(2).textOverflow({overflow:TextOverflow.Ellipsis}).margin({top:4})}}.width(48%).height(100).justifyContent(FlexAlign.Center).backgroundColor(getCapsuleState(capsule)CapsuleState.READY?#FFF8E1:this.getColors().surface).borderRadius(BorderRadius.md)网格卡片的三种状态显示状态图标下方文字背景色SEALED倒计时primary色surfaceREADYcapsule.mood✨可开启黄色#FFF8E1暖黄OPENEDcapsule.mood内容前15字…surfaceREADY 状态的卡片有特殊背景色——暖黄色 #FFF8E1。用户扫一眼网格就能看到哪些胶囊可以开启——黄色卡片在一堆白色卡片里非常醒目。getCapsuleState 函数// 从 model/Capsule.ets 导入functiongetCapsuleState(capsule:Capsule):CapsuleState{if(capsule.isOpened)returnCapsuleState.OPENEDif(capsule.unlockAtDate.now())returnCapsuleState.READYreturnCapsuleState.SEALED}状态计算逻辑和首页 getFiltered 一致——但封装成了独立函数。网格视图和详情页都需要判断状态复用这个函数避免重复代码。五、空状态的引导设计基础版 vs 优化版// 基础版简单文字Column(){Text().fontSize(64)Text(还没有胶囊).fontSize(FontSize.lg)Text(切换到「封印」标签创建第一个).fontSize(FontSize.sm)}// 优化版完整引导Column(){Text().fontSize(72)Text(封印你的第一个胶囊).fontSize(FontSize.lg).fontWeight(FontWeight.Bold)Text(写下想对未来自己说的话\n设定一个未来的时间\n等待时光让它成熟).fontSize(FontSize.sm).textAlign(TextAlign.Center).lineHeight(22)Row(){Text(✍️ 开始封印).fontSize(FontSize.md).fontColor(#FFFFFF).fontWeight(FontWeight.Bold)}.backgroundColor(this.getColors().primary).borderRadius(BorderRadius.full).onClick(()this.currentTabcreate)}空状态的四个层次层次内容作用图标 72px吸引注意力标题“封印你的第一个胶囊”告诉用户该做什么描述三行引导文字解释产品价值按钮“✍️ 开始封印”直接跳转到封印 Tab从告知变成引导——不只是说没有数据而是告诉用户你可以做什么。按钮直接跳转到封印 Tabthis.currentTab create减少用户操作路径。描述文字的三行结构写下想对未来自己说的话 ← 功能价值 设定一个未来的时间 ← 操作步骤 等待时光让它成熟 ← 情感共鸣三行文字分别回答是什么“怎么做”“为什么”。这是产品文案的经典结构——功能步骤情感。最后一行等待时光让它成熟把等待这件事变得浪漫而不是枯燥的倒计时。六、标题栏的两个按钮Row(){Text(时光胶囊).fontSize(FontSize.title).fontColor(this.getColors().text).fontWeight(FontWeight.Bold)Blank()Text(this.viewModelist?⊞:☰).fontSize(20).width(40).height(40).onClick(()this.viewModethis.viewModelist?grid:list)Text().fontSize(20).width(40).height(40).onClick(()this.goToShuffle())}两个按钮的分工按钮图标功能视图切换⊞ / ☰列表/网格切换记忆洗牌跳转 MemoryShuffle标题栏承载了两个不常用但需要快速访问的功能。视图切换和洗牌不是核心操作核心是筛选和查看但放在标题栏比放在筛选栏或底部更合适——不占用筛选空间不影响主流程。七、搜索与筛选的交互优先级用户操作路径 1. 打开时间线 Tab → 显示所有胶囊按时间降序 2. 点击筛选标签 → 按状态/收藏筛选 3. 输入搜索关键词 → 在筛选结果中进一步过滤 4. 点击标签 → 在搜索结果中再过滤 5. 点击 ✕ → 清除所有筛选回到步骤 1筛选和搜索是且的关系——同时生效。用户可以收藏中的搜索’旅行’——先筛选收藏再在收藏中搜索包含旅行的胶囊。搜索的范围list.filter((c:Capsule)c.content.toLowerCase().includes(kw)||c.tags.some((t:string)t.toLowerCase().includes(kw)))搜索覆盖两个字段内容和标签。不搜索心情——因为心情是 emoji 不是文字。不搜索 ID——因为用户不会搜索 ID。toLowerCase()保证大小写不敏感。八、数据流的完整链路用户输入搜索词 → searchKeyword 更新 → getFiltered() 重新计算 → 三层层叠筛选状态→关键词→标签 → 返回过滤后的列表 → ForEach 重新渲染五个 State 变量协同变量作用触发筛选activeFilter状态/收藏筛选是searchKeyword关键词搜索是activeTag标签筛选是viewMode视图切换否只影响渲染方式capsules原始数据是数据源五个变量中三个参与筛选一个影响渲染方式一个是数据源。筛选逻辑和视图逻辑完全分离——改筛选不影响视图改视图不影响筛选。九、整体嵌套结构Column buildTimeline 容器 ├→ Row (标题) 标题 视图切换 洗牌 ├→ Row (搜索框) 搜索图标 TextInput 清除/计数 ├→ Scroll Row (筛选标签) 五种筛选的横向滚动 └→ 内容区三选一 ├→ 空状态 Column 无胶囊时的引导 ├→ Scroll Flex (网格视图) viewMode grid └→ List ForEach (列表视图) viewMode list内容区用 if-else 三选一——空状态、网格、列表互斥。空状态优先判断capsules.length 0有数据时再判断视图模式。嵌套深度 5 层Column → Scroll → Row → ForEach → Text (筛选标签) Column → Scroll → Flex → Column → Text (网格卡片)5 层嵌套——比首页更深。因为时间线增加了搜索和双视图嵌套层级增加了。但在 ArkTS 里 5 层是可接受的——超过 7 层才需要考虑拆分。