鸿蒙原生应用实战:用 ArkUI 造表白墙首页 —— @State 点赞态与帖子流

App 41「校园表白墙」首页(HomeTab),主题色 #FF4757 红色(red),4 个 Tab 分别为首页(💌)、发布(✍️)、互动(💬)、我的(👤)。首页采用"Header + 帖子流"两区布局——白色双行 Header("表白墙"22 号加粗 + 💌 图标 + "最新/最热"分段,选中段带 2 高红色底部指示条) + 4 条匿名表白帖子(🎭 40×40 浅红圆形头像 + "匿名同学" + 时间 + 正文 + 底部互动条:🤍/❤️ 点赞可切换(红色实心/灰色空心)+ 数字、💬 评论数、分享)。其中点赞是系列首个可交互状态(点击 🤍 变 ❤️ 且数字 +1),帖子正文来自静态数组。本篇基于 41-confession-wall/entry/src/main/ets/pages/HomeTab.ets(共 105 行)逐段拆解,附 4 张实机截图。

一、整体结构:两区"Header + 帖子流"布局

首页是"品牌头部 + 匿名帖子列表"的两区布局——2 个 @Builder 块:

build() {
  Column() {
    this.Header()
    Scroll() {
      Column({ space: 12 }) {
        this.PostList()
      }
      .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)
}

2 块结构:

  1. Header — 双行结构:标题行("表白墙" + 💌)+ 分段行(最新/最热),白底、固定在顶部非滚动区
  2. PostList — 4 条匿名帖子卡片,是首页的信息主体

与其他 App 相比,本页没有 Banner——因为"表白墙"的核心内容就是帖子本身,不需要额外的宣传位;Header 自带的分段切换(最新/最热)承担了内容筛选职能。

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

二、Header:标题 + 💌 + 分段指示条

Header() {
  Column() {
    Row() {
      Text('表白墙').fontSize(22).fontWeight(FontWeight.Bold).fontColor(C.text).layoutWeight(1)
      Text('💌').fontSize(24)
    }
    .width('100%').padding({ top: this.safeTop + 10, left: D.pad, right: D.pad, bottom: 8 })
    Row({ space: 20 }) {
      ForEach(this.tabs, (t: string, idx: number) => {
        Text(t).fontSize(15)
          .fontColor(this.tab === idx ? C.primary : C.textSub)
          .fontWeight(this.tab === idx ? FontWeight.Bold : FontWeight.Normal)
          .padding({ bottom: 10 })
          .border({ width: { bottom: this.tab === idx ? 2 : 0 }, color: C.primary })
          .onClick(() => { this.tab = idx; })
      }, (t: string) => t)
    }.width('100%').padding({ left: D.pad })
  }.width('100%').backgroundColor(C.card)
}

技术拆解:

  1. 标题 + 图标行:Text('表白墙') 加 layoutWeight(1) 占满左侧,右侧 24 号 💌 emoji 作为品牌点缀——比纯标题多一分氛围感,也暗示产品主题。
  2. 分段指示条:本页分段与 App 40 活动页不同——选中项不是简单变色,而是在文字下方渲染 2 高红色底部边框(.border({ width: { bottom: 2 }, color: C.primary })),未选中则无边框(width: 0)。这是"Tab 指示条"的标准实现,比纯文字高亮更有"选中感",比完整 Tabs 组件更轻量。
  3. 切换状态:@State tab: number 驱动——选中文字红色加粗 + 底部红条,未选中灰色常规。点击更新索引、UI 自动重绘。
  4. 间距:分段之间 space: 20,左侧 padding({ left: D.pad }) 与标题对齐,整体白底收尾。

指示条的意义:底部 2 高指示条是"分段控件 + 内容页"的视觉桥梁——它把"当前在哪个分类"从单纯的文字颜色差异升级为更明确的锚点,用户扫一眼即知当前视图。若加上 animation 过渡,指示条还能平滑滑动到新分段,这是真实产品中常见的升级路径。

41 首页首屏

三、帖子数据模型与内容设计

帖子由 interface Post(4-11 行)定义,数据来自 @State posts: Post[](23-28 行):

id内容摘要时间赞评论已赞
1图书馆三楼靠窗的女生...5分钟前12832否
2致我暗恋了一学期的学长...20分钟前25668是
3食堂打饭的阿姨...1小时前51289否
4同桌,谢谢你帮我占座位...2小时前9621否

内容设计亮点:

  1. 匿名人设:所有帖子统一署名"匿名同学",头像统一 🎭 面具 emoji——匿名是表白墙的灵魂,🎭 面具符号强化"看不见脸、但心意真实"的产品气质。
  2. 场景全覆盖:四帖分别对应图书馆暗恋(第一帖)、暗恋学长(第二帖)、感谢食堂阿姨(第三帖)、感谢同桌(第四帖)——覆盖"表白 / 暗恋 / 感谢 / 校园生活"四类最典型的心声场景,让首页内容观感丰富真实。
  3. 点赞数据有梯度:512/256/128/96 递减——与"最新/最热"分段的语义呼应(最热 = 按点赞排序),暗示数据层具备排序能力。
  4. liked 状态位:第二条 liked: true(❤️ 红色已赞),其余 false(🤍 空心)——首屏即展示两种点赞态,用户无需操作就能看到状态差异。

四、toggleLike:系列首个可交互状态切换

private toggleLike(item: Post): void {
  item.liked = !item.liked;
  item.likes += item.liked ? 1 : -1;
  this.posts = this.posts.slice();
}

这是本系列目前第一个"点击后 UI 真实变化"的交互(之前的 Toast 只是瞬时反馈):

  1. 状态反转:item.liked = !item.liked 切换点赞态。
  2. 数字联动:item.likes += item.liked ? 1 : -1——点赞 +1、取消 -1,赞数与状态严格同步。
  3. slice() 重赋值:this.posts = this.posts.slice() 是响应式刷新的关键——直接修改数组元素属性不会被 @State 监听到,必须整体替换数组引用。这与 App 39 兴趣页的 slice() 技巧同源,是 ArkUI 状态管理的核心知识点。
  4. 为什么 item 是对象:posts 数组的元素是对象,toggleLike(p) 收到的是对象引用——直接改对象的 liked/likes 字段,再靠数组重赋值触发整体刷新,两行代码完成"状态 + 数字 + UI"三联动。

五、PostList:匿名帖子卡片

PostList() {
  Column({ space: 12 }) {
    ForEach(this.posts, (p: Post) => {
      Column({ space: 12 }) {
        Row({ space: 10 }) {
          Row() { Text('🎭').fontSize(20) }
          .width(40).height(40).backgroundColor(C.primarySoft).borderRadius(20).justifyContent(FlexAlign.Center)
          Column({ space: 2 }) {
            Text('匿名同学').fontSize(13).fontWeight(FontWeight.Medium).fontColor(C.text)
            Text(p.time).fontSize(11).fontColor(C.textDim)
          }.alignItems(HorizontalAlign.Start).layoutWeight(1)
        }.width('100%')
        Text(p.content).fontSize(15).fontColor(C.text).lineHeight(23).width('100%')
        Row({ space: 24 }) {
          Row({ space: 5 }) {
            Text(p.liked ? '❤️' : '🤍').fontSize(15)
            Text(p.likes.toString()).fontSize(13).fontColor(p.liked ? C.primary : C.textDim)
          }.onClick(() => { this.toggleLike(p); })
          Row({ space: 5 }) {
            Text('💬').fontSize(15)
            Text(p.comments.toString()).fontSize(13).fontColor(C.textDim)
          }
          Blank()
          Text('分享').fontSize(13).fontColor(C.textDim)
            .onClick(() => { promptAction.showToast({ message: '分享' }); })
        }.width('100%')
      }
      .width('100%').padding(16).backgroundColor(C.card).borderRadius(D.rLg).border({ width: 1, color: C.stroke })
    }, (p: Post) => p.id.toString())
  }.width('100%')
}

帖子卡片细节:

  1. 作者行:40×40 浅红圆底 + 20 号 🎭 + "匿名同学"13 号 Medium + 时间 11 号浅灰,layoutWeight(1) 撑开。
  2. 正文:15 号 lineHeight(23)(1.5 倍行距,长文阅读舒适),全宽——内容即卡片主体,不加摘要省略,表白全文可见(鼓励读完)。
  3. 互动条:Row({ space: 24 }) 内四元素——点赞(p.liked ? '❤️' : '🤍' 三态渲染 + 数字随 liked 变色,整组可点)、评论(💬 + 数字,未挂事件)、Blank() 弹性占位、分享(右侧文字,点击 Toast"分享")。
  4. 点赞的视觉反馈:❤️ 红色 + 数字红色(已赞)/ 🤍 空心 + 数字灰色(未赞)——emoji 与数字双重状态表达,反馈强烈。
  5. 整卡样式:白底圆角 20 + 描边 + 16 内边距,卡片间距 12。

互动条布局价值:点赞/评论/分享 + Blank 弹性右推,是社交内容卡片的通用底部栏结构——Blank() 把操作组左聚、分享右置,任何屏宽下排版稳定。

六、跨页数据自洽:首页帖子的多页呼应

首页不是孤立页面,其内容与发布页、互动页、我的页形成闭环:

  • 发布 → 首页:发布页写的内容(content)保存后应插入 posts 数组成为新的首页帖子——"发布心声"的产物就是首页的帖子流,"最新"分段即按发布时间倒序。
  • 点赞 → 互动页:首页点 ❤️ 后,互动页"收到的赞"应出现一条"匿名同学 赞了你的表白"——互动页 4 条消息中第一条(赞了你的表白 + 图书馆三楼靠窗的女生...)正是首页第一帖的引用,内容摘要一致,证明同一数据源。
  • 评论 → 互动页:首页 💬 评论数对应互动页"评论了你"消息(好浪漫呀,祝你成功!)——互动消息就是首页互动动作的通知化。
  • 我的页"8 我的发布":8 = 首页当前展示 4 条 + 历史发布 4 条(Demo 简化),"收到赞 156" 与首页点赞数存在汇总关系。

41 首页点赞切换

七、实机截图与交互演示

本节结合 4 张实机截图,逐张还原首页的视觉效果与交互过程。

1. 首页首屏:分段 + 帖子流

第一张截图是进入 App 41 后的默认首屏:顶部"表白墙"标题与 💌 图标,下方"最新 / 最热"分段——"最新"为红色加粗并带底部红色指示条;下方是第一条帖子"图书馆三楼靠窗的女生..."(🎭 匿名头像 + 5分钟前 + 正文 + 🤍 128 + 💬 32 + 分享)与第二条"致我暗恋了一学期的学长..."(❤️ 256 红色已赞态,与第一条的空心 🤍 形成对比)。首屏即展示两种点赞状态,视觉上信息密度饱满。

2. 点击"最热":分段指示条切换

第二张截图是点击"最热"分段后的状态:红色指示条与加粗高亮从"最新"切换到"最热",@State tab 由 0 变 1。虽然 Demo 中两段共用同一列表(未按热度重排),但指示条切换的交互反馈完整,为"最热 = 按点赞数降序"的产品语义预留了数据过滤位置。

3. 点击点赞:🤍 变 ❤️ 数字 +1

第三张截图演示了本系列首次真实状态切换:点击第一条帖子的 🤍 后,图标变为红色 ❤️、数字从 128 变为 129——toggleLike 的"状态反转 + 数字联动 + slice 刷新"三联动生效,页面没有跳转、没有 Toast,只有帖子自身的即时变化。这是社交产品"点赞"的最标准交互,也是本页最核心的交互亮点。

4. 滚动到底:四条帖子完整可见

第四张截图是滚动到底部的视图:第三条"食堂打饭的阿姨..."(🤍 512 + 💬 89)与第四条"同桌,谢谢你帮我占座位..."(🤍 96 + 💬 21)完整可见。四条帖子按时间倒序排列(5分钟 → 2小时),"最新"分段的语义与滚动顺序一致,Scroll 滚动流畅,底部安全区内边距保证 Tab 栏可点。

八、扩展思考:点赞功能的工程化演进

首页的点赞已具备"点击切换 + 数字联动"的最小闭环,真实产品还需补齐:

  1. 节流防抖:快速连点会触发多次状态切换,客户端应加 300-500ms 节流,避免与后端请求冲突(每个赞是一次网络请求)。
  2. 乐观更新:点击立即本地改状态(体验流畅),后台异步请求失败时回滚并提示"网络异常,点赞失败"——乐观更新是社交产品点赞的标准实践。
  3. 幂等设计:后端接口需按 (userId, postId) 幂等——重复请求不重复计数,防止弱网重试导致赞数虚高。
  4. 按热度排序:"最热"分段应执行 posts.slice().sort((a, b) => b.likes - a.likes) 重排列表——当前共用列表是简化,真实产品两段是两个排序视图。
  5. 点赞动画:❤️ 出现时加缩放弹跳动画(animateTo + scale),增强反馈的愉悦感——细节决定社交产品的质感。

九、扩展思考:匿名社交的内容治理设计

表白墙是匿名产品,内容治理是核心命题:

  1. 发布审核:匿名发布(App 41 发布页)的内容应过敏感词过滤 + AI 审核——"表白/感谢"是正向情绪,但匿名也容易滋生网暴,需要"先审后发"或"先发后审 + 举报下架"。
  2. 举报机制:帖子卡片应提供"举报"入口(长按或更多菜单),用户举报后进入人工复核队列——匿名不等于免责。
  3. 隐私保护:匿名发布需剥离设备信息、IP 等可追溯数据,从技术层面保证"真的匿名"——App 41 发布页的匿名 Toggle 开关正是这一设计的 UI 表达。
  4. 情绪引导:针对"寻人/表白"类帖子,可在发布成功页引导"如果对方愿意,可以当面表达"——平台承担适度的社交引导责任。
  5. 数据留存:审核日志、举报记录、处理结果需留存备查,符合 UGC 平台的合规要求。

41 首页滚动底部

十、开发者视角:首页的调试与验证技巧

  1. 点赞状态的回归:重点测试"点 3 次"(128→129→128→129)确认增减交替正确、无累计偏差;再测试"两条帖子交替点赞"确认闭包捕获各自的 p 不串扰——本页 toggleLike(p) 参数传递正确,各卡片状态独立。
  2. slice() 刷新的验证:若点赞后页面无变化,第一排查就是 this.posts = this.posts.slice() 是否遗漏——这是 @State 数组响应式的最常见坑,App 39 兴趣页同样踩过。
  3. 分段指示条的调试:.border({ width: { bottom: ... } }) 只设下边框,若误写成全边框会出现"选中项被框起来"的视觉错误;同时注意 border 的颜色必须与 C.primary 一致。
  4. emoji 渲染:❤️/🤍 是文字 emoji 而非图片,在部分字体下 🤍(空心)可能渲染为纯色方块——真机验证时重点检查,必要时用 Text('🤍') 包裹 fontFamily 兜底。
  5. 长文溢出:lineHeight(23) 的正文若超长(如 500 字)会撑高卡片——真实产品应做"展开全文"(maxLines(3) + 折叠态),本 Demo 4 条帖子长度适中,暂无此问题。

十一、系列横向对比:社交内容流的四种形态

把 App 41 首页放入本系列横向对比,可以看到"内容流"这一核心形态的四种演进:

内容流类型代表 App内容单元核心互动匿名性
匿名帖子流App 41 表白墙文字帖子点赞/评论/分享全匿名
技能交换流App 38 技能交换技能卡片交换按钮实名
相册瀑布流App 40 活动照片相册卡片浏览/打招呼实名
用户推荐流App 39 兴趣匹配用户卡片打招呼半实名

App 41 是系列中第一个"纯文字 + 匿名"的内容流——其他 App 的内容都有"物"(技能/相册)或"人"(用户)作为载体,而表白墙的内容就是"一句话"本身,这让首页的视觉重心完全落在文字上:15 号正文 + 1.5 倍行距 + 全宽展示,没有大图、没有复杂布局,用最朴素的方式让"心声"成为主角。

匿名性带来的交互差异:因为匿名,点赞(🤍❤️)成为唯一安全的互动方式——匿名用户不便"打招呼"(App 39)或"交换"(App 38),点赞是表达共鸣的最低成本动作。这也是为什么本页是系列中第一个做真实点赞状态切换的页面——匿名社交的互动几乎全部浓缩在这个小小的 ❤️ 上。互动页的"收到的赞"也因此成为匿名用户最重要的社交反馈来源。

复用价值:如果把本页的"帖子卡"模板(头像行 + 全文 + 互动条)与 App 40 的"瀑布流"(双列)结合,就能快速搭建"匿名照片墙";与 App 38 的"分类胶囊"结合,就能得到"分话题的树洞"。系列 4 个 App 的内容流组件互相组合,可衍生出大量校园社交变体——这正是"组件化内容流"方法论的价值所在。

41 首页最热分段

十二、系列横向对比:分段切换控件的三种风格

本系列 App 已经出现三种分段切换风格,横向对比可看清选型逻辑:

风格代表页实现方式视觉特征
底部指示条App 41 首页border 下边框文字 + 2 高红条
文本变色App 41 互动页/App 40 活动页条件样式仅文字加粗变色
胶囊选中App 38 技能首页背景圆角色块化选中

选型逻辑:

  1. 指示条(App 41 首页)——分段是页面的"主导航"(最新/最热决定整个列表排序),需要最强的选中反馈,底部红条让选中态"有重量",用户一眼定位。
  2. 文本变色(App 40 活动页/App 41 互动页)——分段只是"筛选"(进行中/已结束、收到的赞/评论/回复),列表主体不变或变化不大,变色足够。
  3. 胶囊(App 38)——分类本身是内容的一部分(横滑的 6 个分类),胶囊的色块化让它"可点感"更强。

一句话选型口诀:主导航用指示条、轻筛选用变色、内容分类用胶囊。App 41 同时使用了指示条(首页)与变色(互动页)两种风格,且都服务于各自场景——同一 App 内"按需选型"而非"统一一种",是更成熟的设计判断。若未来首页"最新/最热"需要更强的切换仪式感,可在指示条上加 animation 实现红条平滑滑动,交互质感会再上一个台阶。

十三、开发者视角:首页的常见问题与避坑清单

开发这类"分段 + 内容流"首页,有几个高频问题需要提前规避:

  1. 点赞数字的精度:item.likes += item.liked ? 1 : -1 在快速连点时可能产生竞态——连续两次点击之间 UI 未刷新,读到的是旧值,导致数字漂移。真实产品应基于"上次 UI 状态"计算或加节流,Demo 单次点击场景无碍,但演进时要注意。
  2. 分段切换与列表排序的联动:若实现"最热 = 按 likes 降序",排序必须在 tab 变化时触发——推荐把排序逻辑写在计算属性(get visiblePosts())里,tab 一变自动重算,避免手动调用排序的遗漏。
  3. ForEach key 与点赞状态:点赞修改的是数组元素对象属性,key(p.id)不变,节点可复用,状态更新无副作用——若 key 误用为 likes 会整列表重建,点赞时所有卡片闪烁,这是经典坑,务必保持 id 作 key。
  4. emoji 在不同设备的渲染:🤍(U+1F90D)在部分 Android 字体下可能显示为方框,真机验收需重点检查;❤️ 兼容性较好。若出现方框,可换用内置图标(symbol)替代 emoji。
  5. 分享的合规:分享需调用系统分享面板而非自定义 Toast——当前占位可接受,产品化时注意分享内容需脱敏(匿名帖子分享出去不能带个人信息)。

十四、FAQ 与一句话总结

Q1:为什么点赞是第一个可交互的状态? 之前的 App 点击都是 Toast 瞬时反馈,本页的点赞是"点击后页面真实变化"(空心变实心 + 数字 +1)——因为社交产品的核心就是"互动",点赞是最小粒度的互动单元,用它引入状态管理最自然。

Q2:最新/最热为什么共用同一个列表? Demo 简化——真实产品"最新"按时间倒序、"最热"按点赞降序,是两个排序视图。数据层(posts 数组)已经具备两个排序字段(time/likes),只需在切换时执行不同排序即可。

Q3:帖子为什么统一署名"匿名同学"? 匿名是表白墙的产品灵魂。统一署名避免"有人匿名、有人实名"的混乱,🎭 面具头像进一步强化"看不见脸"的设定——匿名让用户敢于表达,是产品成立的根基。

Q4:点赞数字会变,为什么评论数字不会? 评论需要完整的输入流程(点开评论框 → 输入 → 提交),Demo 未实现评论功能,所以 💬 只是静态展示。产品化后在帖子下挂评论列表 + 输入框即可让评论数也动起来。

Q5:分享为什么是 Toast 而不是真分享? 分享需要调用系统分享面板(@ohos.share 的 ShareController),Demo 用 Toast 占位。挂上系统分享接口后,点击即可拉起系统分享弹窗,当前占位为其预留了入口。

一句话总结:首页以"双行 Header + 匿名帖子流"两区结构完成表白墙的信息展示,而 toggleLike 的"状态反转 + 数字联动 + slice 刷新"则是本页乃至全 App 最具工程价值的一环——它是社交互动的最小实现,也是 @State 响应式编程的教科书案例。配合最新/最热的指示条分段、匿名 🎭 人设与点赞双向态,首页在 105 行代码内完成了"可读、可赞、可切换"的完整体验。

配图

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