☰
鸿蒙原生应用实战:用 ArkUI 搭投票首页 —— Header 内嵌三 Tab 设计
2026/10/24 13:51:25 网站建设 项目流程

鸿蒙原生应用实战:用 ArkUI 搭投票首页 —— Header 内嵌三 Tab 设计

App 22「校园投票管理」首页(HomeTab),主题色#7C3AED紫色,4 个 Tab 分别为首页(🗳️)、投票(✅)、结果(📊)、我的(👤)。首页采用**"Header 内嵌 3 Tab"独特设计**——固定 Header(标题"校园投票" + 内嵌进行中/即将结束/热门 3 个下划线 Tab)+ 4 张投票卡(56×56 emoji + 标题 + 总票数 + 状态色 + 剩余时间 + 紫色"参与投票"按钮)。本篇基于22-campus-vote/entry/src/main/ets/pages/HomeTab.ets(约 113 行)逐段拆解,附 4 张实机截图。

一、整体结构:Header 双层 + 投票列表

App 22 首页是"Header + Scroll 投票列表"——但 Header 是双层结构(标题层 + Tab 层),投票列表只有 1 个 @Builder:

build() { Column() { this.Header() Scroll() { Column({ space: 14 }) { this.VoteList() } .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— 标题"校园投票" + 3 Tab(进行中/即将结束/热门)
  2. VoteList— 4 张投票卡

与系列其他首页对比:

  • App 21 问卷首页 3 块(Header + Chip + List)——Header 单独 1 行
  • App 22 投票首页 2 块(Header 2 行 + List)——Header 内嵌 Tab

"Header 内嵌 Tab"是 App 22 的特色——首页需要"按时间状态分类"(进行中/即将结束/热门)时,把 Tab 放在 Header 内比放在 List 上方更紧凑。

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

二、Header:双层结构(标题 + 3 Tab)

Header 是标题层 + Tab 层双层结构(系列唯一):

@Builder Header() { Column() { Row() { Text('校园投票') .fontSize(22).fontWeight(FontWeight.Bold).fontColor(C.text) } .width('100%').height(this.safeTop + 52) .padding({ top: this.safeTop, left: D.pad, right: D.pad }) .alignItems(VerticalAlign.Bottom) Row() { ForEach(this.tabs, (t: string, idx: number) => { Text(t) .fontSize(14) .fontColor(this.tab === idx ? C.primary : C.textSub) .fontWeight(this.tab === idx ? FontWeight.Bold : FontWeight.Normal) .layoutWeight(1).textAlign(TextAlign.Center).padding({ bottom: 12 }) .onClick(() => { this.tab = idx; }) }, (t: string) => t) } .width('100%') } .width('100%').backgroundColor(C.card) }

第 1 层(标题):

  • "校园投票" 22sp 加粗深色
  • height(this.safeTop + 52)让出状态栏 + 52vp 标题
  • alignItems(VerticalAlign.Bottom)内容靠底
  • 没用bottom边框(与 Tab 层自然衔接)

第 2 层(Tab):

  • 3 个 Tab(进行中/即将结束/热门)等宽分布
  • layoutWeight(1)3 个均分
  • textAlign(TextAlign.Center)文字居中
  • padding({ bottom: 12 })给 Tab 文字下留 12vp(视觉缓冲)
  • 没有下划线(与 App 14/16/18 的"24×2 下划线"不同)——只靠"文字色 + 字重"区分选中

@State tab: number = 0默认选中"进行中"——onClick(() => { this.tab = idx; })切换——注意 demo 未联动过滤(点击 Tab 只切换文字样式,列表永远是全部 4 张)——"假筛选"——真实项目应加filteredVotes()按status过滤。

2.1 "无下划线 Tab" vs "有下划线 Tab"

App 14/16/18 的 Tab 是"24×2 下划线指示器"(系列标准),App 22 简化成"无下划线"——只靠文字色(紫/灰)+ 字重(加粗/常规)区分选中:

Text(t) .fontSize(14) .fontColor(this.tab === idx ? C.primary : C.textSub) .fontWeight(this.tab === idx ? FontWeight.Bold : FontWeight.Normal) .layoutWeight(1).textAlign(TextAlign.Center).padding({ bottom: 12 })

"无下划线"的优势:更简洁、更轻量——Header 不显笨重。"无下划线"的劣势:用户不知道"哪个是当前选中的"——靠"字重加粗"不够明显(轻度色弱用户难识别)。

"轻量级 Tab 用无下划线、强调 Tab 坐下划线"——本系列两种风格并存。

三、VoteList:4 张投票卡(本页核心)

VoteList 是 4 张投票卡,每张含 56×56 emoji + 标题 + 总票数 + 状态色 + 剩余时间 + 紫色"参与投票"按钮:

@Builder VoteList() { Column({ space: 12 }) { ForEach(this.votes, (v: Vote) => { Column({ space: 10 }) { Row({ space: 12 }) { Row() { Text(v.emoji).fontSize(30) } .width(56).height(56) .backgroundColor(C.primarySoft).borderRadius(D.rMd) .justifyContent(FlexAlign.Center) Column({ space: 4 }) { Text(v.title).fontSize(15).fontWeight(FontWeight.Medium).fontColor(C.text) Text(v.total).fontSize(12).fontColor(C.textDim) } .alignItems(HorizontalAlign.Start).layoutWeight(1) Column({ space: 4 }) { Text(v.status).fontSize(11).fontColor(v.statusColor) Text(v.left).fontSize(11).fontColor(C.warn) } } .width('100%') Button('参与投票') .width('100%').height(38) .fontSize(13).fontColor('#FFFFFF').backgroundColor(C.primary) .borderRadius(D.rSm) .onClick(() => { promptAction.showToast({ message: v.title }); }) } .width('100%') .padding(14) .backgroundColor(C.card).borderRadius(D.rLg) .border({ width: 1, color: C.stroke }) }, (v: Vote) => v.id.toString()) } .width('100%') }

3.1 4 张投票的数据

private votes: Vote[] = [ { id: 1, emoji: '🏆', title: '最佳校园歌手评选', total: '1,856 票', status: '进行中', statusColor: C.ok, left: '剩 3 天' }, { id: 2, emoji: '🎨', title: '校园logo设计方案投票', total: '920 票', status: '即将结束', statusColor: C.warn, left: '剩 6 小时' }, { id: 3, emoji: '⚽', title: '最受欢迎运动评选', total: '2,100 票', status: '进行中', statusColor: C.ok, left: '剩 5 天' }, { id: 4, emoji: '🍜', title: '食堂最佳菜品投票', total: '3,560 票', status: '热门', statusColor: C.danger, left: '剩 2 天' } ];

4 张投票的"3 状态 + 校园主题":

Emoji标题状态总票数剩余
🏆最佳校园歌手评选进行中(绿)1,856 票剩 3 天
🎨校园 logo 方案即将结束(橙)920 票剩 6 小时
⚽最受欢迎运动进行中(绿)2,100 票剩 5 天
🍜食堂最佳菜品热门(红)3,560 票(最高)剩 2 天

3 状态三色映射:

  • 进行中→C.ok绿(活跃/好状态)
  • 即将结束→C.warn橙(警示/快截止)
  • 热门→C.danger红(高热度/紧急)

"剩 6 小时"是即将结束——C.warn橙色(警告用户"快去投")——**"截止倒计时 = 警示色"**的合理映射。

"3,560 票"是热门(食堂投票参与度最高)——"高票数 = 热门"——食堂是校园最关心的话题(民以食为天),1 万学生投票参与形成热门。

3.2 投票卡的"三色"信息

每张投票卡含 3 种信息:

  1. 左侧 56×56 emoji 块(C.primarySoft浅紫底 + 🏆/🎨/⚽/🍜 emoji)
  2. 中间 标题 + 总票数(layoutWeight(1)占主要空间)
  3. 右侧 状态色 + 剩余时间(两行紧凑)

"状态色" + "剩余时间"——两个"紧迫感"信号:

  • 状态色告诉用户"这个投票处于什么状态"(进行中/即将结束/热门)
  • 剩余时间告诉用户"还剩多久"(3 天/6 小时/2 天)

"双信号"让用户做出投票决策——"还剩 6 小时" + 橙色 = 立刻行动。

3.3 紫色"参与投票"按钮

Button('参与投票')100% 宽 38vp 紫底白字按钮——整张卡唯一的"行动"入口(点击跳到 Func1Tab 投票页)。

100% 宽让按钮"贯穿"整张卡——视觉上"参与投票"是这张卡唯一的目的(不是"查看详情"那种次要操作)。

borderRadius(D.rSm)圆角 10(按钮)——比卡片圆角 20 略小——**"按钮圆角 ≤ 卡片圆角"**是设计原则(按钮嵌在卡片内,圆角不能比卡片大)。

四、"假筛选"与 3 Tab 的关系

@State tab: number = 0控制 3 个 Tab 的视觉(哪个选中),但VoteList的ForEach(this.votes, ...)没有联动过滤——4 张卡永远全部展示。

真实项目应实现"按 status 过滤":

private filtered(): Vote[] { if (this.tab === 0) return this.votes; // 全部 if (this.tab === 1) return this.votes.filter(v => v.status === '进行中'); if (this.tab === 2) return this.votes.filter(v => v.status === '即将结束'); return this.votes.filter(v => v.status === '热门'); }

tab一变 →filtered()重算 → ForEach 重渲染。这是本系列"假筛选"共有的简化(App 13/17/20/22 都是)。

"假筛选"在 3 Tab 的设计里影响:用户点"即将结束"看不到其他 Tab 的内容,会以为"现在只有 1 个即将结束的投票"——误导用户——真实项目必须联动过滤。

五、@State 与主题色

HomeTab 只有1 个 @State:tab(当前 Tab 0-2)。其余数据private不可变。

主题色#7C3AED紫色——投票/决策/调查类 App 的常见色(SurveyMonkey 用紫色、Tally Form 用绿色、Typeform 用深色——紫色 = 智能/选择/民主决策的隐喻)。紫色调性契合"投票"的庄严感。

C.primarySoft: '#F1EAFE'浅紫——背景块/胶囊底色。C.accent: '#A78BFA'浅紫——渐变副色。

六、Header 内嵌 Tab 的设计模式

App 22 的"Header 内嵌 Tab"是"双层 Header"——标题层 + Tab 层:

┌──────────────────────────┐ │ 校园投票 (标题) │ <- 标题层 ├──────────────────────────┤ │ 进行中 即将结束 热门 │ <- Tab 层 ├──────────────────────────┤ │ [投票卡 1] │ │ [投票卡 2] │ │ [投票卡 3] │ <- List │ [投票卡 4] │ └──────────────────────────┘

"双层 Header"适用场景:

  • 主页有"按状态/类型分类"的需求(进行中/即将结束/热门)
  • Tab 数量少(2-3 个)
  • Tab 切换不联动复杂过滤

"双层 Header"不适用场景:

  • Tab 数量多(4+)——应单独 Header + 独立 Tab 区
  • Tab 切换联动复杂过滤——应让 List 区域更大

"Header 内嵌 Tab"是 App 22 的设计选择——"哪些场景用 Header 内嵌 Tab"取决于"Tab 与 List 的耦合度"。

七、"剩余时间"的双通道设计

App 22 投票卡显示"剩 3 天/剩 6 小时/剩 5 天/剩 2 天"——"剩余时间"是投票场景的特有维度(对比问卷的"截止日期"、商城的"剩余库存")。

"剩余时间"的 3 种精度:

  • 天(剩 3 天)—— 长期投票,时间粒度"天"
  • 小时(剩 6 小时)—— 紧急投票,时间粒度"小时"
  • 分钟(剩 30 分钟)—— 临截止投票,时间粒度"分钟"

App 22 演示了"天/小时"两种精度——真实项目应根据截止时间动态选择精度。

"剩余时间"是倒计时的"倒序显示"——投票刚发布是"剩 7 天"、过半是"剩 3 天"、临截止是"剩 6 小时"——"剩余 = 截止 - 当前"——真实项目应后端实时计算(避免静态数据不准)。

八、3 Tab 切屏的 4 张实机图

本文 4 张图展示 3 个 Tab 的不同状态:

  • top(默认 Tab="进行中"):4 张投票全部展示
  • mid(点"即将结束"Tab):高亮移到"即将结束",4 张卡同样展示(因为是假筛选)
  • lower(点"热门"Tab):高亮移到"热门"
  • bottom(切回"进行中"):高亮回到"进行中"

虽然 4 张图内容相同(投票卡不变),但 Tab 高亮位置不同——"假筛选"的可视化证据——读者可对比看 4 张图的"进行中/即将结束/热门"字色变化。

九、本页与系列其他首页的对比

App首页定位Tab 位置主列表
13/17 音乐/志愿内容流无 Tab3 项目
14 运动行动型无 Tab5 场地
15 环保知识型无 Tab4 指南
16 商城交易型无 Tab5 商品
18 社交人脉型无 Tab4 同好
19 摄影创作型无 Tab4 作品
20 阅读学习型无 Tab4 书
21 问卷任务型Chip(横滑)5 问卷
22 投票决策型Header 内嵌 Tab4 投票

"投票" = 决策型——用户来"做决定"(选最喜欢的歌手/菜品/运动)——Header 内嵌 Tab 让"按时间状态分类"更醒目。

"任务型 = Chip(横滑)"、"决策型 = Header 内嵌 Tab"——两种"分类"的不同 UI 形态。

十、3 状态投票卡的"优先级"设计

App 22 投票卡的 3 种状态(进行中/即将结束/热门)按"用户行动优先级"排序:

优先级从高到低:

  1. 即将结束(剩 6 小时,橙色警示)——"立刻投"——最高优先级
  2. 热门(高票数,红色突出)——"凑热闹"——次高优先级
  3. 进行中(剩 3-5 天,绿色)——"慢慢投"——低优先级

"优先级 = 视觉强度":

  • 即将结束(剩 6 小时)→C.warn橙——"警示色 = 优先"
  • 热门(3,560 票)→C.danger红——"突出色 = 流量"
  • 进行中(剩 3-5 天)→C.ok绿——"平静色 = 常规"

颜色编码对应"行动优先级"——用户看到橙色会想"快去投"、看到绿色会想"不急"、看到红色会想"很多人在投"——"颜色 = 行动指南"。

"剩 6 小时"特别短——这是合理的紧迫感设计(让用户感觉"错过就没了")——"虚假紧迫感"是投票平台提高参与度的常用心理学(淘宝"剩 1 小时"、滴滴"3 分钟内有司机")。

真实项目的"真实紧迫感":从服务端实时计算截止 - 当前倒计时——"剩 X 小时 Y 分"动态显示——比静态"剩 6 小时"更可信。

十一、Header 内嵌 Tab 与"分类 Tab"的对比

App 22 的"Header 内嵌 Tab"与系列其他 Tab 类型对比:

类型示例位置视觉适用
Header 内嵌 TabApp 22 首页Header 内部文字色+字重分类少 + 强调分类
列表上方下划线 TabApp 13/14/16/18/20 记录列表上方24×2 紫条分类 2-3 个
横滑 ChipApp 14/15/16/19/21 主页列表上方胶囊横滑分类 4-6 个
列表上方胶囊App 18 消息列表上方圆角胶囊分类 4 个
底部 Tab BarApp 11-22 主导航屏幕底部4 个 Tab4 个一级页面

5 种 Tab 类型覆盖所有分类需求——"Tab 类型跟随分类数量和强调程度"——读者做自己产品时,先数分类数量(2-3/4-6/7+)再选 Tab 类型(下划线/横滑/树形)。

"Header 内嵌 Tab"的优势:

  • 节省屏幕空间(不占独立行)
  • 视觉强调(Header 高度 = 分类区)
  • 与标题形成"主标题 + 子分类"层次

"Header 内嵌 Tab"的劣势:

  • Tab 与 List 之间无视觉缓冲
  • Tab 切换不联动过滤时,List 不会变(用户困惑)

App 22 的"假筛选"恰好命中劣势——读者可引以为戒:内嵌 Tab 必须配过滤逻辑,否则用户不知道"切 Tab 没用"。

十二、"学校投票"的业务化设计

App 22 的"校园歌手/校园 logo/校园运动/校园菜品"4 个投票——全部围绕"校园"主题——这是"校园场景"的业务化:

校园主题的具体体现:

  • 🏆校园歌手——文艺/活动(校园活动经典项目)
  • 🎨校园 logo——学校形象(设计/品牌)
  • ⚽校园运动——体育(学校体育节)
  • 🍜校园菜品——生活(食堂主题)

4 个投票覆盖"校园 4 大类":

  • 文艺(歌手)
  • 形象(logo)
  • 体育(运动)
  • 生活(菜品)

"校园投票"是学生群体的特色应用——真实校园场景下,学生有"集体决策"需求(选课/选活动/选菜品/选学生干部)——App 22 是这类应用的最简骨架。

真实项目扩展:

  • 加"投票广场"(全校学生都能发起)
  • 加"实名/匿名投票"切换(敏感话题匿名)
  • 加"投票截止提醒"(push 通知)
  • 加"实时票数更新"(WebSocket 推送)

十三、VoteList 的 Scroll 容器与安全区适配

VoteList 外层包裹了Scroll容器,配合安全区适配:

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

Scroll 容器的 3 个关键属性:

  1. layoutWeight(1)—— 让 Scroll 占满 Header 以下的所有剩余空间——**"Header 固定 + 列表滚动"**的经典布局。

  2. scrollBar(BarState.Off)—— 隐藏滚动条——系列标准做法(所有列表页都隐藏滚动条)——"隐藏滚动条 = 视觉更干净"。

  3. align(Alignment.Top)—— 内容顶部对齐——滚动初始位置在顶部——"打开就在第一项"。

安全区适配:padding({ bottom: D.pad + this.safeBottom + 20 })——底部 padding = 基础 padding + 安全区高度 + 20vp 额外留白——"底部 Tab 栏不遮挡最后一张卡片"——这是全面屏(有底部手势条)的必备适配。

Column({ space: 14 })—— Scroll 内层 Column 的 14vp 间距——"卡片间距 = 呼吸感"——适合"卡片型列表"(信息密度低的列表)。VoteList 内部的Column({ space: 12 })则是 4 张投票卡之间的 12vp 间距。

十四、总结

App 22 首页解析完毕。Header 双层结构(标题 + 3 Tab)+ 4 张投票卡(emoji + 状态色 + 剩余时间 + 参与投票按钮)是核心组件。"Header 内嵌 Tab"是系列独有设计——适合"按状态/类型分类"且 Tab 数量少的场景。**"3 状态优先级 = 颜色强度"**是投票场景的特色设计(橙色警示/红色突出/绿色平静)。Scroll 容器 + 安全区适配确保全面屏下底部不被遮挡——这是所有列表页的通用适配方案。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询