news 2026/9/4 4:58:55

HarmonyOS ArkTS实战:一种动态一种颜色 —— ACG 我的动态页的多状态时间轴着色法

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
HarmonyOS ArkTS实战:一种动态一种颜色 —— ACG 我的动态页的多状态时间轴着色法

HarmonyOS ArkTS实战:一种动态一种颜色 —— ACG 我的动态页的多状态时间轴着色法

应用背景:12 ACG 社区(acg-community)第三个 Tab 是"作品",对应源码Func2Tab.ets(共 207 行),页面标题「我的动态」。它是用户回溯自己在社区里一切行为的流水账,玫红主题#DB2777。本文逐行拆解TabBar 动态类型分类OverviewCard 社区数据概览TimelineList 多状态动态时间轴三大模块,重点讲解"一种动态一种颜色"的多状态着色体系,以及"个人动态时间轴"与"物流时间轴"的本质差异。

如果说首页是"看别人的内容",那"我的动态"就是"看自己的足迹"。社区用户会在意"我发过什么、评论过什么、收藏过什么、点赞过什么",这些分散的行为需要一条时间轴串起来,形成"我在社区的完整轨迹"。与 11 号快递的"取件记录"相比,这一页的动态类型更丰富、状态色更多元——取件记录只有"待取/已取"两态,而社区动态有"发帖/评论/收藏/点赞/审核"五种。读懂这一页,你就掌握了"多类型动态流"这一社区高频 UI 的声明式实现。

一、数据模型:三种结构

"我的动态"页定义了三个接口,其中TimelineItem最能体现"社区动态"的数据特征:

interface TimelineItem { id: number; emoji: string; title: string; date: string; author: string; status: string; statusColor: string; progress: number; } interface StatCard { label: string; value: string; emoji: string; } interface TabItem { label: string; count: number; }

TimelineItem与 11 号取件页的同名接口高度相似,但把code(单号)换成了author(作者),statusColor从"三态色"扩展为"多态色"。author字段在 demo 里固定为"我",语义是"这条动态是谁做的"——个人动态页里作者恒为"我",但真实项目里若是"关注的人动态",作者就会是他人。这个字段的预留,暗示了这一页的可扩展方向:把"我的动态"改成"关注动态",只需换author数据,UI 不用动

statusColor被设计成"数据字段"而非"渲染时计算",这是本页最值得注意的设计决策。它意味着每条动态的颜色由数据直接指定,UI 只负责"读出来画上去",不写任何三元判断。这个决策的利弊后面会展开。

项目源码开源:https://gitee.com/codenestFlow/HarmonyOSHub

二、页面骨架

build() { Column() { this.Header() this.TabBar() Scroll() { Column({ space: 14 }) { this.OverviewCard() this.TimelineList() } .width('100%') .padding({ left: D.pad, right: D.pad, top: 14, bottom: D.pad + this.safeBottom + 20 }) } .layoutWeight(1).scrollBar(BarState.Off).align(Alignment.Top) } .width('100%').height('100%').backgroundColor(C.bg) }

Header + TabBar + Scroll三段式,与取件页一致。TabBar是吸顶的动态类型筛选条,永远停在 Header 下方。Header标题是「我的动态」,TabBar的四类(全部/帖子/评论/收藏)正是动态的四种类型维度。

三、Header 与 TabBar 动态类型分类

Header 与系列统一写法(.height(safeTop + 56)+alignItems(Bottom))。重点是 TabBar——它的分类从取件页的"状态"变成了"动态类型":

// TabBar — 动态类型分类 @Builder TabBar() { Row({ space: 6 }) { ForEach(this.tabs, (t: TabItem, idx: number) => { Row({ space: 4 }) { Text(t.label).fontSize(13).fontColor(this.tabIdx === idx ? '#FFFFFF' : C.textSub) Text(t.count.toString()).fontSize(10).fontColor(this.tabIdx === idx ? '#FFFFFF' : C.textDim) .padding({ left: 5, right: 5, top: 1, bottom: 1 }) .backgroundColor(this.tabIdx === idx ? '#33FFFFFF' : C.cardSoft) .borderRadius(8) } .padding({ left: 14, right: 14, top: 8, bottom: 8 }) .backgroundColor(this.tabIdx === idx ? C.primary : C.card) .borderRadius(18) .border({ width: 1, color: this.tabIdx === idx ? C.primary : C.stroke }) .onClick(() => { this.tabIdx = idx; }) }, (t: TabItem) => t.label) } .width('100%').padding({ left: D.pad, right: D.pad, top: 12, bottom: 12 }) .backgroundColor(C.card) }

