HarmonyOS应用《玄象》开发实战:SplashPage 全屏暗色背景与 Stack 层叠布局

阅读时长:约 18 分钟 | 难度:★★★★☆ | 篇章:第 2 篇 · 启动体验:Splash 与动画
对应源码:entry/src/main/ets/pages/SplashPage.ets、entry/src/main/ets/pages/Index.ets

前言
启动页是用户对应用的第一印象。玄象项目作为一款中华传统文化主题应用,启动页采用深邃星空 + 金色太极的视觉语言,传递出"玄之又玄,众妙之门"的东方美学。本篇将深入剖析玄象项目 SplashPage 的实现细节:从 @State 状态定义、Stack 层叠布局、Canvas 多实例管理,到 aboutToAppear 生命周期与 setTimeout 跳转。掌握这些技术细节,您就能为任何 HarmonyOS 应用打造令人印象深刻的启动体验。
提示:启动页的设计精髓在于"瞬间传达品牌调性"。玄象项目用 3 秒钟让用户感受到东方神秘主义与金色质感,这一视觉锚点将贯穿整个应用。
一、SplashPage 完整源码
1.1 源码全貌
import { Colors } from '../common/constants/Colors';
import { router } from '@kit.ArkUI';
@Entry
@Component
export struct SplashPage {
@State fadeOpacity: number = 0;
@State logoScale: number = 0.8;
private timer: number = -1;
private settings: RenderingContextSettings = new RenderingContextSettings(true);
private starFieldContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
private starMapContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
private taijiContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
aboutToAppear(): void {
this.fadeOpacity = 0;
this.logoScale = 0.8;
setTimeout(() => {
this.fadeOpacity = 1;
this.logoScale = 1;
}, 100);
this.timer = setTimeout(() => {
router.replaceUrl({ url: 'pages/HomePage' });
}, 3000);
}
aboutToDisappear(): void {
if (this.timer !== -1) {
clearTimeout(this.timer);
}
}
build() {
Stack() {
// 星空背景
Canvas(this.starFieldContext)
.width('100%')
.height('100%')
.onReady(() => {
this.drawStarField(this.starFieldContext, this.starFieldContext.width, this.starFieldContext.height);
})
// 底部星图装饰
Canvas(this.starMapContext)
.width('100%')
.height('40%')
.align(Alignment.Bottom)
.onReady(() => {
this.drawStarMap(this.starMapContext, this.starMapContext.width, this.starMapContext.height);
})
// 中央内容
Column({ space: 20 }) {
Canvas(this.taijiContext)
.width(220)
.height(220)
.margin({ top: 100 })
.onReady(() => {
this.drawTaiji(this.taijiContext, this.taijiContext.width, this.taijiContext.height);
})
Column({ space: 12 }) {
Text('玄象')
.fontSize(56)
.fontWeight(FontWeight.Bold)
.fontColor(Colors.PRIMARY_GOLD)
.letterSpacing(8)
Text('中华传统文化')
.fontSize(20)
.fontColor(Colors.LIGHT_GOLD)
.letterSpacing(4)
}
}
.width('100%')
.height('100%')
.opacity(this.fadeOpacity)
.scale({ x: this.logoScale, y: this.logoScale })
.animation({ duration: 1000, curve: Curve.EaseOut })
}
.width('100%')
.height('100%')
.backgroundColor(Colors.BG_DARK)
}
private drawStarField = (canvas: CanvasRenderingContext2D, canvasWidth: number, canvasHeight: number) => {
// ...
}
private drawTaiji = (canvas: CanvasRenderingContext2D, canvasWidth: number, canvasHeight: number) => {
// ...
}
private drawStarMap = (canvas: CanvasRenderingContext2D, canvasWidth: number, canvasHeight: number) => {
// ...
}
}
1.2 源码结构拆解
玄象项目 SplashPage.ets 包含四大组成部分:
| 部分 | 行数 | 职责 |
|---|---|---|
| 状态与上下文定义 | 8 行 | @State 与 CanvasRenderingContext2D |
| 生命周期回调 | 18 行 | aboutToAppear / aboutToDisappear |
build 渲染方法 |
60 行 | Stack + 3 个 Canvas + 中央文字 |
| Canvas 绘制函数 | 100+ 行 | 星空 / 太极 / 星图 |
二、@State 状态变量
2.1 状态变量定义
@State fadeOpacity: number = 0;
@State logoScale: number = 0.8;
玄象项目使用两个 @State 变量控制启动页动画:
| 状态变量 | 初始值 | 作用 |
|---|---|---|
fadeOpacity |
0 | 中央内容的透明度 |
logoScale |
0.8 | 中央内容的缩放比例 |
2.2 @State 工作原理
@State 是 ArkUI 的响应式状态装饰器,当变量值改变时,引用该变量的 UI 组件会自动重新渲染:
@State fadeOpacity = 0
↓
[UI 渲染] opacity(0)
↓
[setTimeout 100ms 后]
fadeOpacity = 1
↓
[ArkUI 监听到变化]
↓
[重新渲染] opacity(1) + 动画过渡
2.3 状态变量的初始化时机
玄象项目的状态变量初始化发生在组件实例创建时:
@State fadeOpacity: number = 0; // 实例化时初始化为 0
但在 aboutToAppear 中又重新赋值:
aboutToAppear(): void {
this.fadeOpacity = 0; // 重置为 0
this.logoScale = 0.8; // 重置为 0.8
// ...
}
提示:这种"双重保险"的写法确保每次进入启动页时状态都是从初始值开始的。即便应用被系统回收后重建,动画也能正确播放。
三、timer 与生命周期管理
3.1 timer 变量定义
private timer: number = -1;
玄象项目使用 private timer 保存 setTimeout 返回的 timer id,初始值 -1 表示"无 timer"。
3.2 aboutToAppear 中启动两个 timer
aboutToAppear(): void {
this.fadeOpacity = 0;
this.logoScale = 0.8;
// timer 1:100ms 后开始淡入动画
setTimeout(() => {
this.fadeOpacity = 1;
this.logoScale = 1;
}, 100);
// timer 2:3000ms 后跳转首页
this.timer = setTimeout(() => {
router.replaceUrl({ url: 'pages/HomePage' });
}, 3000);
}
玄象项目在 aboutToAppear 中启动两个定时器:
- 淡入动画定时器:100ms 后将透明度从 0 改为 1,触发淡入动画。
- 跳转定时器:3000ms 后调用
router.replaceUrl跳转到首页。
3.3 aboutToDisappear 中清理 timer
aboutToDisappear(): void {
if (this.timer !== -1) {
clearTimeout(this.timer);
}
}
玄象项目在 aboutToDisappear 中清理 timer,避免组件销毁后定时器仍触发。
3.4 两个 timer 的差异
| timer | 用途 | 是否需要清理 |
|---|---|---|
| 淡入动画 timer | 触发一次性状态变更 | 不需要(执行完自动销毁) |
| 跳转 timer | 3 秒后跳转 | 需要(避免重复跳转) |
提示:玄象项目仅保存了第二个 timer 的 id,因为只有这个 timer 在组件销毁时可能还未执行。第一个 timer 在 100ms 内必然执行完毕,无需保存 id。
四、Stack 层叠布局
4.1 Stack 容器特性
Stack 是 ArkUI 提供的层叠布局容器,子元素按顺序堆叠:
- 后声明的子元素在上方
- 默认居中对齐
- 可通过
.align()修改对齐方式
4.2 SplashPage 的 Stack 结构
Stack() {
// 第一层:星空背景(最底层,占满全屏)
Canvas(this.starFieldContext)
.width('100%')
.height('100%')
// 第二层:底部星图装饰(中间层,对齐底部,占下方 40%)
Canvas(this.starMapContext)
.width('100%')
.height('40%')
.align(Alignment.Bottom)
// 第三层:中央内容(最上层,太极图 + 文字)
Column({ space: 20 }) {
// ...
}
.width('100%')
.height('100%')
}
.width('100%')
.height('100%')
.backgroundColor(Colors.BG_DARK)
4.3 三层视觉关系图
┌──────────────────────────────────────┐
│ │
│ [星空背景 Canvas] │ ← 第一层(最底)
│ [底部星图 Canvas] │ ← 第二层(中间)
│ │
│ [太极 Canvas] │ ← 第三层(最上)
│ [玄象 标题] │
│ [中华传统文化] │
│ │
└──────────────────────────────────────┘
4.4 Stack 的关键配置
玄象项目 Stack 的关键配置:
| 配置 | 值 | 作用 |
|---|---|---|
width |
'100%' |
占满屏幕宽度 |
height |
'100%' |
占满屏幕高度 |
backgroundColor |
Colors.BG_DARK |
深色背景(避免首帧白屏) |
提示:
.backgroundColor(Colors.BG_DARK)是玄象项目启动页的关键技巧。它确保即 Canvas 还未完成绘制,用户看到的也是深色背景而非刺眼的白色。
五、Canvas 多实例管理
5.1 三个 CanvasRenderingContext2D 实例
private settings: RenderingContextSettings = new RenderingContextSettings(true);
private starFieldContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
private starMapContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
private taijiContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
玄象项目创建了 3 个 CanvasRenderingContext2D 实例,分别对应星空、星图、太极。
5.2 共享 RenderingContextSettings
三个 Context 共享同一个 RenderingContextSettings:
private settings: RenderingContextSettings = new RenderingContextSettings(true);
RenderingContextSettings(true) 启用抗锯齿,让 Canvas 绘制的圆形与曲线更平滑。
5.3 为什么使用多个 Context
玄象项目使用多个 Canvas 实例的原因:
- 隔离绘制状态:每个 Context 独立管理
fillStyle/strokeStyle等状态,避免相互污染。 - 独立尺寸控制:星空背景占 100%,星图占 40%,太极占 220×220,每个 Canvas 尺寸独立。
- 独立对齐方式:星图通过
.align(Alignment.Bottom)对齐底部,其他 Canvas 居中。
5.4 Canvas onReady 时机
Canvas(this.starFieldContext)
.width('100%')
.height('100%')
.onReady(() => {
this.drawStarField(this.starFieldContext, this.starFieldContext.width, this.starFieldContext.height);
})
onReady 在 Canvas 准备好绘制时触发,此时 starFieldContext.width 与 height 已经是实际像素值,可以安全绘制。
提示:玄象项目在
onReady内立即调用绘制函数,确保 Canvas 一准备好就开始绘制。如果在aboutToAppear中绘制,Canvas 可能还未准备好,导致绘制失败。
六、中央内容布局
6.1 Column 容器结构
Column({ space: 20 }) {
// 太极图
Canvas(this.taijiContext)
.width(220)
.height(220)
.margin({ top: 100 })
// 标题列
Column({ space: 12 }) {
Text('玄象')
.fontSize(56)
.fontWeight(FontWeight.Bold)
.fontColor(Colors.PRIMARY_GOLD)
.letterSpacing(8)
Text('中华传统文化')
.fontSize(20)
.fontColor(Colors.LIGHT_GOLD)
.letterSpacing(4)
}
}
.width('100%')
.height('100%')
.opacity(this.fadeOpacity)
.scale({ x: this.logoScale, y: this.logoScale })
.animation({ duration: 1000, curve: Curve.EaseOut })
6.2 嵌套 Column 的设计考量
玄象项目使用嵌套 Column 的设计:
Column (中央内容容器,space=20)
├── Canvas (太极图)
└── Column (标题列,space=12)
├── Text "玄象"
└── Text "中华传统文化"
为什么不直接使用单个 Column?
- 外层
Column的space=20控制太极图与标题列之间的间距。 - 内层
Column的space=12控制主标题与副标题之间的间距。 - 不同的间距通过嵌套实现独立控制。
6.3 字体属性详解
玄象项目主标题"玄象"的字体属性:
| 属性 | 值 | 作用 |
|---|---|---|
fontSize |
56 | 主标题字号 |
fontWeight |
FontWeight.Bold |
加粗显示 |
fontColor |
Colors.PRIMARY_GOLD |
主金色 |
letterSpacing |
8 | 字间距 8vp |
副标题"中华传统文化"的字体属性:
| 属性 | 值 | 作用 |
|---|---|---|
fontSize |
20 | 副标题字号 |
fontColor |
Colors.LIGHT_GOLD |
浅金色 |
letterSpacing |
4 | 字间距 4vp |
提示:
letterSpacing是营造东方书法美感的关键属性。玄象项目通过字间距拉开,让文字显得更"舒展"。
七、淡入动画机制
7.1 opacity 与 scale 双重动画
.opacity(this.fadeOpacity)
.scale({ x: this.logoScale, y: this.logoScale })
.animation({ duration: 1000, curve: Curve.EaseOut })
玄象项目启动页中央内容有两层动画:
- opacity 透明度动画:从 0 渐变到 1,营造"浮现"效果。
- scale 缩放动画:从 0.8 渐变到 1,营造"由小到大"效果。
7.2 animation 关键参数
.animation({ duration: 1000, curve: Curve.EaseOut })
| 参数 | 值 | 作用 |
|---|---|---|
duration |
1000ms | 动画时长 |
curve |
Curve.EaseOut |
缓动曲线,先快后慢 |
7.3 EaseOut 曲线的视觉感受
Curve.EaseOut 的运动曲线:
速度
↑
│ ╱────╲
│ ╱ ╲
│╱ ╲___
└─────────────→ 时间
EaseOut 的特点:
- 开始快:动画一开始就有较大变化。
- 结束慢:接近终点时变化放缓。
这种曲线特别适合"元素出现"的场景,让用户感受到有力道的浮现。
7.4 动画触发时机
aboutToAppear(): void {
this.fadeOpacity = 0;
this.logoScale = 0.8;
setTimeout(() => {
this.fadeOpacity = 1; // 状态改变触发动画
this.logoScale = 1;
}, 100);
}
玄象项目刻意延迟 100ms 才改变状态,目的:
- 确保组件已渲染:等待 ArkUI 完成首次渲染。
- 避免状态丢失:如果状态在渲染前就改变,可能导致动画丢失。
提示:这种"延迟 100ms 触发动画"是 ArkUI 中常见的最佳实践。也可以使用
setTimeout(..., 0)让状态变更进入下一个事件循环。
八、Stack 布局的进阶技巧
8.1 使用 align 控制子元素位置
Canvas(this.starMapContext)
.width('100%')
.height('40%')
.align(Alignment.Bottom) // 对齐底部
Stack 的 align 属性控制子元素在 Stack 中的对齐方式:
| 对齐方式 | 含义 |
|---|---|
Alignment.TopStart |
左上角 |
Alignment.Top |
顶部居中 |
Alignment.Center |
居中(默认) |
Alignment.Bottom |
底部居中 |
Alignment.BottomEnd |
右下角 |
8.2 使用 position 精确定位
玄象项目在首页"今日天地"卡片中使用 position 实现五行环绕:
.position(this.getFiveElementPosition(index, 180, 44))
position 接收一个 Position 对象 { x, y },精确控制子元素的左上角位置。
8.3 使用 zIndex 控制层级
如果需要手动控制子元素的堆叠层级,可以使用 zIndex:
Canvas(this.starFieldContext)
.zIndex(1) // 最底层
Canvas(this.starMapContext)
.zIndex(2) // 中间层
Column({ space: 20 }) {
// 中央内容
}
.zIndex(3) // 最上层
提示:玄象项目通过
Stack中子元素的声明顺序控制层级(后声明的在上层),因此不需要显式使用zIndex。
九、SplashPage 的扩展方向
9.1 添加品牌 LOGO 图片
玄象项目当前使用 Canvas 绘制太极图。如果引入 LOGO 图片:
Column({ space: 20 }) {
Image($r('app.media.logo'))
.width(220)
.height(220)
.objectFit(ImageFit.Contain)
.margin({ top: 100 })
Column({ space: 12 }) {
// 文字
}
}
9.2 添加加载进度条
启动页常用于显示加载进度:
Column({ space: 20 }) {
// 太极图 + 标题
// 加载进度条
Progress({ value: this.loadingProgress, total: 100, type: ProgressType.Linear })
.width('60%')
.color(Colors.PRIMARY_GOLD)
}
9.3 添加版本号
Column({ space: 20 }) {
// 主内容
}
.justifyContent(FlexAlign.Center)
Text(`v1.0.0`)
.fontSize(12)
.fontColor(Colors.TEXT_DIM)
.position({ x: '50%', y: '90%' })
.translate({ x: '-50%' })
总结
本篇以玄象项目 SplashPage 为蓝本,深入剖析了 HarmonyOS 应用启动页的实现细节:从 @State 响应式状态定义、Stack 三层视觉堆叠、Canvas 多实例管理,到 aboutToAppear 生命周期、setTimeout 双定时器机制、opacity + scale 双重动画,再到 backgroundColor 预设避免首帧白屏的关键技巧。掌握这套启动页实现方法论,您就能为任何 HarmonyOS 应用打造令人印象深刻的启动体验。
下一篇:《12 · Canvas 星空绘制:arc / fillStyle / 随机散点算法》,将带您深入玄象项目启动页的星空背景 Canvas 绘制细节。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源:
- HarmonyOS 官方文档:Stack 组件
- HarmonyOS 官方文档:Canvas 组件
- HarmonyOS 官方文档:CanvasRenderingContext2D
- HarmonyOS 官方文档:animation 动画
- 开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐
所有评论(0)