鸿蒙原生实战:用 ArkUI 搭 ACG 社区首页 —— 沉浸式头图与帖子信息流
鸿蒙原生实战:用 ArkUI 搭 ACG 社区首页 —— 沉浸式头图与帖子信息流
应用背景:12 ACG 社区(acg-community)是鸿蒙原生校园工具系列的第 12 个应用,定位"校园二次元爱好者社区",采用玫红主题
#DB2777、强调色#F472B6。本文逐行拆解HomeTab.ets(共 312 行),聚焦 沉浸式全屏安全区适配、HeroCard 渐变头图、WeekChart 发帖趋势条形图、PostList 帖子信息流 四大核心模块,并穿插SearchBar、QuickAction、RankList等辅助组件。
与第 11 号"校园快递"不同,12 号应用是内容社区:它的首页核心不是"任务提醒",而是"内容消费"。用户打开首页,第一眼要看到的是"社区在聊什么、有什么新帖、哪个板块最火"。因此首页的信息密度与情感色彩都更浓——玫红渐变头图奠定"二次元"的年轻气质,帖子列表用点赞数与星级制造"热度感",板块排行则引导用户进入细分话题。理解首页,就是理解"内容社区如何用一屏留住用户"。
一、页面骨架与沉浸式安全区
首页根结构依旧是本系列统一的 Header + Scroll 二分式,Scroll 用 layoutWeight(1) 撑满剩余空间:
build() {
Column() {
this.Header()
Scroll() {
Column({ space: 14 }) {
this.HeroCard()
this.SearchBar()
this.QuickAction()
this.WeekChart()
this.SectionTitle('热门帖子', '全部 >')
this.PostList()
this.SectionTitle('板块排行', '详情 >')
this.RankList()
}
.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)
}
@StorageProp('safeTop') / @StorageProp('safeBottom') 从 AppStorage 读取避让区,值由 EntryAbility.onWindowStageCreate() 通过 setWindowLayoutFullScreen(true) + getWindowAvoidArea() 计算写入。底部 padding 的 D.pad + this.safeBottom + 20 表达式与前作一致,保证最后一张卡片不被底部导航条遮挡。
值得一提的是,本应用的沉浸式实现里,setWindowSystemBarProperties 把状态栏文字色设成了深色 #1C2333(与 C.text 一致),而首页 Header 背景是白卡、HeroCard 是玫红渐变——这意味着状态栏文字在白色标题栏上呈深色、在渐变卡上方则因 safeTop 的补偿而让出空间。深浅两种底色都能兼容,靠的正是"状态栏文字色统一深色 + 各页面自行用 safeTop 避让"这条约定。

二、数据模型:五类结构
首页把零散字段收敛为 5 个接口,其中 Post 最能体现"内容社区"的数据特点:
interface Post {
id: number; emoji: string; title: string; author: string;
time: string; tag: string; rating: number; progress: number; likes: string;
}
interface StatItem { value: string; label: string; color: string; }
interface WeekBar { day: string; value: number; }
interface RankItem {
id: number; rank: number; name: string; value: string; emoji: string; percent: number;
}
interface QuickItem { emoji: string; label: string; }
对比 11 号快递的 Express,Post 把 code(单号)换成了 likes(点赞数)、把 company 换成了 author(作者)——这就是"物流"与"社区"的数据差异:物流关心"谁送、单号多少",社区关心"谁写、多少人赞"。rating(星级)在两个应用里都保留了,因为"评分"是通用的质量信号。tag 在这里是"板块标签"(追番/游戏/创作),而非快递的"状态标签"。
RankItem 的 value 从"8 件"变成了"452帖"——社区排行的单位是"帖子数"。这些细微的字段语义差异,正是"同一套骨架、不同业务数据"的生动例证。当你用统一模板开发系列应用时,接口的字段命名要随业务调整,但"先抽象接口、再渲染 UI"的范式始终不变。
三、Header 与 HeroCard 渐变头图
Header 与 11 号一字不差(标题「ACG社区」),重点在 HeroCard——它用 linearGradient 铺出玫红 → 浅粉的渐变,承载社区名、三栏统计与双按钮:
// HeroCard — 渐变头图
@Builder HeroCard() {
Column({ space: 16 }) {
Row() {
Text('次元壁 · ACG爱好者的家').fontSize(13).fontColor('#FFFFFF').opacity(0.85)
Blank()
Text('今日热榜').fontSize(11).fontColor('#FFFFFF').opacity(0.7)
}.width('100%')
Row({ space: 12 }) {
ForEach(this.stats, (s: StatItem) => {
Column({ space: 4 }) {
Text(s.value).fontSize(22).fontWeight(FontWeight.Bold).fontColor('#FFFFFF')
Text(s.label).fontSize(11).fontColor('#FFFFFF').opacity(0.8)
}.layoutWeight(1).alignItems(HorizontalAlign.Center)
}, (s: StatItem) => s.label)
}.width('100%')
Row({ space: 12 }) {
Button('发布帖子')
.fontSize(14).fontColor(C.primary).backgroundColor('#FFFFFF')
.borderRadius(D.rSm).height(40).layoutWeight(1)
.onClick(() => { promptAction.showToast({ message: '发布帖子' }); })
Button('加入圈子')
.fontSize(14).fontColor('#FFFFFF').backgroundColor('#33FFFFFF')
.borderRadius(D.rSm).height(40).layoutWeight(1)
.onClick(() => { promptAction.showToast({ message: '加入圈子' }); })
}.width('100%')
}
.width('100%').padding(20).borderRadius(D.rLg)
.linearGradient({ angle: 135, colors: [[C.primary, 0.0], [C.accent, 1.0]] })
}
渐变从 #DB2777(玫红)过渡到 #F472B6(浅粉),135° 角让高光落在右上。三栏统计是"今日新帖 128 / 活跃用户 2.4k / 本周话题 892"——注意 2.4k 这种缩写数值,社区类应用常用 k 表示千,避免数字过长挤压布局。双按钮"发布帖子"(白底玫红字,主操作)与"加入圈子"(半透明白底白字,次操作),主次分明的视觉层级与 11 号一脉相承。
一个值得点出的细节:社区的头图 Slogan「次元壁 · ACG爱好者的家」比快递的「菜鸟驿站 · 梅园站点」更具情感色彩。头图文案从"地理坐标"变成"情感归属",这是内容社区区别于工具类应用的重要信号——社区卖的是"认同感",头图就是这种认同感的第一块招牌。

四、SearchBar 与 QuickAction 快捷入口
SearchBar 结构与前作一致,但占位文案变成了「搜索帖子、话题、用户」——三对象精确覆盖社区的搜索场景:
// SearchBar — 搜索
@Builder SearchBar() {
Row({ space: 10 }) {
Text('🔍').fontSize(18)
TextInput({ placeholder: '搜索帖子、话题、用户', text: this.searchText })
.backgroundColor('transparent').borderRadius(D.rSm).height(40)
.layoutWeight(1).placeholderColor(C.textDim).placeholderFont({ size: 14 })
.onChange((val: string) => { this.searchText = val; })
}
.width('100%').padding({ left: 14, right: 14 })
.backgroundColor(C.card).borderRadius(D.rMd)
.border({ width: 1, color: C.stroke })
}
QuickAction 四宫格从"寄件/取件/代取/查件"换成"✏️发帖 / 🔥热门 / 🏆排行 / 💬消息":
// QuickAction — 快捷入口
@Builder QuickAction() {
Row({ space: 10 }) {
ForEach(this.quickActions, (item: QuickItem) => {
Column({ space: 6 }) {
Text(item.emoji).fontSize(26)
Text(item.label).fontSize(12).fontColor(C.textSub)
}
.layoutWeight(1).padding({ top: 14, bottom: 14 })
.backgroundColor(C.card).borderRadius(D.rMd)
.border({ width: 1, color: C.stroke })
.onClick(() => { promptAction.showToast({ message: item.label }); })
}, (item: QuickItem) => item.label)
}.width('100%')
}
四个入口里"发帖"是内容生产的起点、"热门/排行"是内容发现、"消息"是社交互动——恰好构成社区用户动线的完整闭环(生产 → 发现 → 互动)。这个四宫格虽然布局简单,但每一项都对应社区的顶层功能,是"入口即导航"的典型设计。
五、WeekChart 本周发帖趋势
条形图与前作共享同一套数学公式,但数据语义变成了"发帖量":
// WeekChart — 发帖趋势
@Builder WeekChart() {
Column({ space: 12 }) {
Row() {
Text('本周发帖趋势').fontSize(15).fontWeight(FontWeight.Bold).fontColor(C.text)
Blank()
Text('峰值 95 帖').fontSize(12).fontColor(C.textDim)
}.width('100%')
Row({ space: 6 }) {
ForEach(this.weekBars, (w: WeekBar) => {
Column({ space: 6 }) {
Column() {
Column()
.width('100%')
.height(Math.floor(w.value / this.maxBar * 80))
.backgroundColor(C.primary).borderRadius(4)
}.width('100%').height(80)
Text(w.day).fontSize(11).fontColor(C.textDim)
}
.layoutWeight(1).alignItems(HorizontalAlign.Center)
}, (w: WeekBar) => w.day)
}.width('100%')
}
.width('100%').padding(14)
.backgroundColor(C.card).borderRadius(D.rLg)
.border({ width: 1, color: C.stroke })
}
公式 Math.floor(w.value / this.maxBar * 80) 与前作一字不差——这正是"系列化 demo"的价值:一处成熟的图表写法被跨应用复用,读者只需换 weekBars 数据和标题。本页数据 [65, 88, 52, 95, 78, 90, 72] 是一周发帖量的模拟分布,maxBar = 95 对应"峰值 95 帖"。
社区语境下,这组"发帖趋势"其实承载了"社区活跃度"的信号——用户看到周六峰值、周日回落,能直观感知社区的活跃节奏。把抽象的发帖量画成柱子,比摆一个"本周共 540 帖"的数字更有说服力,这是数据可视化的核心价值:让趋势可见。

六、PostList 帖子信息流(核心)
这是首页的灵魂。每条帖子卡片包含 emoji、标题、板块标签、作者·时间、星级、点赞数与进度条:
// PostList — 帖子列表
@Builder PostList() {
Column({ space: 10 }) {
ForEach(this.posts, (p: Post) => {
Column({ space: 10 }) {
Row({ space: 12 }) {
Row() { Text(p.emoji).fontSize(22) }
.width(46).height(46)
.backgroundColor(C.primarySoft).borderRadius(D.rSm)
.justifyContent(FlexAlign.Center)
Column({ space: 4 }) {
Row({ space: 8 }) {
Text(p.title).fontSize(14).fontWeight(FontWeight.Medium).fontColor(C.text)
Text(p.tag).fontSize(10).fontColor(C.primary)
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
.backgroundColor(C.primarySoft).borderRadius(4)
}.width('100%')
Text(p.author + ' · ' + p.time).fontSize(11).fontColor(C.textDim)
Row({ space: 2 }) {
ForEach([1, 2, 3, 4, 5], (n: number) => {
Text(n <= p.rating ? '★' : '☆').fontSize(12).fontColor(n <= p.rating ? C.warn : C.textDim)
}, (n: number) => n.toString())
}.width('100%')
}
.alignItems(HorizontalAlign.Start).layoutWeight(1)
Column({ space: 4 }) {
Text('❤ ' + p.likes).fontSize(13).fontColor(C.danger).fontWeight(FontWeight.Bold)
Text(p.progress === 100 ? '精华' : '热门').fontSize(10).fontColor(C.textDim)
}
.alignItems(HorizontalAlign.End)
}
.width('100%')
Progress({ value: p.progress, total: 100 }).color(C.primary).width('100%')
}
.width('100%').padding(14)
.backgroundColor(C.card).borderRadius(D.rMd)
.border({ width: 1, color: C.stroke })
.onClick(() => { promptAction.showToast({ message: p.title }); })
}, (p: Post) => p.id.toString())
}.width('100%')
}
与 11 号快递的 ExpressList 相比,PostList 有三处关键差异,恰好体现"社区"与"物流"的业务分野:
其一,板块标签用主色而非状态色。快递的状态标签是 tag === '待取件' ? C.warn : C.ok(警示/成功二态);社区的板块标签则是固定的 fontColor(C.primary) + backgroundColor(C.primarySoft)(玫红 + 浅粉底)。原因很直白:板块标签是"分类"(追番/游戏/创作),没有好坏之分,不该用黄绿去暗示状态,用品牌色统一标注即可。这提醒我们——颜色语义要匹配字段语义,分类用品牌色、状态用警示色、成功用绿色,别混用。
其二,点赞数用 ❤ + likes 并以 C.danger(红)高亮。这是社区特有的"热度信号":红心 + 数字是全网通用的"赞"符号,用户扫一眼就能感知帖子热度。而 p.progress === 100 ? '精华' : '热门' 把"进度条拉满"映射成"精华帖"标签,进度条在这里不再是"运输进度",而是"内容质量分/热度分"的可视化——同一组件,语义因业务而彻底改变。
其三,作者信息 p.author + ' · ' + p.time 用点号拼接。社区要突出"谁在何时写了什么",作者名与时间并排呈现,是内容平台的标配信息架构。快递则只需"时间 + 状态",不需要作者维度。
这三点差异浓缩成一个结论:UI 骨架可以复用,但字段语义、颜色语义、拼接方式必须随业务重写。系列化开发的精髓,正是在"复用"与"重写"之间找到平衡。
七、RankList 板块排行
排行榜把社区各板块的帖子量做了排序展示,引导用户进入热门话题:
// RankList — 板块排行
@Builder RankList() {
Column({ space: 10 }) {
ForEach(this.ranks, (r: RankItem) => {
Row({ space: 12 }) {
Text(r.rank.toString()).fontSize(16).fontWeight(FontWeight.Bold)
.fontColor(r.rank === 1 ? C.warn : C.textDim).width(24)
Text(r.emoji).fontSize(20)
Text(r.name).fontSize(14).fontColor(C.text).layoutWeight(1)
Text(r.value).fontSize(14).fontColor(C.primary).fontWeight(FontWeight.Medium)
}
.width('100%').padding({ left: 14, right: 14, top: 10, bottom: 10 })
.backgroundColor(C.card).borderRadius(D.rSm)
.border({ width: 1, color: C.stroke })
.onClick(() => { promptAction.showToast({ message: r.name }); })
}, (r: RankItem) => r.id.toString())
}.width('100%')
}
第一名「追番讨论 452帖」用 C.warn 橙色高亮序号,其余淡化。数据"追番讨论 452 / 游戏攻略 318 / 同人创作 276"三个板块,与 HeroCard 的"今日新帖 128"、WeekChart 的周趋势形成数据闭环——首页的信息架构是"总览(头图)→ 趋势(图表)→ 内容(帖子)→ 分类(排行)",层层递进,符合用户"先看大盘、再看细节、最后分流"的浏览习惯。

八、与快递首页的"同源异构"
把 12 号 ACG 社区与 11 号校园快递的首页并排看,是理解"系列化开发"的最佳样本:两者的 Header、SearchBar、QuickAction、WeekChart、SectionTitle、RankList 几乎逐字相同(同源),但 HeroCard 的文案、PostList/ExpressList 的字段语义与颜色语义完全重写(异构)。
这种"同源异构"正是本系列的价值主张:骨架沉淀为模板,业务重写为差异。11 号教你"物流信息流怎么写",12 号教你"内容信息流怎么写",两者共享的骨架让你快速上手,两者的差异让你理解"业务如何塑造 UI"。学会抽象出"头图 + 图表 + 列表 + 排行"这套社区信息流模板,你就能用半天时间搭出任意一个内容社区 App 的首页。
九、本章小结与工程经验
| 模块 | 核心技术 | 学习价值 |
|---|---|---|
| Header | @StorageProp safeTop | 沉浸式安全区适配 |
| HeroCard | 玫红渐变 + 双按钮 | 内容社区头图 |
| QuickAction | 四宫格导航 | 生产/发现/互动闭环 |
| WeekChart | Math.floor 映射高度 | 发帖趋势可视化 |
| PostList | ❤ 点赞 + 板块标签 + 精华态 | 内容信息流标准写法 |
| RankList | 条件着色排名 | 板块排行引导 |
首页最值得迁移的是"内容信息流"范式:一张情感化渐变头图(Slogan + 三栏统计 + 双按钮)奠定社区气质,一张发帖趋势图让活跃度可见,一条帖子列表承载内容消费(标题 + 板块标签 + 作者时间 + 星级 + 点赞 + 热度进度),一张板块排行引导用户分流。四块拼起来,就是内容社区首页的完整骨架。
其次要记住颜色语义匹配字段语义这条铁律:分类标签用品牌色(C.primary)、状态标签用警示/成功色(C.warn/C.ok)、点赞等情感信号用红色(C.danger)。颜色不是随意取的装饰,它是"无声的文案",用错了会让用户误读信息。

十、帖子数据与交互的深层解读
首页四条帖子数据,本身就是 ACG 社区内容生态的微型样本:🌸 2025夏番追番清单分享(追番)、🎮 原神5.0版本攻略合集(游戏)、🎨 同人画作每周精选Vol.42(创作)、📚 轻小说推荐:异世界题材(轻小说)——四条帖子恰好覆盖 ACG 四大内容板块,与 HeroCard 的"本周话题 892"、RankList 的"追番讨论/游戏攻略/同人创作"形成完整闭环。这种"数据互相呼应"的编排,让首页显得真实可信,而不是一堆随机 mock。
星级评分用 ForEach([1, 2, 3, 4, 5]) 生成五颗星,n <= p.rating ? '★' : '☆' 决定实心或空心,fontColor(n <= p.rating ? C.warn : C.textDim) 让实心星呈琥珀色、空心星呈灰色。这是"固定数组造循环"的经典用法,五颗星不重复、key 用 n.toString() 稳定标识。若未来要支持半星,可把 rating 从整数改为 0.5 步进的浮点,再把判断改成"满星/半星/空星"三分支——但 demo 用整数星级已足够。
❤ 328 这类点赞数用红色 C.danger + 心形 emoji,是全网通用的"热度"视觉符号。用户扫一眼右侧红色数字,就能感知热度排序——506 赞的同人画作显然比 128 赞的轻小说更热。点赞数、星级、精华/热门 标签三者共同构成帖子的"质量信号",引导用户优先点击高热度内容。
可访问性上,帖子卡片做到了"颜色 + 文字双通道":板块标签除了玫红,还有"追番/游戏"文字;精华/热门除了进度条长度,还有文字标签。色弱用户不靠颜色也能读懂信息——这是"靠颜色区分信息时,务必配文字兜底"的无障碍原则。
性能上,ForEach(this.posts, ...) 的 key 用 p.id.toString(),id 唯一稳定,增删帖子时节点能正确复用、不闪烁。未来做"下拉刷新 + 上拉加载",只需更新 @State posts 数组,列表自动重绘,无需手写刷新。
十一、沉浸式状态栏与模板化总结
首页沉浸式实现里有个容易被忽略的关键点:setWindowSystemBarProperties 把状态栏文字色设成深色 #1C2333,而首页从上到下是"白 Header → 玫红 HeroCard → 白卡片流"的交替底色。状态栏文字位于 Header 白色区域上方(Header 的 safeTop padding 把标题推到状态栏下方),深色文字在白色背景上清晰可见;HeroCard 的玫红渐变则因 safeTop 补偿而与状态栏文字互不重叠。这种"状态栏文字统一深色 + 顶部用白色 Header 承载状态栏区域"的做法,是深色文字状态栏的标准方案;反之若顶部是深色头图,状态栏文字就该设白。理解"状态栏文字色跟随顶部底色"这条规则,沉浸式适配就完成了一半。
配色上,#DB2777(玫红)+ #F472B6(浅粉)的渐变,是典型的"二次元少女感"配色,配合 emoji(🌸🎮🎨)营造年轻化氛围。相比 11 号快递的橙色 #EA580C(工具感、效率感),玫红更"情感、浪漫、二次元"。主题色选择本身就是产品定位的表达——工具应用偏冷色/中性色,内容社区偏暖色/品牌色。这也是本系列"每应用一个主题色"的设计用意:让读者通过颜色差异,直观感受不同应用的调性。
最后把首页抽象成模板:内容社区首页的通用骨架是沉浸式 Header + 情感化 HeroCard(Slogan + 三栏统计 + 双按钮)+ 搜索栏 + 四宫格入口 + 趋势图表 + 内容信息流 + 分类排行。这套骨架的每块"积木"都值得抽进组件库(HeroCard、SearchBar、QuickActionGrid、WeekBarChart、CardList、RankList),换主题色 C.primary、换数据源、换文案,就能拼出任意一个内容社区首页。系列化 demo 的终极价值,正在于把"一次性页面"沉淀成"可复用模板"。把这一页吃透,你就同时掌握了"内容社区首页"与"沉浸式安全区适配"两个高频模板,足以支撑起一个完整的信息流首页。
更多推荐




所有评论(0)