四个标签「全部 28 / 帖子 15 / 评论 8 / 收藏 5」,选中项"玫红底白字 + 半透明白计数徽标"。注意计数徽标在选中时用'#33FFFFFF'(半透明白底),未选中用C.cardSoft(浅灰底)——选中态的徽标"融"进玫红底里,视觉更统一。

分类维度是"动态类型"而非"状态":取件页按"待取/已取/退回"分状态,这里按"帖子/评论/收藏"分类型。因为社区用户关心的不是"我的动态完成没",而是"我发的帖子有哪些、我的评论有哪些、我收藏了什么"——类型是内容社区的第一分类维度,状态只是帖子内部的子属性。这个差异再次说明:筛选标签的维度选择,取决于业务的核心关切。

四、OverviewCard 社区数据概览

概览卡用 3 格统计 + 一行明细,呈现用户的社区影响力:

// OverviewCard — 数据概览 @Builder OverviewCard() { Column({ space: 14 }) { Row() { ForEach(this.statCards, (s: StatCard) => { Column({ space: 4 }) { Text(s.emoji).fontSize(22) Text(s.value).fontSize(18).fontWeight(FontWeight.Bold).fontColor(C.text) Text(s.label).fontSize(11).fontColor(C.textDim) } .layoutWeight(1).alignItems(HorizontalAlign.Center) }, (s: StatCard) => s.label) }.width('100%') Divider().color(C.stroke).strokeWidth(0.5) Row({ space: 12 }) { Column({ space: 2 }) { Text('本周发帖').fontSize(11).fontColor(C.textDim) Text('7 帖').fontSize(14).fontColor(C.text).fontWeight(FontWeight.Medium) } .alignItems(HorizontalAlign.Start).layoutWeight(1) Column({ space: 2 }) { Text('平均点赞').fontSize(11).fontColor(C.textDim) Text('42 赞').fontSize(14).fontColor(C.text).fontWeight(FontWeight.Medium) } .alignItems(HorizontalAlign.Start).layoutWeight(1) Column({ space: 2 }) { Text('活跃排名').fontSize(11).fontColor(C.textDim) Text('Top 8%').fontSize(14).fontColor(C.text).fontWeight(FontWeight.Medium) } .alignItems(HorizontalAlign.Start).layoutWeight(1) }.width('100%') } .width('100%').padding(16) .backgroundColor(C.card).borderRadius(D.rLg) .border({ width: 1, color: C.stroke }) }

三格统计「我的帖子 15 / 获赞总数 1.2k / 粉丝 286」,用 emoji(📝/❤️/👥)+ 数值 + 标签。下方明细「本周发帖 7 帖 / 平均点赞 42 赞 / 活跃排名 Top 8%」用Divider隔开。这些指标共同回答"我在社区有多活跃、多受欢迎"——对比取件页的"平均取件时长/代取费/退回率",社区指标从"服务效率"转向"影响力"(帖子数、点赞、粉丝、排名),这是社区类应用衡量用户价值的核心维度。

活跃排名 Top 8%是一个很有社区味的指标——用"百分位排名"而非绝对名次,既能激励用户("你超过 92% 的人"),又避免了"第 286 名"这种尴尬的绝对名次。这种"相对排名"的展示手法,在游戏、社交、内容社区里都很常见,是产品设计的巧思。

五、TimelineList 多状态动态时间轴(核心)

这是全页的灵魂。时间轴用"圆节点 + 条件竖线"串起六条动态,每条动态根据类型使用不同的状态色:

