06 闪屏页与动画

闪屏页(Splash Screen)是应用启动后的第一个页面,承担着品牌展示、资源预加载和页面跳转的职责。本文将围绕「柚兔学伴」项目的 SplashPage.etsSplashViewModel.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
@Component
struct SplashPage {
  private pageContext: PageContext = AppStorage.get('pageContext') as PageContext;
  private appPathInfo: NavPathStack = this.pageContext.navPathStack;
  private viewModel: SplashViewModel = new SplashViewModel();
  // ...
}

关键点:

  • @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 执行时序

页面加载后,按照以下时序依次执行:

  1. 更新状态栏颜色——WindowUtil.updateStatusBarColor 传入 true 表示使用浅色状态栏(白底黑字),以匹配闪屏背景。
  2. 检查首次启动——发送 CHECK_FIRST_START 事件,ViewModel 判断是否首次打开应用并记录标记。
  3. 启动动画——调用 animateTo,在动画闭包中发送 PRELOAD_RESOURCES 事件预加载资源。
  4. 动画结束跳转——onFinish 回调中发送 JUMP_TO_MAIN 事件,导航到主页。

2.2 animateTo 全局动画 API

animateTo 是 ArkUI 提供的全局隐式动画接口,签名如下:

animateTo(value: AnimateParam, task: () => void): void
参数 说明
value 动画参数对象,包含 durationdelaycurveonFinish
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.ets
export abstract class BaseVM<T extends BaseState> {
  protected state: T;

  public constructor(initialState: T) {
    this.state = initialState;
  }

  getState(): T {
    return this.state;
  }

  public abstract sendEvent(baseVMEvent: BaseVMEvent);
}

设计要点:

  • 泛型 T extends BaseState:每个 ViewModel 可以定义自己的状态类型。
  • sendEvent 抽象方法:子类必须实现,通过事件类型分发到不同的处理方法。
  • getState:暴露当前状态给 UI 层读取。

3.2 SplashViewModel 实现

export class SplashViewModel extends BaseVM<BaseState> {
  private pageContext: PageContext = AppStorage.get('pageContext') as PageContext;
  private preferenceManager: PreferenceManager = PreferenceManager.getInstance();

  public constructor() {
    super(new BaseState());
  }

  public sendEvent(eventType: SplashEventTypeEnum): void {
    if (eventType === SplashEventTypeEnum.JUMP_TO_MAIN) {
      this.jumpToMainPage();
    } else if (eventType === SplashEventTypeEnum.PRELOAD_RESOURCES) {
      this.preloadResources();
    } else if (eventType === SplashEventTypeEnum.CHECK_FIRST_START) {
      this.checkIsFirstStart();
    }
  }
}

3.3 事件枚举

export enum SplashEventTypeEnum {
  JUMP_TO_MAIN = 'jumpToMainPage',
  PRELOAD_RESOURCES = 'preloadResources',
  CHECK_FIRST_START = 'checkIsFirstStart',
}

使用枚举而非硬编码字符串,确保事件名的类型安全和可维护性。

3.4 具体事件处理

跳转到主页

private jumpToMainPage(): void {
  this.pageContext.replacePage({
    routerName: 'MainPage',
  });
}

使用 replacePage 而非 pushPage,意味着闪屏页会从导航栈中移除,用户无法返回闪屏页。

检查首次启动

private checkIsFirstStart(): 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 架构等多个核心概念。掌握这些模式,将为后续复杂页面的开发打下坚实基础。

Logo

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

更多推荐