06 闪屏页与动画
闪屏页(Splash Screen)是应用启动后的第一个页面,承担着品牌展示、资源预加载和页面跳转的职责。本文将围绕「柚兔学伴」项目的SplashPage.ets和SplashViewModel.ets,讲解如何利用 ArkUI 的页面生命周期、animateTo全局动画 API 以及事件驱动的 ViewModel 架构来构建一个完整的闪屏页。
1. SplashPage 整体结构
SplashPage使用@Entry+@Component装饰器声明为页面入口组件:
import{ConfigurationConstant}from'@kit.AbilityKit';import{CommonConstants,Logger,PageContext,WindowUtil}from'common';import{SplashEventTypeEnum,SplashViewModel}from'../viewmodel/SplashViewModel';@Entry@Componentstruct SplashPage{privatepageContext:PageContext=AppStorage.get('pageContext')asPageContext;privateappPathInfo:NavPathStack=this.pageContext.navPathStack;privateviewModel:SplashViewModel=newSplashViewModel();// ...}关键点:
@Entry:将该组件标记为页面路由入口,框架会自动注册路由表。PageContext:从AppStorage获取全局页面上下文,其中包含NavPathStack导航栈,用于页面跳转。SplashViewModel:事件驱动的 ViewModel 实例,负责处理业务逻辑。
2. 页面生命周期与动画流程
闪屏页的核心逻辑集中在aboutToAppear生命周期回调中:
aboutToAppear():void{WindowUtil.updateStatusBarColor(getContext(this),true);this.viewModel.sendEvent(SplashEventTypeEnum.CHECK_FIRST_START);animateTo({delay:CommonConstants.ANIMATION_DELAY,duration:CommonConstants.ANIMATION_DURATION,onFinish:()=>{this.viewModel.sendEvent(SplashEventTypeEnum.JUMP_TO_MAIN);}},()=>{this.viewModel.sendEvent(SplashEventTypeEnum.PRELOAD_RESOURCES);})}2.1 执行时序
页面加载后,按照以下时序依次执行:
- 更新状态栏颜色——
WindowUtil.updateStatusBarColor传入true表示使用浅色状态栏(白底黑字),以匹配闪屏背景。 - 检查首次启动——发送
CHECK_FIRST_START事件,ViewModel 判断是否首次打开应用并记录标记。 - 启动动画——调用
animateTo,在动画闭包中发送PRELOAD_RESOURCES事件预加载资源。 - 动画结束跳转——
onFinish回调中发送JUMP_TO_MAIN事件,导航到主页。
2.2 animateTo 全局动画 API
animateTo是 ArkUI 提供的全局隐式动画接口,签名如下:
animateTo(value:AnimateParam,task:()=>void):void| 参数 | 说明 |
|---|---|
value | 动画参数对象,包含duration、delay、curve、onFinish等 |
task | 动画闭包,闭包内触发的属性变化会自动以动画形式过渡 |
在本项目中:
delay: CommonConstants.ANIMATION_DELAY(500ms)——延迟启动,确保页面渲染完毕。duration: CommonConstants.ANIMATION_DURATION(1000ms)——动画持续时长。onFinish——动画完成后的回调,用于触发页面跳转。
注意:
animateTo内的闭包应当只包含 UI 属性修改。如果只需延时执行逻辑而不涉及 UI 属性动画,也可以考虑使用setTimeout替代。本项目中闭包内调用PRELOAD_RESOURCES事件,是在动画期间并行执行预加载任务。
3. 事件驱动的 ViewModel
SplashViewModel继承自BaseVM<BaseState>,采用事件驱动架构:
3.1 BaseVM 抽象基类
// common/src/main/ets/viewmodel/BaseVM.etsexportabstractclassBaseVM<TextendsBaseState>{protectedstate:T;publicconstructor(initialState:T){this.state=initialState;}getState():T{returnthis.state;}publicabstractsendEvent(baseVMEvent:BaseVMEvent);}设计要点:
- 泛型
T extends BaseState:每个 ViewModel 可以定义自己的状态类型。 sendEvent抽象方法:子类必须实现,通过事件类型分发到不同的处理方法。getState:暴露当前状态给 UI 层读取。
3.2 SplashViewModel 实现
exportclassSplashViewModelextendsBaseVM<BaseState>{privatepageContext:PageContext=AppStorage.get('pageContext')asPageContext;privatepreferenceManager:PreferenceManager=PreferenceManager.getInstance();publicconstructor(){super(newBaseState());}publicsendEvent(eventType:SplashEventTypeEnum):void{if(eventType===SplashEventTypeEnum.JUMP_TO_MAIN){this.jumpToMainPage();}elseif(eventType===SplashEventTypeEnum.PRELOAD_RESOURCES){this.preloadResources();}elseif(eventType===SplashEventTypeEnum.CHECK_FIRST_START){this.checkIsFirstStart();}}}3.3 事件枚举
exportenumSplashEventTypeEnum{JUMP_TO_MAIN='jumpToMainPage',PRELOAD_RESOURCES='preloadResources',CHECK_FIRST_START='checkIsFirstStart',}使用枚举而非硬编码字符串,确保事件名的类型安全和可维护性。
3.4 具体事件处理
跳转到主页:
privatejumpToMainPage():void{this.pageContext.replacePage({routerName:'MainPage',});}使用replacePage而非pushPage,意味着闪屏页会从导航栈中移除,用户无法返回闪屏页。
检查首次启动:
privatecheckIsFirstStart():void{this.preferenceManager.hasValue('isFirstStart').then((hasResult:boolean)=>{if(hasResult){Logger.info(TAG,'Not first startup.');}else{Logger.info(TAG,'First startup.');this.preferenceManager.setValue('isFirstStart',false).then(()=>{Logger.info(TAG,'Put the value of startup Successfully.');}).catch((err:BusinessError)=>{Logger.error(TAG,`Put the value of startup Failed, err code:${err.code}`);});}});}通过PreferenceManager持久化存储首次启动标记,基于@ohos.data.preferences实现。
4. UI 构建:Navigation 与背景图
build(){Navigation(this.appPathInfo){Column().width('100%').height('100%').backgroundImage($r('app.media.ic_splash')).backgroundImageSize({width:'100%',height:'100%'})}.hideTitleBar(true).mode(NavigationMode.Stack).height('100%').width('100%')}4.1 Navigation 容器
Navigation(this.appPathInfo):绑定NavPathStack导航栈,支持页面路由。.hideTitleBar(true):隐藏标题栏,闪屏页需要全屏展示。.mode(NavigationMode.Stack):使用栈管理模式,支持pushPage/replacePage等路由操作。
4.2 背景图
$r('app.media.ic_splash'):引用resources/base/media/ic_splash资源,这是 ArkUI 的资源引用语法$r('app.type.name')。backgroundImageSize:设为{ width: '100%', height: '100%' }确保图片铺满整个容器。
5. onPageHide:状态栏颜色恢复
当用户离开闪屏页(跳转到主页后),需要将状态栏颜色恢复为与系统深浅色模式匹配:
onPageHide(){Logger.info(TAG,'onPageHide');WindowUtil.updateStatusBarColor(getContext(this),AppStorage.get('systemColorMode')===ConfigurationConstant.ColorMode.COLOR_MODE_DARK);}onPageHide:页面隐藏时的生命周期回调。AppStorage.get('systemColorMode'):从全局存储读取当前颜色模式。- 当系统为深色模式时,传入
true(浅色状态栏);否则传入false(深色状态栏)。
6. 总结
| 知识点 | 说明 |
|---|---|
@Entry+@Component | 页面入口组件声明 |
aboutToAppear | 页面出现前回调,适合初始化逻辑 |
animateTo | 全局隐式动画 API,支持 delay/duration/onFinish |
BaseVM<T> | 抽象 ViewModel 基类,事件驱动架构 |
sendEvent+ 枚举 | 类型安全的事件分发机制 |
Navigation+NavPathStack | 声明式导航与路由栈管理 |
$r() | 资源引用语法,访问 media/string/color 资源 |
onPageHide | 页面隐藏回调,用于资源清理或状态恢复 |
PreferenceManager | 轻量级键值对持久化存储 |
闪屏页看似简单,却串联了 ArkUI 的页面生命周期、动画系统、导航框架和 ViewModel 架构等多个核心概念。掌握这些模式,将为后续复杂页面的开发打下坚实基础。