// TimelineList — 动态时间轴 @Builder TimelineList() { Column({ space: 0 }) { ForEach(this.timeline, (item: TimelineItem, idx: number) => { Column() { Row({ space: 12 }) { Column({ space: 0 }) { Row() { Text(item.emoji).fontSize(18) } .width(36).height(36) .backgroundColor(C.primarySoft).borderRadius(18) .justifyContent(FlexAlign.Center) if (idx < this.timeline.length - 1) { Column().width(2).height(28).backgroundColor(C.stroke) } } .alignItems(HorizontalAlign.Center) Column({ space: 8 }) { Row() { Text(item.title).fontSize(13).fontColor(C.text).fontWeight(FontWeight.Medium).layoutWeight(1) }.width('100%') Row() { Text(item.date).fontSize(11).fontColor(C.textDim).layoutWeight(1) Text(item.status).fontSize(10).fontColor(item.statusColor) .padding({ left: 8, right: 8, top: 2, bottom: 2 }) .backgroundColor(C.cardSoft).borderRadius(4) }.width('100%') Progress({ value: item.progress, total: 100 }).color(item.statusColor).width('100%') } .alignItems(HorizontalAlign.Start).layoutWeight(1) } .width('100%').alignItems(VerticalAlign.Top) } .width('100%').padding({ bottom: 4 }) .onClick(() => { promptAction.showToast({ message: item.title }); }) }, (item: TimelineItem) => item.id.toString()) } .width('100%').padding(14) .backgroundColor(C.card).borderRadius(D.rLg) .border({ width: 1, color: C.stroke }) }

时间轴的"节点 + 竖线"结构与取件页完全一致(36×36 圆节点 +if (idx < length-1)条件竖线 + 竖线height(28)固定高),但右侧信息区的语义与取件页显著不同:

其一,title是"动词短语"而非"名词"。取件页的 title 是"顺丰速运 · 待取件"(快递公司名),这里是"发表了帖子「夏番追番清单」"(行为 + 对象)。社区动态的 title 天然是"我做了什么",用"动词 + 宾语"的句式,用户扫一眼就知道这是"发帖/评论/收藏/点赞"哪种行为。

其二,状态色四态化。取件页只有"待取(黄)/已取(绿)/退回(红)"三态;这里的状态色扩展到四种:已发布=绿(C.ok)、已收藏=玫红(C.primary)、审核中=黄(C.warn)、已点赞=红(C.danger)。四种颜色对应四种动态类型,让用户通过颜色就能快速区分"这是发帖、收藏、待审核还是点赞"。

其三,Progress的颜色也跟随item.statusColor。进度条颜色和状态标签颜色绑定同一个字段——审核中的帖子进度 50%、进度条是黄色;已发布的进度 100%、进度条是绿色。颜色语义贯穿"标签 + 进度条",视觉高度统一。

这里有个值得深挖的设计决策:statusColor是数据字段,不是渲染时算出来的。取件页是fontColor(item.statusColor)(同样读字段),但状态色只有三态、且可以简单三元算出;而社区动态有四种类型、且颜色与类型强绑定,用数据字段直接指定比写三元/映射更直白。不过更工程化的做法是"类型字段 + 颜色映射表"(数据存type,渲染时查TYPE_COLOR[type]),这样颜色统一维护、数据不冗余。两种方案各有取舍:字段直存颜色省事但把"表现"混进了"数据",类型 + 映射表干净但多一层查表。demo 选了前者,读者可自行权衡。

六、动态类型的数据样本

六条动态数据本身也值得一读:

private timeline: TimelineItem[] = [ { id: 1, emoji: '🌸', title: '发表了帖子「夏番追番清单」', date: '2小时前', author: '我', status: '已发布', statusColor: C.ok, progress: 100 }, { id: 2, emoji: '💬', title: '评论了「原神5.0攻略」', date: '5小时前', author: '我', status: '已发布', statusColor: C.ok, progress: 100 }, { id: 3, emoji: '⭐', title: '收藏了「同人画作精选」', date: '8小时前', author: '我', status: '已收藏', statusColor: C.primary, progress: 100 }, { id: 4, emoji: '🎮', title: '发表了帖子「游戏推荐」', date: '昨天', author: '我', status: '审核中', statusColor: C.warn, progress: 50 }, { id: 5, emoji: '❤️', title: '点赞了「轻小说推荐」', date: '昨天', author: '我', status: '已点赞', statusColor: C.danger, progress: 100 }, { id: 6, emoji: '📝', title: '发表了帖子「杂谈讨论」', date: '2天前', author: '我', status: '已发布', statusColor: C.ok, progress: 100 } ];

