功能概述
在热力图页面(HeatmapPage)中,使用说明功能由三部分协同工作:
- 内嵌式使用说明面板 --- 首次进入或开启常驻时,在网格上方展开的引导卡片
- 浮动帮助按钮 --- 面板隐藏后出现在右上角的圆形悬浮按钮
- AlertDialog 弹窗 --- 点击
?按钮后弹出的详细帮助对话框
三者通过两个状态变量联动:showGuide(控制面板显隐)和 showGuideAlways(控制是否常驻)。同时利用 PreferencesManager 将用户选择持久化到本地。
一、状态定义与初始化
typescript
@State showGuide: boolean = false // 是否显示内嵌面板
@State showGuideAlways: boolean = false // 是否常驻显示
页面加载时调用 loadGuidePreferences() 从本地偏好读取设置:
typescript
async aboutToAppear(): Promise<void> {
await this.loadData()
await this.loadGuidePreferences() // 加载使用说明偏好
}
private async loadGuidePreferences(): Promise<void> {
const hasSeenGuide = await this.preferencesManager.hasSeenGuide()
this.showGuideAlways = await this.preferencesManager.getShowGuideAlways()
// 首次使用 或 设置了常驻显示 → 自动展示面板
if (!hasSeenGuide || this.showGuideAlways) {
this.showGuide = true
// 首次查看后标记为"已读",下次不再自动弹出
if (!hasSeenGuide) {
await this.preferencesManager.markGuideAsSeen()
}
}
}
设计要点:
- 使用双条件判断:首次用户必看;老用户可选择常驻
markGuideAsSeen()只在首次触发一次,之后由showGuideAlways接管
二、内嵌式使用说明面板
当 showGuide === true 时,在网格内容上方渲染一个卡片:
typescript
// GridTimeline() Builder 中
if (this.showGuide) {
Column() {
// 标题栏:标题 + 关闭按钮
Row() {
Text('使用说明')
.fontSize(this.fs(16))
.fontWeight(FontWeight.Bold)
.fontColor($r('app.color.text_primary'))
Blank()
Button() { Text('×') ... }
.backgroundColor(Color.Transparent)
.onClick(async () => {
this.showGuide = false
// 关闭时若开启了常驻,则一并取消
if (this.showGuideAlways) {
this.showGuideAlways = false
await this.preferencesManager.setShowGuideAlways(false)
}
})
}
.width('100%')
.margin({ bottom: 8 })
// 引导条目列表
Column() {
Text('• 点击空白格子:快速记录单个时间段')
Text('• 长按并拖动:选择多个连续时间段')
Text('• 点击已记录格子:编辑时间块')
Text('• 左上角白点:标识时间块的起始位置')
Text('• 金色边框:高质量时间(专注度≥4 且 价值感≥4)')
Text('提示:可通过右上角 ? 按钮随时查看帮助')
.fontStyle(FontStyle.Italic)
}
.alignItems(HorizontalAlign.Start)
}
.width('100%')
.padding(16)
.backgroundColor($r('app.color.card_background'))
.borderRadius(8)
.margin({ left: 8, right: 8, bottom: 12 })
}
布局要点:
| 要素 | 实现方式 |
|---|---|
| 标题与关闭按钮同行 | Row + Blank() 占据中间空间 |
| 关闭按钮样式 | 背景透明、无 padding、仅显示 × 符号 |
| 条目左对齐 | 外层 Column 设置 alignItems(HorizontalAlign.Start) |
| 底部提示区分级 | 斜体 + 灰色 + 较小字号 |
关闭逻辑的细节 :点击 × 时,不仅设 showGuide = false,还检查并同步取消常驻状态,保证 UI 和持久化数据一致。

