鸿蒙原生应用实战:通知提醒日历页的 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 块结构

  1. Header — "日历视图"(单行)
  2. WeekRow — 7 天周视图(本页核心)
  3. SectionTitle('今日事项 (4)') + ItemList — 4 条事项

与 App 27 首页的结构对比:首页是"倒计时卡 + 提醒列表"、日历页是"周视图 + 事项列表"——"同为'概览 + 列表'双区,但概览区形态不同"——"首页管'最近重要'、日历管'按天查看'"——两个 Tab 从不同维度组织同一批提醒数据——"同一数据多视图 = 信息架构的分工"

SectionTitle('今日事项 (' + this.items.length + ')') 动态计数——"标题里带数量"(用 items.length 而非硬编码 4)——"数量跟着数据走"——加事项标题自动变——"用 length 不用魔法数字"——这是数据驱动 UI 的基本素养。

日历页的 Tab 身份:📅 图标 + "日历"标题——"📅 = 日期视图的通用符号"——与首页 🔔(提醒)、添加 ➕(创建)、我的 👤(个人)并列——"4 个 Tab emoji 覆盖了提醒 App 的完整功能面"

主题色 #D97706 琥珀在日历页的运用——选中日期格实琥珀底、今天浅琥珀底 + 琥珀边框、今天星期/日期琥珀字——"琥珀在日历里 = 时间标记"——"日历是时间工具,琥珀恰好是'时间感'的颜色"

日历视图首屏 · 单行Header(日历视图)+7天周视图(周二12到周一18,今日14琥珀高亮+红色角标4)+今日事项(4)列表(高数作业截止/选课系统开放/社团活动报名截止/电费充值提醒)

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

二、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 层):

  1. 星期(10 号:今天琥珀、其余灰)
  2. 日期数字(16 号加粗:选中白 / 今天琥珀 / 普通深色)
  3. 事项角标(count 红点 14×14:有事项显示数字、无事项占位)
  4. 格底 + 边框(选中琥珀 / 今天浅琥珀 / 普通白)

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 条事项

  1. 高数作业截止(23:59)
  2. 选课系统开放(09:00)
  3. 社团活动报名截止
  4. 电费充值提醒

事项行结构🔔 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 的跨页数据一致性"——"多页面展示同一数据的不同切片,是信息架构的常态"

日历日期切换 · 点击"周三 13"日期格后琥珀选中态移至13,13 的角标 2 变白色,周视图与事项列表保持显示

四、数据模型与状态设计

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 索引驱动选中视觉(数据联动留白)——"同一'索引选中'模式,联动深度不同"——"系列的模式复用 + 各自的完成度"

日历选中联动 · 点击"周五 15"日期格后琥珀选中态从今日14移至15,15 的角标 1 变白色,今日14保留浅琥珀今天标识

五、日历的实现细节

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——系列标准——"日历页也遵循全系列的基础架构"

事项交互反馈 · 点击"电费充值提醒"事项行后屏幕底部弹出 Toast,周视图与事项列表保持显示

六、日历的交互与可用性

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列表形态图标策略
13emoji + 标题 + 操作每行不同 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 周视图的"一眼定位"

用户打开日历页的视觉路径

  1. 先看周视图——找到"今天"(琥珀标识)
  2. 看今天角标(4)——"今天最忙"
  3. 看下方列表——具体事项

"周视图负责定位、列表负责细节"——"概览 + 细节的两级阅读"——"日历页的信息架构:先粗后细"

8.13 周视图的日期语义

周视图展示"12 → 18"连续 7 天——"从周二到周一"——跨了周末(周六 16/周日 17)——"校园场景的周视图"——作业/考试/报名等事项分布在整周——"周视图的完整周期 = 周一起到周日止"——demo 从 12(周二)到 18(周一)正好跨一周,覆盖工作日与周末——"演示数据的时间线完整"

九、总结:周视图日历页设计

App 27 日历页一句话用琥珀(#D97706)组织一个"7 天周视图 + 当日事项"的日历页——顶部 Header 单行,周视图 7 格横排(今天四重琥珀标识 + 红色事项角标 + 点击选中联动),下方今日事项列表展示当天 4 条事项。

3 个设计亮点

  1. 双态独立——selected(选中)与 isToday(今天)两个概念分开表达,今天浅琥珀 + 选中实琥珀不冲突
  2. 角标自适应——count > 0 显示红色数字角标、else 空占位保布局稳定,选中格角标变白保对比度
  3. 三态底色链——选中琥珀实底 > 今天浅琥珀 > 普通白,嵌套三元表达三级优先级

主题色设计逻辑:琥珀 = 时间/警示——"日历是时间工具,琥珀的'时间感'与日历天然契合"——角标用红色(C.danger)守平台惯例,与主题色分工明确——"主题色管氛围、功能色管语义"

与系列呼应@State 索引选中(App 26 编辑页)、条件渲染(App 21/26)、红角标(消息类通用)、双行/单行 Header(系列标准)——"27 的日历页把系列的状态管理 + 条件渲染组合出'日历视图'场景"

体验细节:空占位保布局稳定、角标颜色自适应、items.length 动态计数、item + idx 唯一 key——"日历页的每个细节都在服务'看时间、知事项'的核心任务"

可扩展性展望:selected 联动事项列表(点 13 显示 13 号事项)、月视图切换、事项创建跳转、实时倒计时——"demo 的周视图骨架完整,扩展点在数据联动"。 日历视图让提醒事项按日期直观呈现,本文解析完毕。

配图

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