
鸿蒙原生应用 ArkUI 实战条件渲染驱动消息中心页四分类真切换与未读红点App 18「校园社交匹配」消息 TabFunc2Tab是本系列交互最完整的消息中心——4 栏彩色统计 4 分类 Tab全部/未读/好友申请/系统通知真实切换不同列表if/else条件渲染 通知卡片 聊天卡片未读红点角标/在线状态。本篇基于18-social-match/entry/src/main/ets/pages/Func2Tab.ets约 261 行逐段拆解附 4 张实机截图。一、整体结构4 分类真切换本页最大亮点Func2Tab 的骨架与系列记录页不同——不是Scroll 装全部内容而是按 currentTab 切换不同列表build() { Column() { this.Header() this.StatOverview() this.TabBar() if (this.currentTab 0) { this.AllList() } else if (this.currentTab 1) { this.UnreadList() } else if (this.currentTab 2) { this.FriendReqList() } else { this.SystemList() } } .width(100%).height(100%).backgroundColor(C.bg) }if/else if/else四分支条件渲染——State currentTab决定显示哪个列表currentTab 0→ AllList通知区 聊天区currentTab 1→ UnreadList未读消息筛选currentTab 2→ FriendReqList好友申请 通过/忽略else3→ SystemList系统通知这是本系列第一个按 Tab 完全切换页面内容的消息中心App 13/14/15/16/17 的 Tab 只是切换高亮或过滤同一个列表。真切换比过滤更彻底——不同分类可以有不同的卡片形态好友申请卡有通过/忽略按钮聊天卡有未读红点。if/else if/else在 ArkUI build 里是合法的条件渲染与ForEach一样是声明式渲染的一部分。项目源码开源https://gitee.com/codenestFlow/HarmonyOSHub二、Header StatOverview4 栏彩色统计Header 单行标题消息中心。StatOverview 是 4 栏统计未读消息 3/好友申请 5/本周互动 12/系统通知 2每栏带颜色Builder StatOverview() { Row() { ForEach(this.stats, (s: StatItem) { Column({ space: 4 }) { Text(s.value).fontSize(18).fontWeight(FontWeight.Bold).fontColor(s.color) Text(s.label).fontSize(10).fontColor(C.textDim) }.layoutWeight(1) }, (s: StatItem) s.label) } .width(100%).padding({ top: 14, bottom: 14 }) .backgroundColor(C.card).borderRadius(D.rMd).border({ width: 1, color: C.stroke }) .margin({ left: D.pad, right: D.pad, top: 12 }) }4 色映射private stats: StatItem[] [ { value: 3, label: 未读消息, color: C.primary }, { value: 5, label: 好友申请, color: C.warn }, { value: 12, label: 本周互动, color: C.ok }, { value: 2, label: 系统通知, color: C.accent } ];未读消息 3 → 粉红主色好友申请 5 → 橙警示本周互动 12 → 绿成功系统通知 2 → 浅粉红accent好友申请 5用橙色警示——申请是待处理需要行动橙色强化。本周互动 12绿色——互动是好状态。统计色 语义色。数据一致性未读消息 3 4 个聊天里 213 个未读小明 2 小华 1✓系统通知 2 notices 里 2 个未读/待处理——未读消息 3与聊天数据严格对应好数据。三、TabBar4 段下划线TabBar 是 4 个分类全部/未读/好友申请/系统通知的下划线指示器Builder TabBar() { Row() { ForEach(this.tabs, (t: string, idx: number) { Column({ space: 4 }) { Text(t).fontSize(13) .fontColor(this.currentTab idx ? C.primary : C.textDim) .fontWeight(this.currentTab idx ? FontWeight.Bold : FontWeight.Normal) Row() .width(24).height(3).borderRadius(2) .backgroundColor(this.currentTab idx ? C.primary : transparent) }.layoutWeight(1).padding({ top: 12, bottom: 8 }) .onClick(() { this.currentTab idx; }) }, (t: string, idx: number) t idx) } .width(100%).backgroundColor(C.card) .margin({ top: 12 }) .border({ width: 0, color: C.stroke }) .border({ width: { bottom: 1 }, color: C.stroke }) }与 App 13/17 TabBar 的差异下划线 24×3App 13 是 24×2更粗border({ width: 0, ... }).border({ width: { bottom: 1 }, ... })双 border 写法——先全 0 再覆盖底部 1vp底部横线分隔 TabBar 与列表onClick(() { this.currentTab idx; })切换 currentTab →if/else重新渲染列表——Tab 真切换。四、AllList通知区 聊天区AllList全部 Tab是通知区 聊天区两个分区Builder AllList() { Scroll() { Column({ space: 12 }) { this.NoticeSection() this.ChatSection() } .width(100%) .padding({ left: D.pad, right: D.pad, top: 12, bottom: D.pad this.safeBottom 20 }) } .layoutWeight(1).scrollBar(BarState.Off).align(Alignment.Top) }NoticeSection4 条通知ChatSection4 个聊天——通知 聊天是消息中心的两大内容微信的服务通知 聊天。4.1 NoticeCard通知卡片Builder NoticeCard(n: NoticeItem) { Row({ space: 12 }) { Row() { Text(n.icon).fontSize(24) } .width(44).height(44).backgroundColor(C.primarySoft).borderRadius(22).justifyContent(FlexAlign.Center) Column({ space: 4 }) { Row() { Text(n.title).fontSize(14).fontWeight(FontWeight.Bold).fontColor(C.text).layoutWeight(1) Text(n.status).fontSize(10).fontColor(n.statusColor) .padding({ left: 6, right: 6, top: 3, bottom: 3 }) .backgroundColor(C.cardSoft).borderRadius(4) }.width(100%) Text(n.desc).fontSize(12).fontColor(C.textSub).maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis }) Text(n.time).fontSize(10).fontColor(C.textDim) }.alignItems(HorizontalAlign.Start).layoutWeight(1) } .width(100%).padding(12).backgroundColor(C.card).borderRadius(D.rMd).border({ width: 1, color: C.stroke }) .onClick(() { promptAction.showToast({ message: n.title }); }) }4 条通知新的好友申请 待处理 橙、活动提醒 未读 粉红、匹配成功⭐ 已查看 灰、系统升级 已读 灰。状态标签三态status statusColor待处理橙/未读粉红/已查看已读灰——通知状态三色。maxLines(1) Ellipsis 描述截断。4.2 ChatCard聊天卡片未读红点 在线状态Builder ChatCard(c: ChatItem) { Row({ space: 12 }) { Stack({ alignContent: Alignment.TopEnd }) { Row() { Text(c.avatar).fontSize(32) } .width(50).height(50).backgroundColor(C.primarySoft).borderRadius(25).justifyContent(FlexAlign.Center) if (c.unread 0) { Text(c.unread.toString()).fontSize(10).fontColor(#FFFFFF) .backgroundColor(C.danger).borderRadius(10) .padding({ left: 5, right: 5, top: 2, bottom: 2 }).minFontSize(8) } } Column({ space: 4 }) { Row() { Text(c.name).fontSize(15).fontWeight(FontWeight.Bold).fontColor(C.text).layoutWeight(1) Text(c.time).fontSize(10).fontColor(C.textDim) }.width(100%) Row() { Text(c.lastMsg).fontSize(12).fontColor(C.textSub).layoutWeight(1) .maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis }) Text(c.status).fontSize(10).fontColor(c.statusColor) .padding({ left: 6, right: 6, top: 2, bottom: 2 }) .backgroundColor(C.cardSoft).borderRadius(4) }.width(100%) }.alignItems(HorizontalAlign.Start).layoutWeight(1) } .width(100%).padding(12).backgroundColor(C.card).borderRadius(D.rMd).border({ width: 1, color: C.stroke }) .onClick(() { promptAction.showToast({ message: 打开聊天: c.name }); }) }聊天卡 4 要素标准微信风格头像 未读红点Stack TopEndif (c.unread 0) { Text(c.unread.toString()).fontSize(10).fontColor(#FFFFFF) .backgroundColor(C.danger).borderRadius(10) .padding({ left: 5, right: 5, top: 2, bottom: 2 }).minFontSize(8) }if (c.unread 0)有未读才显示红点——小明 2、小华 1 有红点小美/小雪无。红点 红色圆形borderRadius 10 白字数字。.minFontSize(8)数字过大时自动缩小如 99——未读数字自适应。昵称 时间首行左右小明 10:30最后消息 在线状态次行周末一起去打篮球吧 在线绿在线状态三态在线绿 ok/离线灰 textDim/忙碌橙 warn——状态色三态聊天对象 4 人2 在线/1 离线/1 忙碌。Stack({ alignContent: Alignment.TopEnd })未读红点贴在头像右上角区别于首页的在线状态点 BottomEnd——未读红点 TopEnd、在线状态 BottomEnd两种角标位置。五、UnreadList未读筛选UnreadList未读 Tab过滤出有未读的聊天 未读/待处理的通知Builder UnreadList() { Scroll() { Column({ space: 10 }) { ForEach(this.chats, (c: ChatItem) { if (c.unread 0) { this.ChatCard(c) } }, (c: ChatItem) c.id.toString()) ForEach(this.notices, (n: NoticeItem) { if (n.status 待处理 || n.status 未读) { this.NoticeCard(n) } }, (n: NoticeItem) n.id.toString()) } .width(100%) .padding({ left: D.pad, right: D.pad, top: 12, bottom: D.pad this.safeBottom 20 }) } .layoutWeight(1).scrollBar(BarState.Off).align(Alignment.Top) }两种过滤ForEach(this.chats, (c: ChatItem) { if (c.unread 0) { this.ChatCard(c) } }, (c: ChatItem) c.id.toString())聊天c.unread 0小明/小华 2 条通知n.status 待处理 || n.status 未读好友申请 活动提醒 2 条ForEach 内 if 过滤是条件渲染的精简写法——不需要额外filtered()方法直接在 ForEach 回调里if判断。内联过滤适合简单条件一个 if复杂过滤多条件建议抽方法。六、FriendReqList好友申请通过/忽略双操作FriendReqList好友申请 Tab只显示新的好友申请通知每条带通过/忽略按钮Builder FriendReqList() { Scroll() { Column({ space: 10 }) { ForEach(this.notices, (n: NoticeItem) { if (n.title 新的好友申请) { Column({ space: 12 }) { Row({ space: 12 }) { Row() { Text(n.icon).fontSize(28) } .width(50).height(50).backgroundColor(C.primarySoft).borderRadius(25).justifyContent(FlexAlign.Center) Column({ space: 4 }) { Text(n.title).fontSize(14).fontWeight(FontWeight.Bold).fontColor(C.text) Text(n.desc).fontSize(12).fontColor(C.textDim) Text(n.time).fontSize(10).fontColor(C.textDim) }.alignItems(HorizontalAlign.Start).layoutWeight(1) Text(n.status).fontSize(11).fontColor(n.statusColor) .padding({ left: 8, right: 8, top: 4, bottom: 4 }) .backgroundColor(C.cardSoft).borderRadius(4) }.width(100%) Row({ space: 10 }) { Button(忽略).fontSize(13).fontColor(C.textSub).backgroundColor(C.cardSoft) .borderRadius(D.rMd).height(36).layoutWeight(1) .onClick(() { promptAction.showToast({ message: 已忽略 }); }) Button(通过).fontSize(13).fontColor(#FFFFFF).backgroundColor(C.primary) .borderRadius(D.rMd).height(36).layoutWeight(1) .onClick(() { promptAction.showToast({ message: 已通过好友申请 }); }) }.width(100%) } .width(100%).padding(14).backgroundColor(C.card).borderRadius(D.rLg).border({ width: 1, color: C.stroke }) } }, (n: NoticeItem) n.id.toString()) } .width(100%) .padding({ left: D.pad, right: D.pad, top: 12, bottom: D.pad this.safeBottom 20 }) } .layoutWeight(1).scrollBar(BarState.Off).align(Alignment.Top) }新的好友申请过滤n.title 新的好友申请匹配双操作按钮忽略灰底灰字次级— 拒绝申请通过粉红底白字主操作— 接受申请通过/忽略是好友申请的经典双操作微信同款。真实项目应实现真逻辑通过 → 状态变已通过、双方成为好友、聊天页新增会话忽略 → 状态变已忽略。好友申请卡比 NoticeCard 更丰富50×50 大图标 3 行文字 双按钮——同一条数据不同分类不同形态全部 Tab 显示精简版NoticeCard好友申请 Tab 显示操作版FriendReqCard——按场景裁剪卡片。七、SystemList系统通知SystemList系统通知 Tab显示全部通知与 NoticeCard 同款Builder SystemList() { Scroll() { Column({ space: 10 }) { ForEach(this.notices, (n: NoticeItem) { this.NoticeCard(n) }, (n: NoticeItem) n.id.toString()) } .width(100%) .padding({ left: D.pad, right: D.pad, top: 12, bottom: D.pad this.safeBottom 20 }) } .layoutWeight(1).scrollBar(BarState.Off).align(Alignment.Top) }系统通知 Tab 展示全部 4 条通知好友申请/活动提醒/匹配成功/系统升级——系统通知 通知全集。4 个 Tab 的内容矩阵Tab聊天通知卡片形态全部4 条4 条ChatCard NoticeCard未读2 条unread02 条待处理/未读ChatCard NoticeCard好友申请—1 条好友申请FriendReqCard双按钮系统通知—4 条NoticeCard**按 Tab 裁剪内容 按场景定制卡片**是消息中心的完整设计。八、State 与辅助方法的克制Func2Tab 只有1 个 StatecurrentTab当前分类 0-3。chats/notices/stats/tabs全部private不可变。1 个 State 驱动 4 个列表切换——currentTab一变if/else重新渲染对应列表。状态驱动视图切换是消息中心的核心比4 个列表都渲染、隐藏 3 个高效得多——按需渲染优于全部渲染 显隐控制。辅助方法 0——过滤逻辑全部内联在 ForEach 的if里简单条件不需要抽方法。九、消息中心的可复用模板App 18 消息页的模板价值Header 4 栏彩色统计 4 分类下划线 Tab if/else 4 分支 - 全部通知区 聊天区 - 未读过滤未读聊天 未读通知 - 好友申请申请卡通过/忽略双按钮 - 系统通知通知列表核心可复用组件ChatCard头像 未读红点 TopEnd 昵称时间 最后消息 在线状态——任何聊天 AppNoticeCard图标 标题 状态标签 描述 时间——任何通知中心FriendReqCard大图标 双操作按钮——任何好友/申请场景Tab 真切换if/else分支——任何多分类中心这套消息中心模板可复用到聊天 App、社交 App、协作工具钉钉/飞书、邮件客户端——任何通知 聊天 待处理的场景。十、聊天卡片的信息架构App 18 的 ChatCard 是微信式聊天列表的完整范本信息架构值得逐层拆解Builder ChatCard(c: ChatItem) { Row({ space: 12 }) { Stack({ alignContent: Alignment.TopEnd }) { Row() { Text(c.avatar).fontSize(32) } .width(50).height(50).backgroundColor(C.primarySoft).borderRadius(25).justifyContent(FlexAlign.Center) if (c.unread 0) { Text(c.unread.toString()).fontSize(10).fontColor(#FFFFFF) .backgroundColor(C.danger).borderRadius(10) .padding({ left: 5, right: 5, top: 2, bottom: 2 }).minFontSize(8) } } ...一条聊天卡片承载 6 个信息维度从最重要到最次要头像最直观— 识别和谁聊未读红点最紧急— 有新消息昵称第一行主信息— 是谁时间第一行次信息— 什么时候最后消息第二行主信息— 聊了什么在线状态第二行次信息— 现在在吗左头像右内容 上行昵称/时间下行消息/状态是聊天列表的黄金布局微信/QQ/钉钉全用这套——视觉层级第一眼头像大第二眼红点红色最显眼第三眼昵称加粗第四眼最后消息正文第五眼时间/状态小字信息密度控制maxLines(1)最后消息截断不换行Ellipsis省略——单行截断保持列表整齐聊天列表每行高度一致。未读红点是聊天列表的注意力引擎——2 个红点小明 2/小华 1引导用户先处理这 2 个——红点 待办清单处理完红点消失下次再有又出现。真实项目的聊天列表进阶置顶会话重要的人置顶免打扰红点变免打扰图标草稿有输入未发送显示草稿十一、消息状态机的已读/未读流转App 18 消息页的通知有 4 种状态待处理/未读/已查看/已读背后是消息状态机新消息 → 未读status: 未读未读统计 1 → 用户打开 → 已读status: 已读未读统计 -1 好友申请 → 待处理需要行动 → 通过/忽略 → 已处理从好友申请列表移除状态决定展示未读统计StatOverview未读消息 3 所有unread 0的聊天 status: 待处理/未读的通知未读 Tab 只显示未读的过滤好友申请 Tab 只显示待处理的申请通过/忽略后移除已读回执是消息系统的核心——用户打开聊天 → 对方看到已读微信的已读/未读——状态流转驱动 UI。真实项目的消息状态送达状态发送中 → 已送达 → 已读单/双勾通知状态未读 → 已读红点消失申请状态待处理 → 已通过/已忽略消息状态机是消息系统的灵魂——App 18 简化了状态流转未读固定 3 不变真实项目要点开即已读onClick清 unread。十二、消息中心的双列表分区设计App 18 全部 Tab 的通知区 聊天区双分区是消息中心的经典信息架构Builder AllList() { Scroll() { Column({ space: 12 }) { this.NoticeSection() this.ChatSection() } ...通知在上、聊天在下NoticeSection 先渲染、ChatSection 后渲染——**系统消息优先、个人消息次要**的阅读顺序微信的服务通知置顶、聊天在下的同款逻辑。NoticeSection / ChatSection 两个分区标题通知/聊天——分区标题 内容导航用户滚动时知道现在看到哪类。双分区的价值类型分离——系统通知活动/升级与个人聊天小明/小美分开放不混在一起注意力分配——通知区少4 条聊天区多4 条用户先看通知可能有时效性再看聊天扩展性——未来加群聊区、订阅号区只需再加一个 Section双分区 分区标题模板可复用到邮件客户端收件箱/订阅、电商 App订单/优惠券、工作台待办/已办——任何多类型列表场景。十三、总结App 18 消息中心页解析完毕。4 分类真切换if/else 条件渲染 未读红点角标Stack TopEnd minFontSize 好友申请双操作 在线状态三色是四大亮点。按场景裁剪卡片同一数据不同 Tab 不同形态是本页最值得学习的设计。