三、浮动帮助按钮
当内嵌面板隐藏时(!this.showGuide),在页面右上角显示一个圆形悬浮按钮:
typescript
// build() 方法中的 Stack 布局内
if (!this.showGuide) {
Button() {
Text('?')
.fontSize(this.fs(20))
.fontWeight(FontWeight.Bold)
.fontColor(Color.White)
}
.width(48)
.height(48)
.borderRadius(24) // 正圆形
.backgroundColor($r('app.color.primary_color'))
.position({ x: '100%', y: 70 }) // 锚定右侧,y=70 是日期导航栏高度
.translate({ x: -68, y: 20 }) // 向左偏移68,向下偏移20
.shadow({
radius: 12,
color: $r('app.color.primary_shadow'),
offsetX: 0,
offsetY: 4
})
.onClick(() => {
this.showGuideDialog()
})
}
定位原理:
- 整个页面使用
Stack布局,浮动按钮作为叠加层放置 position({ x: '100%', y: 70 })将按钮锚定到容器右边缘,垂直位置在日期导航栏下方translate({ x: -68, y: 20 })微调最终位置:距离右边距 20px,距离导航栏底部 20px(48px按钮宽度 + 20px边距 = 68px)shadow属性赋予按钮浮起感
互斥显示 :if (!this.showGuide) 保证面板和按钮不会同时出现。

四、AlertDialog 帮助弹窗
点击 ? 按钮后调用 showGuideDialog() 弹出系统对话框:
typescript
private showGuideDialog(): void {
AlertDialog.show({
title: '使用说明',
message: '• 点击空白格子:快速记录单个时间段\n\n' +
'• 长按并拖动:先长按某个格子,再滑动选择多个连续时间段\n\n' +
'• 点击已记录格子:编辑时间块\n\n' +
'• 左上角白点:标识每个时间块的起始位置\n\n' +
'• 金色边框:高质量时间(专注度≥4 且 价值感≥4)\n\n' +
'• 左侧时间标签:显示每行的开始时间\n\n' +
'• 格子颜色:整点格子较深,半点格子较浅',
primaryButton: {
value: this.showGuideAlways ? '关闭常驻显示' : '开启常驻显示',
action: async () => {
this.showGuideAlways = !this.showGuideAlways
await this.preferencesManager.setShowGuideAlways(this.showGuideAlways)
this.showGuide = this.showGuideAlways // 开启时常驻显示面板
}
},
secondaryButton: {
value: '知道了',
action: () => {} // 仅关闭对话框
},
alignment: DialogAlignment.Center
})
}
关键设计:
- 动态按钮文案:主按钮文字根据当前状态切换------"开启常驻显示" ↔ "关闭常驻显示"
- 双操作合一 :点击主按钮同时完成:切换状态 → 持久化 → 控制
showGuide(开启时同步显示面板) \n\n分段:在 message 中使用双换行实现段落间距

五、偏好设置持久化
PreferencesManager 提供四个 API 管理使用说明状态:
typescript
// PreferencesManager.ets 中相关方法
private static readonly KEY_HAS_SEEN_GUIDE: string = 'has_seen_guide'
private static readonly KEY_SHOW_GUIDE_ALWAYS: string = 'show_guide_always'
// 检查是否首次查看
async hasSeenGuide(): Promise<boolean> {
return await this.prefs.get(KEY_HAS_SEEN_GUIDE, false) as boolean
}
// 标记为已查看(只执行一次)
async markGuideAsSeen(): Promise<void> {
await this.prefs.put(KEY_HAS_SEEN_GUIDE, true)
await this.prefs.flush()
}
// 获取常驻显示设置
async getShowGuideAlways(): Promise<boolean> {
return await this.prefs.get(KEY_SHOW_GUIDE_ALWAYS, false) as boolean
}
// 切换常驻显示
async setShowGuideAlways(show: boolean): Promise<void> {
await this.pfs.put(KEY_SHOW_GUIDE_ALWAYS, show)
await this.prefs.flush()
}
首次启动时在 setDefaultPreferences() 中写入默认值:
typescript
await this.prefs.put(KEY_HAS_SEEN_GUIDE, false) // 默认未查看
await this.prefs.put(KEY_SHOW_GUIDE_ALWAYS, false) // 默认非常驻
底层使用 HarmonyOS 的 @ohos.data.preferences 轻量级 KV 存储,flush() 保证立即写入磁盘。