鸿蒙原生应用实战:通知提醒日历页的 7 天周视图实现
鸿蒙原生应用实战:通知提醒日历页的 7 天周视图实现
App 27「校园通知提醒」日历 Tab(Func2Tab),主题色
#D97706琥珀(amber)。日历页是系列第一个"日历周视图"页面——Header(日历视图)+ WeekRow(7 天横排:周二 12 → 周一 18,今日 14 琥珀高亮 + 红色事项角标 +@State selected点击联动) + SectionTitle(今日事项 (4))+ 4 条事项列表(🔔 高数作业截止(23:59)等,点击弹 Toast)。本篇基于27-notice-reminder/entry/src/main/ets/pages/Func2Tab.ets(约 108 行)逐段拆解,附 4 张实机截图。
一、整体结构:双区"周视图 + 事项"布局
日历页是"7 天周视图 + 当日事项"的双区布局——3 个 @Builder 块:
build() {
Column() {
this.Header()
Scroll() {
Column({ space: 14 }) {
this.WeekRow()
this.SectionTitle('今日事项 (' + this.items.length + ')')
this.ItemList()
}
.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)
}
3 块结构:
- Header — "日历视图"(单行)
- WeekRow — 7 天周视图(本页核心)
- SectionTitle('今日事项 (4)') + ItemList — 4 条事项
与 App 27 首页的结构对比:首页是"倒计时卡 + 提醒列表"、日历页是"周视图 + 事项列表"——"同为'概览 + 列表'双区,但概览区形态不同"——"首页管'最近重要'、日历管'按天查看'"——两个 Tab 从不同维度组织同一批提醒数据——"同一数据多视图 = 信息架构的分工"。
SectionTitle('今日事项 (' + this.items.length + ')') 动态计数——"标题里带数量"(用 items.length 而非硬编码 4)——"数量跟着数据走"——加事项标题自动变——"用 length 不用魔法数字"——这是数据驱动 UI 的基本素养。
日历页的 Tab 身份:📅 图标 + "日历"标题——"📅 = 日期视图的通用符号"——与首页 🔔(提醒)、添加 ➕(创建)、我的 👤(个人)并列——"4 个 Tab emoji 覆盖了提醒 App 的完整功能面"。
主题色 #D97706 琥珀在日历页的运用——选中日期格实琥珀底、今天浅琥珀底 + 琥珀边框、今天星期/日期琥珀字——"琥珀在日历里 = 时间标记"——"日历是时间工具,琥珀恰好是'时间感'的颜色"。

二、WeekRow:7 天周视图(本页核心)
WeekRow 是 7 天的横排日期格——星期 + 日期 + 事项角标:
@Builder WeekRow() {
Row({ space: 6 }) {
ForEach(this.days, (d: DayItem, idx: number) => {
Column({ space: 4 }) {
Text(d.day).fontSize(10).fontColor(d.isToday ? C.primary : C.textDim)
Text(d.date).fontSize(16).fontWeight(FontWeight.Bold)
.fontColor(this.selected === idx ? '#FFFFFF' : (d.isToday ? C.primary : C.text))
if (d.count > 0) {
Text(d.count.toString()).fontSize(9).fontColor('#FFFFFF')
.width(14).height(14).textAlign(TextAlign.Center)
.backgroundColor(this.selected === idx ? '#FFFFFF' : C.danger).borderRadius(7)
} else {
Text('').fontSize(9).width(14).height(14)
}
}
.layoutWeight(1)
.padding({ top: 10, bottom: 10 })
.backgroundColor(this.selected === idx ? C.primary : (d.isToday ? C.primarySoft : C.card))
.borderRadius(D.rSm)
.border({ width: 1, color: d.isToday ? C.primary : C.stroke })
.onClick(() => { this.selected = idx; })
}, (d: DayItem, idx: number) => d.date + idx)
}.width('100%')
}
7 天数据(DayItem 4 字段):
- 周二 12(count 0)
- 周三 13(count 2)
- 今天 14(今天,count 4)
- 周五 15(count 1)
- 周六 16(count 0)
- 周日 17(count 2)
- 周一 18(count 3)
日期格结构(4 层):
- 星期(10 号:今天琥珀、其余灰)
- 日期数字(16 号加粗:选中白 / 今天琥珀 / 普通深色)
- 事项角标(count 红点 14×14:有事项显示数字、无事项占位)
- 格底 + 边框(选中琥珀 / 今天浅琥珀 / 普通白)
if (d.count > 0) 条件渲染角标——"有事项才显示红点":
if (d.count > 0) {
Text(d.count.toString()).fontSize(9).fontColor('#FFFFFF')
.width(14).height(14).textAlign(TextAlign.Center)
.backgroundColor(this.selected === idx ? '#FFFFFF' : C.danger).borderRadius(7)
} else {
Text('').fontSize(9).width(14).height(14)
}
角标逻辑:
count > 0→ 显示红色数字角标(红底白字 14×14 正圆)count = 0→ 显示空占位(Text('')同尺寸)
"空占位"的细节价值——"角标区高度恒定,日期格不跳高"——如果不渲染空 Text,有的格子有角标(高)、有的没有(矮),整行高度会参差——"用空元素占位 = 布局稳定"——"隐藏元素也要占位的布局思维"——这是 if/else 成对使用的经典案例:"条件渲染的 else 分支不一定是'不渲染',可能是'渲染占位'"。
为什么角标用红色(C.danger)而不是主题琥珀?——"角标 = 数量警示,红色是最强的警示色"——红底白字数字角标是消息/日历的通用语言(微信未读、系统角标都是红)——"角标语义独立于主题色,遵守平台惯例"——"功能色要按语义用,不硬套主题色"。
选中角标变色:backgroundColor: this.selected === idx ? '#FFFFFF' : C.danger——"选中格的角标变白色"(因为选中格底是琥珀,白角标才看得清)——"角标颜色随格底自适应"——"对比度的动态计算"——细节见功力——"选中琥珀底 + 白色角标 = 高对比可读"。
d.isToday 的"今天"四重标识:
- 星期显示:
isToday ? C.primary : C.textDim——今天格"今天"变琥珀 - 日期显示:
isToday ? C.primary : C.text——今天"14"变琥珀 - 格底:
isToday ? C.primarySoft : C.card——今天浅琥珀底 - 边框:
isToday ? C.primary : C.stroke——今天琥珀边框
"今天"四重琥珀标识——"今天必须一眼可辨"——星期/日期/底色/边框全标识——"重要的日子用所有可用手段强调"——"今天的视觉权重 = 日历的锚点"——用户看日历首先要定位"今天是哪一天"。
选中态(selected)与今天态(isToday)的独立性——selected === idx 控制"琥珀实底白字"(用户点选),isToday 控制"浅琥珀底 + 琥珀边框"(今天标记)——"两个状态独立运作、互不干扰":
- 选中今天 → 琥珀实底 + 白色角标(今天标记被选中盖过)
- 选中其他天 → 该天琥珀实底,今天保持浅琥珀 + 琥珀边框
"今天"和"选中"是日历的两个不同概念——今天(客观日期)、选中(用户操作)——"demo 用两个字段(isToday/selected)分别表达,状态设计清晰"。
onClick(() => { this.selected = idx; }) 点击联动——点任意日期 → selected 更新 → 琥珀选中态移动——"日历的核心交互:点哪天看哪天"。
@State selected: number = 0 默认选中第一个日期格——代码里初始化 0 对应周二 12——"默认选中的位置由业务决定"(真实产品通常默认选中今天,demo 初始化第 1 天)——"初始值设计是状态设计的一部分"。
注意 demo 的选中只改视觉——事项列表(items)未联动 selected(始终显示 4 条"今日事项")——"视觉联动做了、数据联动留白"——真实产品点 13 号应显示 13 号的事项——"demo 的取舍:先做视觉骨架,数据联动是扩展点"。
(d: DayItem, idx: number) => d.date + idx 的 key——日期数字 + 索引(不同月份日期数字会重复,加索引保唯一)——"key 唯一性处理"——"列表 key 要为'真实数据'留余地"。
layoutWeight(1) 七等分——"7 天等宽铺满"——Row 容器自动分配宽度——"日期格自适应屏宽"——不同机型宽度变化,7 格始终均分。
三、ItemList:今日事项(4 条)
ItemList 是 4 条事项的列表——🔔 + 事项名 + 箭头:
@Builder ItemList() {
Column({ space: 10 }) {
ForEach(this.items, (item: string, idx: number) => {
Row({ space: 10 }) {
Text('🔔').fontSize(18)
Text(item).fontSize(14).fontColor(C.text).layoutWeight(1)
Text('>').fontSize(14).fontColor(C.textDim)
}
.width('100%').padding(14).backgroundColor(C.card).borderRadius(D.rMd).border({ width: 1, color: C.stroke })
.onClick(() => { promptAction.showToast({ message: item }); })
}, (item: string, idx: number) => item + idx)
}.width('100%')
}
4 条事项:
- 高数作业截止(23:59)
- 选课系统开放(09:00)
- 社团活动报名截止
- 电费充值提醒
事项行结构:🔔 18 号图标 → 事项名(14 号)→ ">" 箭头(灰)——**"图标 + 文字 + 箭头"**列表标准形态。
事项名带时间后缀:"高数作业截止(23:59)"——"事项名(含时间)"——直接把时间写在名称里——"省去单独时间列,列表更简洁"——**"信息合并进主文字"**的简洁设计——日历场景下"哪天"已由周视图给出,"几点"写进名称即可。
🔔 每行同图标——事项行统一用铃铛(与首页每行不同 emoji 形成对比)——"图标策略 = 数据类型":首页 4 类不同 emoji(区分提醒类型)、日历页同属"事项"用统一铃铛——"图标一致性 = 列表类型的一致"。
点击反馈:promptAction.showToast({ message: item })——点击弹事项名——"占位跳转"。
(item: string, idx: number) => item + idx 的 key——事项内容 + 索引——"内容可能重复(同名事项),加索引保唯一"——"key 的唯一性兜底"。
事项与首页提醒的呼应:
- 高数作业截止(23:59)→ 首页"高数作业截止(今天 23:59)" ✓
- 选课系统开放(09:00)→ 首页"选课系统开放(明天 09:00)" ✓
"日历事项 = 首页提醒的子集"——"两个页面的数据贯通"——用户看到同一事项在两个页面出现,形成"这 App 数据一致"的信任——"demo 的跨页数据一致性"——"多页面展示同一数据的不同切片,是信息架构的常态"。

四、数据模型与状态设计
DayItem 模型(4 字段):
date: string— 日期数字("12"~"18")day: string— 星期("周二"~"周一")count: number— 事项数量(角标)isToday: boolean— 是否今天(视觉标识)
items: string[] 事项数组——纯字符串列表(事项名含时间)——"简单数据类型够用就不建对象"——事项不需要额外字段,字符串直接展示——"数据模型的复杂度 = 业务需求"。
双状态体系:
@State selected: number = 0— 选中日期索引(用户交互)isToday(数据字段)— 今天标记(客观日期)
"状态(selected)+ 数据(isToday)的分离"——selected 是"用户在哪"(可变交互状态)、isToday 是"今天是什么"(静态数据)——"交互状态用 @State、客观属性用数据字段"——"状态与数据的分工清晰"。
角标 count 的数据驱动——每个日期的 count 预存在数组里——"角标数字 = 数据属性,不是计算出来的"——真实产品 count 由当天事项数统计——"demo 预写 count 模拟统计结果"。
与 App 26 编辑页的 current 对比:App 26 用 current 索引驱动预览卡联动(数据联动做了)、App 27 用 selected 索引驱动选中视觉(数据联动留白)——"同一'索引选中'模式,联动深度不同"——"系列的模式复用 + 各自的完成度"。

五、日历的实现细节
5.1 嵌套条件渲染的层级
WeekRow 里有两层条件:
- 外层:
selected === idx决定格底/文字(三元) - 内层:
if (d.count > 0)决定角标显示
"外层样式条件 + 内层内容条件"——"样式的条件用三元、内容的条件用 if"——"不同场景用不同条件写法"——这是 ArkUI 的常见组合。
5.2 三元表达式的嵌套
.fontColor(this.selected === idx ? '#FFFFFF' : (d.isToday ? C.primary : C.text))
三层嵌套三元:选中白 > 今天琥珀 > 普通深色——"优先级的链式判断"——"嵌套三元 = 多级状态的简洁表达"(但可读性略降,是取舍)——"优先级:选中 > 今天 > 普通"。
5.3 格底的三态
.backgroundColor(this.selected === idx ? C.primary : (d.isToday ? C.primarySoft : C.card))
格底三态:琥珀实底(选中)> 浅琥珀(今天)> 白(普通)——"三态底色 = 三个信息层级"——"视觉阶梯:实 > 浅 > 无"。
5.4 space: 6 日期格间距
日期格间距 6(比列表的 12 小)——"紧凑排列的日期格"——7 格横排需要紧凑——"间距密度 = 容器容纳元素的数量"。
5.5 安全区与滚动
@StorageProp 安全区 + Scroll + 底部 padding——系列标准——"日历页也遵循全系列的基础架构"。

六、日历的交互与可用性
6.1 点击区域的完整
日期格 padding({ top: 10, bottom: 10 }) + 整个 Column 可点——"日期格全格可点"——"点击热区 = 视觉元素全覆盖"。
6.2 视觉反馈的即时性
点击 → selected 变 → 格底/文字/角标三处同时刷新——"一处交互、三处反馈"——"选中反馈要'全面而即时'"。
6.3 可读性细节
- 星期 10 号 vs 日期 16 号——"星期辅助、日期主读"
- 角标 9 号数字在 14×14 圆内——"小数字居中"
- 选中白字在琥珀底上——"高对比"
6.4 日历的可扩展性
真实日历产品的扩展:左右切换周、月视图切换、点击日期联动下方列表、长按日期创建事项——"demo 只做了周视图 + 选中,但骨架可扩展"——"从周视图到月视图 = 数据从 7 天扩到 30 天"。
七、周视图的视觉与交互分析
7.1 周视图 vs 月视图
demo 选择 7 天周视图而非 30 天月视图——"周视图的信息密度刚好":
- 周视图:7 天 × 4 层信息(星期/日期/角标/底色)——每格信息充足
- 月视图:30 天挤一屏——每格只能放日期数字,角标/星期放不下
"周视图 = 移动端的实用选择"——手机屏宽有限,7 天一行正好——"日历的展示粒度 = 屏宽 + 信息量"——"周视图是'事项型日历'的标配"(iOS 日历周视图同款)。
7.2 事项角标的"有/无"对比
7 天里 4 天有角标(13/14/15/17/18)、2 天无(12/16)——"有事项的日子用红点数字、空的日子无角标"——"角标的对比度 = 事项的分布感知"——用户扫一眼看到"今天最多(4)",其他日子零零散散——"角标让'哪天忙'一目了然"。
7.3 角标数字与事项量的映射
- 14(今天)→ 4(最多,今日事项 4 条)
- 13/17 → 2
- 18 → 3
- 15 → 1
- 12/16 → 0
"角标数字 = 事项数量"——14 有 4 条(与下方"今日事项 (4)"一致)——"角标与列表的数据一致"——"demo 的数据自洽"——用户看到 4 个角标,下面正好 4 条,形成验证。
7.4 选中态的"移动"视觉
点击不同日期 → 琥珀实底"移动"——"选中态的位移"——用户点 15,琥珀从 14 移到 15——"选中移动 = 日历的操作反馈"——"位置变化是日历最自然的选中表达"(月视图/周视图通用)。
7.5 今天标识的"永不消失"
无论选中谁,今天(14)都保留浅琥珀底 + 琥珀边框——"今天的锚点永在"——用户切来切去永远知道"今天是哪天"——"锚点信息的稳定性 = 日历的可用性"——"次要状态(选中)可变、核心状态(今天)恒显"。
7.6 事项列表的"时间后缀"
4 条事项里 2 条带时间(23:59/09:00)、2 条不带(社团活动报名截止/电费充值提醒)——"有具体时间才写、全天事项不写"——"时间后缀 = 事项的时间属性"——"信息展示的取舍:有时间写时间,没时间不硬写"。
7.7 日历页的"跨页一致性"
今日事项 = 首页提醒的子集——"日历与首页的数据贯通"——"多页面共享同一数据的切片"——这是信息架构的常态:"首页看最急、日历看哪天"。
八、与系列页面的设计对比
8.1 列表样式对比
| App | 列表形态 | 图标策略 |
|---|---|---|
| 13 | emoji + 标题 + 操作 | 每行不同 emoji |
| 24 | 看板卡 | 每行状态图标 |
| 26 | 作品行 + "编辑 >" | 每行不同 emoji |
| 27 日历 | 🔔 + 事项 + ">" | 统一 🔔 |
"27 日历页统一图标"——因为事项同属"提醒"类型——"图标统一 = 列表类型统一"——"图标策略由数据多样性决定"。
8.2 角标与徽标的系列运用
- App 22 投票:选项角标(✓)
- App 24 看板:完成度徽标
- App 27 日历:红色数字角标
"红色数字角标是系列首次"——之前没有用"红底白字圆角标"——"27 引入消息类通用角标"——"角标 = 数量的视觉速记"。
8.3 选中态的系列手法
- App 26 编辑页:
current === idx索引选中 - App 27 日历页:
selected === idx索引选中
"索引选中是系列的标准手法"——"一个 @State + 一个三元,选中态就通了"——"模式复用 = 系列开发效率"。
8.4 条件渲染的系列对比
- App 21 问卷:
if (qType === ...)题型切换 - App 26 创建:
if (generating)大纲卡出现 - App 27 日历:
if (d.count > 0)角标显示
"三个 if 三种用途"——"条件渲染是系列最常用的动态化手段"——"if 让 UI 跟着数据走"。
8.5 日历页的信息架构位置
日历在 4 Tab 中排第 3——首页(看)→ 添加(建)→ 日历(查) → 我的(管)——"查"是提醒的回顾视角——"Tab 顺序 = 用户旅程"——建了提醒 → 回首页看倒计时 → 到日历按天回顾——"日历是'时间维度'的补充视图"。
8.6 周视图的扩展方向
从周视图到完整日历:
- 左右滑动切周
- 月视图切换(数据扩到 30 天)
- 点击日期联动下方列表(selected → items)
- 长按创建事项
"demo 的周视图 = 日历的骨架"——"完整日历的所有交互都能从 selected 扩展"——"demo 展示了最小可用周视图"。
8.7 日历页的代码组织
日历页 4 个 @Builder(Header/WeekRow/SectionTitle/ItemList)——"少而精"——页面逻辑集中在 WeekRow(7 天 × 4 层条件渲染)——"复杂逻辑集中在一个 Builder"。
WeekRow 的复杂度:7 天 ×(星期/日期/角标/底色/边框)五要素 + 两组条件(selected/isToday)+ 一个 if(count)——"日历格是系列最复杂的单一组件"——"复杂度 = 信息层级"——一天的格子承载了 5 层信息。
8.8 日历的数据与视图分离
数据(DayItem 数组)+ 视图(ForEach 渲染)分离——"数据驱动视图":
- 加一天 → 数组加一项 → 视图自动多一格
- 改 count → 角标自动变
- 改 isToday → 今天标识自动移
"改数据即改视图"——"声明式 UI 的核心价值"——demo 的数据是写死的,但结构是数据驱动的——"真实日历接接口数据即可替换"。
8.9 周视图的边界与不足
demo 周视图的边界:
- 固定 7 天(不滚动换周)
- 选中不联动列表
- 无月视图切换
- 无创建入口
"边界的意义"——"demo 展示了'最小可用周视图'"——每个不足都是明确的扩展点——"demo 的价值 = 骨架正确"。
8.10 日历页的用户价值
"按天查看"解决什么问题——"我的提醒都在哪天?"——首页按"最近/紧急"排、日历按"日期"排——"两种视角的互补"——用户既看"什么最急"(首页)也看"哪天最忙"(日历)——"多视图 = 多问法"——"一个数据,多个切片"。
8.11 日历页的"今日事项"计数
"今日事项 (4)"中的 4 = items.length = 列表 4 条 = 今天角标 4——"三处数字一致":
- 标题计数(
items.length) - 列表条数(4 行)
- 今天角标(红色 4)
"数字的一致性 = 可信度"——"三处对得上,用户信得过"——如果标题写 4 但列表只有 3 条,用户立刻觉得"App 有 bug"——"跨组件的数字一致性是数据严谨性的体现"——demo 用 items.length 从源头保证一致。
8.12 周视图的"一眼定位"
用户打开日历页的视觉路径:
- 先看周视图——找到"今天"(琥珀标识)
- 看今天角标(4)——"今天最忙"
- 看下方列表——具体事项
"周视图负责定位、列表负责细节"——"概览 + 细节的两级阅读"——"日历页的信息架构:先粗后细"。
8.13 周视图的日期语义
周视图展示"12 → 18"连续 7 天——"从周二到周一"——跨了周末(周六 16/周日 17)——"校园场景的周视图"——作业/考试/报名等事项分布在整周——"周视图的完整周期 = 周一起到周日止"——demo 从 12(周二)到 18(周一)正好跨一周,覆盖工作日与周末——"演示数据的时间线完整"。
九、总结:周视图日历页设计
App 27 日历页一句话:用琥珀(#D97706)组织一个"7 天周视图 + 当日事项"的日历页——顶部 Header 单行,周视图 7 格横排(今天四重琥珀标识 + 红色事项角标 + 点击选中联动),下方今日事项列表展示当天 4 条事项。
3 个设计亮点:
- 双态独立——
selected(选中)与isToday(今天)两个概念分开表达,今天浅琥珀 + 选中实琥珀不冲突 - 角标自适应——
count > 0显示红色数字角标、else空占位保布局稳定,选中格角标变白保对比度 - 三态底色链——选中琥珀实底 > 今天浅琥珀 > 普通白,嵌套三元表达三级优先级
主题色设计逻辑:琥珀 = 时间/警示——"日历是时间工具,琥珀的'时间感'与日历天然契合"——角标用红色(C.danger)守平台惯例,与主题色分工明确——"主题色管氛围、功能色管语义"。
与系列呼应:@State 索引选中(App 26 编辑页)、条件渲染(App 21/26)、红角标(消息类通用)、双行/单行 Header(系列标准)——"27 的日历页把系列的状态管理 + 条件渲染组合出'日历视图'场景"。
体验细节:空占位保布局稳定、角标颜色自适应、items.length 动态计数、item + idx 唯一 key——"日历页的每个细节都在服务'看时间、知事项'的核心任务"。
可扩展性展望:selected 联动事项列表(点 13 显示 13 号事项)、月视图切换、事项创建跳转、实时倒计时——"demo 的周视图骨架完整,扩展点在数据联动"。 日历视图让提醒事项按日期直观呈现,本文解析完毕。

更多推荐



所有评论(0)