六条动态覆盖了五种行为:发帖(🌸/🎮/📝)、评论(💬)、收藏(⭐)、点赞(❤️)、以及"审核中"这一中间态。emoji 与行为强关联(🌸 追番帖、💬 评论、⭐ 收藏、❤️ 点赞),status 与 statusColor 一一对应(已发布=绿、已收藏=玫红、审核中=黄、已点赞=红)。"审核中"的进度 50、绿色之外的三色点缀,让这条时间轴比取件页的"待取/已取"单调配色生动得多。

"审核中"这个状态是社区内容审核机制的体现——社区帖子发布后需经审核才能公开,这是内容社区与物流业务的本质区别:物流是"物理流程"(运输进度线性推进),社区是"内容流程"(发布 → 审核 → 公开,可能打回)。时间轴因此承载了"内容生命周期"的可视化,而不只是"运输进度"。

七、交互:类型筛选与状态驱动

与取件页一样,tabIdx是唯一的@State,当前只驱动 TabBar 高亮。源码里timeline写死,点击"帖子/评论/收藏"标签并不会真正过滤列表——这是留给读者的扩展点。真实实现只需维护private allTimeline(全量)+ 按tabIdx过滤出timeline

  • tabIdx=0(全部):不过滤;
  • tabIdx=1(帖子):filter(t => t.status === '已发布' || t.status === '审核中')
  • tabIdx=2(评论):filter(t => t.title.includes('评论'))
  • tabIdx=3(收藏):filter(t => t.status === '已收藏')

关键点:动态的类型信息需要先建模进数据(如加一个type字段),否则只能靠字符串includes这种脆弱的做法过滤。这也是为什么接口设计时就要想清楚"筛选维度有哪些、用什么字段承载"。demo 里author字段被预留、type字段缺失,正是"数据建模不完整"的典型反面教材——真实接数据前,务必补上type字段。

八、与取件记录页的"物流 vs 内容"对比

把 12 号"我的动态"与 11 号"取件记录"并排看,两者共享"TabBar + OverviewCard + TimelineList"的骨架,但业务差异鲜明:

  • 分类维度:取件页按"状态"(待取/已取/退回),动态页按"类型"(帖子/评论/收藏);
  • 时间轴语义:取件页是"物流轨迹"(运输进度线性推进),动态页是"行为轨迹"(发帖/评论/收藏/点赞的行为流);
  • 状态色:取件页三态(黄/绿/红),动态页四态(绿/玫红/黄/红);
  • 概览指标:取件页是"服务效率"(取件时长/代取费/退回率),动态页是"影响力"(帖子/点赞/粉丝/排名)。

这些差异浓缩成一句话:同一条时间轴组件,承载的是两种完全不同的"流"——物流的流和内容的流。学会区分"流程型时间轴"(物流、订单、审批)与"行为型时间轴"(动态、足迹、操作记录),你就能判断任何时间轴该用什么数据、什么分类、什么配色。

九、本章小结与工程经验

模块核心技术学习价值
TabBar类型分类 + 计数徽标动态类型筛选
OverviewCardemoji 统计 + 影响力指标社区数据概览
TimelineList圆节点 + 条件竖线 + 四态色多状态行为时间轴
statusColor数据字段驱动颜色多态着色体系

"我的动态"页最值得迁移的是"多状态行为时间轴"范式:圆节点 + 条件竖线搭骨架,title用"动词 + 宾语"描述行为,statusColor按类型赋予不同颜色(发帖=绿、收藏=玫红、审核=黄、点赞=红),Progress颜色跟随状态。这套范式能覆盖一切"用户行为流"(动态、足迹、通知、操作记录)。

其次要牢记"流程型 vs 行为型"时间轴的区分:流程型(物流/订单)状态线性推进、颜色少;行为型(动态/足迹)类型多样、颜色多。最后,筛选维度要提前建模进数据——动态页的type字段缺失是典型教训,接真实数据前务必补全,否则筛选只能靠脆弱的字符串匹配。

十、可扩展方向

这一页有很强的扩展潜力,读者可自行尝试:把author从"我"扩展为真实用户,这一页就变成"关注动态流";给TimelineItemtype字段,就能实现真正的类型筛选;把progress语义从"审核进度"扩展为"内容生命周期"(草稿/审核中/已发布/已下架),时间轴就变成了"内容管理流"。同一条时间轴,改数据、改字段、改颜色,就能演化出动态流、通知流、审批流——这正是"行为型时间轴"作为通用组件的价值。

十一、竖线高度:固定值 vs 跟随内容

本页时间轴的竖线用的是.height(28)固定值,这与 11 号取件页一致。固定高度有个隐含假设:每条动态的右侧内容高度恰好约 28vp。当动态只有一行 title + 一行 date + 一条进度条时,28 刚好衔接上下节点;但如果某条动态标题换行成两行,右侧内容变高,28 的竖线就会出现"短线悬空"的缝隙。

更健壮的做法是让竖线layoutWeight(1)跟随右侧内容高度——把竖线放进一个与右侧内容等高的容器里,用弹性高度而非固定 28。这需要把左列和右列包进一个等高 Row(alignItems(VerticalAlign.Top)已保证顶部对齐),竖线用layoutWeight(1)撑满剩余高度。demo 用固定 28 是因为数据都精心控制在单行,真实项目若内容高度不定,务必改成自适应,否则时间轴会"断线"。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/4 4:57:39

72GB大显存本地AI部署:从硬件配置到性能验证全攻略

这次我们来看一个名为“PRO5000 72G”的项目。从标题来看&#xff0c;这很可能是一个与高性能计算、大显存显卡或AI模型本地部署相关的技术突破或工具整合。核心的关注点在于“72G”这个显存规格&#xff0c;这通常意味着它瞄准了需要处理超大规模模型或数据的专业场景&#xf…

作者头像 李华
网站建设 2026/9/4 4:56:56

基于Vue的校园闲置交易平台H5前端架构与核心功能实现

简介&#xff1a;本资源是一款面向高校师生的校园闲置物品交易平台H5用户端完整前端源码&#xff0c;基于Vue 2/3主流技术栈开发&#xff0c;聚焦移动端二手交易场景&#xff0c;解决校内物品流转低效、信息分散、平台缺失等实际问题&#xff0c;适合前端初学者进阶实践与课程设…

作者头像 李华
网站建设 2026/9/4 4:56:14

Java Web 业务流程 注释

RestController RequestMapping("/api") public class HealthController {GetMapping("/health")public Map<String, String> health() {return Map.of("status", "ok");} }RestController、RequestMapping、GetMapping都是注解…

作者头像 李华
网站建设 2026/9/4 4:55:14

工业设备故障诊断:电流表显示放电但风泵不工作的排查指南

这次我们来看一个在工业设备维护中非常实用的故障分析案例&#xff1a;充放电电流表显示放电&#xff0c;但风泵打风异常。这个问题的核心不是复杂的理论推导&#xff0c;而是如何通过现场仪表读数、设备状态和系统逻辑&#xff0c;快速定位故障点并给出可执行的解决方案。对于…

作者头像 李华
网站建设 2026/9/4 4:54:00

2026论文降重降AI工具:一次搞定双检

知网、维普相继升级AI生成内容识别功能后&#xff0c;论文查重和AI检测成了毕业季的两道关卡。不少学生重复率达标了&#xff0c;却栽在"疑似AI生成"这一关上。本文盘点几款兼具降重与降AI能力的工具&#xff0c;AIBiye、AICheck、Passbug是当前讨论较多的三款&#…

作者头像 李华
网站建设 2026/9/4 4:50:41

有哪些靠谱的国内学 Python 网站?2026 年实测,这 5 个平台数据说话

国内, 学习渠道繁杂, 免费教程呈碎片化, 付费课程质量高低不一。依据2026年编程学习行业调研数据, 近67%自学的用户, 曾遭遇课程老旧这一问题, 还碰到缺少实操情况, 且答疑无保障。大量初学者花费1至3个月时间, 可依旧没办法独立完成代码项目。结合千万学员真实使用数据、课程更…

作者头像 李华