OpenHarmony动画详解

简介

动画是组件的基础特性之一,精心设计的动画使 UI 变化更直观,平滑的动画效果能够很好地增强差异性功能的过渡,有助于改进应用程序的外观并改善用户体验。

OpenHarmony 动画分类:

  • 属性动画:组件的某些通用属性变化时,可以通过属性动画实现渐变过渡效果,提升用户体验。支持的属性包括 width、height、backgroundColor、opacity、scale、rotate、translate 等。
  • 显示动画:提供全局 animateTo 显式动画接口来指定由于闭包代码导致的状态变化插入过渡动效。
  • 转场动画
    • 页面间转场:在全局 pageTransition 方法内配置页面入场和页面退场时的自定义转场动效。
    • 组件内转场:组件内转场主要通过 transition 属性配置转场参数,在组件插入和删除时显示过渡动效,主要用于容器组件中的子组件插入和删除时,提升用户体验(需要配合animateTo 才能生效,动效时长、曲线、延时跟随 animateTo 中的配置)。
    • 共享元素转场:设置页面间转场时共享元素的转场动效。
  • 路径动画:设置组件进行位移动画时的运动路径。
  • 窗口动画:提供启动退出过程中控件动画和应用窗口联动动画能力。

动画详解

属性动画

通过控件的 animation 属性实现动画效果。

animation(value: {duration?: number, tempo?: number, curve?: string | Curve | ICurve, delay?:number, iterations: number, playMode?: PlayMode, onFinish?: () => void})

参数类型必填描述
durationnumber设置动画时长。单位为毫秒,默认动画时长为 1000 毫秒。 默认值:1000
temponumber动画播放速度。数值越大,动画播放速度越快,数值越小,播放速度越慢 值为 0 时,表示不存在动画。 默认值:1
curvestringCurveICurve9+
delaynumber设置动画延迟执行的时长。单位为毫秒,默认不延时播放。 默认值:0
iterationsnumber设置播放次数。默认播放一次,设置为-1 时表示无限次播放。 默认值:1
playModePlayMode设置动画播放模式,默认播放完成后重头开始播放。 默认值:PlayMode.Normal
onFinish() => void状态回调,动画播放完成时触发。

示例

// xxx.ets
@Entry
@Component
struct AnimateToExample {
  @State widthSize: number = 250
  @State heightSize: number = 100
  @State rotateAngle: number = 0
  private flag: boolean = true

  build() {
    Column() {
      Button('change width and height')
        .width(this.widthSize)
        .height(this.heightSize)
        .margin(30)
        .onClick(() => {
          if (this.flag) {
            animateTo({
              duration: 2000,
              curve: Curve.EaseOut,
              iterations: 3,
              playMode: PlayMode.Normal,
              onFinish: () => {
                console.info('play end')
              }
            }, () => {
              this.widthSize = 100
              this.heightSize = 50
            })
          } else {
            animateTo({}, () => {
              this.widthSize = 250
              this.heightSize = 100
            })
          }
          this.flag = !this.flag
        })
      Button('change rotate angle')
        .margin(50)
        .rotate({ angle: this.rotateAngle })
        .onClick(() => {
          animateTo({
            duration: 1200,
            curve: Curve.Friction,
            delay: 500,
            iterations: -1, // 设置-1表示动画无限循环
            playMode: PlayMode.AlternateReverse,
            onFinish: () => {
              console.info('play end')
            }
          }, () => {
            this.rotateAngle = 90
          })
        })
    }.width('100%').margin({ top: 5 })
  }
}

显示动画

通过全局 animateTo 显式动画接口来定由于闭包代码导致的状态变化插入过渡动效。

animateTo(value: AnimateParam, event: () => void): void

参数类型是否必填描述
valueAnimateParam设置动画效果相关参数。
event() => void指定显示动效的闭包函数,在闭包函数中导致的状态变化系统会自动插入过渡动画。

AnimateParam 对象说明

名称类型描述
durationnumber动画持续时间,单位为毫秒。 默认值:1000
temponumber动画的播放速度,值越大动画播放越快,值越小播放越慢,为 0 时无动画效果。 默认值:1.0
curveCurveCurves
delaynumber单位为 ms(毫秒),默认不延时播放。 默认值:0
iterationsnumber默认播放一次,设置为-1 时表示无限次播放。 默认值:1
playModePlayMode设置动画播放模式,默认播放完成后重头开始播放。 默认值:PlayMode.Normal
onFinish() => void动效播放完成回调。

示例

// xxx.ets
@Entry
@Component
struct AnimateToExample {
  @State widthSize: number = 250
  @State heightSize: number = 100
  @State rotateAngle: number = 0
  private flag: boolean = true

  build() {
    Column() {
      Button('change width and height')
        .width(this.widthSize)
        .height(this.heightSize)
        .margin(30)
        .onClick(() => {
          if (this.flag) {
            animateTo({
              duration: 2000,
              curve: Curve.EaseOut,
              iterations: 3,
              playMode: PlayMode.Normal,
              onFinish: () => {
                console.info('play end')
              }
            }, () => {
              this.widthSize = 100
              this.heightSize = 50
            })
          } else {
            animateTo({}, () => {
              this.widthSize = 250
              this.heightSize = 100
            })
          }
          this.flag = !this.flag
        })
      Button('change rotate angle')
        .margin(50)
        .rotate({ angle: this.rotateAngle })
        .onClick(() => {
          animateTo({
            duration: 1200,
            curve: Curve.Friction,
            delay: 500,
            iterations: -1, // 设置-1表示动画无限循环
            playMode: PlayMode.AlternateReverse,
            onFinish: () => {
              console.info('play end')
            }
          }, () => {
            this.rotateAngle = 90
          })
        })
    }.width('100%').margin({ top: 5 })
  }
}

注:效果和属性动画等价

转场动画

页面间转场

在全局 pageTransition 方法内配置页面入场和页面退场时的自定义转场动效。

名称参数参数描述
PageTransitionEnter{ type: RouteType, duration: number, curve:Curvestring, delay: number }
PageTransitionExit{ type: RouteType, duration: number, curve: Curvestring, delay: number }

RouteType 枚举说明

名称描述
Pop重定向指定页面。PageA 跳转到 PageB 时,PageA 为 Exit+Pop,PageB 为 Enter+Pop。
Push跳转到下一页面。PageB 返回至 PageA 时,PageA 为 Enter+Push,PageB 为 Exit+Push。
None页面未重定向。

属性

参数名称参数类型必填参数描述
slideSlideEffect设置页面转场时的滑入滑出效果。 默认值:SlideEffect.Right
translate{ x? : numberstring, y? : numberstring, z? : number
scale{ x? : number, y? : number, z? : number, centerX? : numberstring, centerY? : numberstring }
opacitynumber设置入场的起点透明度值或者退场的终点透明度值。 默认值:1

SlideEffect 枚举说明

名称描述
Left设置到入场时表示从左边滑入,出场时表示滑出到左边。
Right设置到入场时表示从右边滑入,出场时表示滑出到右边。
Top设置到入场时表示从上边滑入,出场时表示滑出到上边。
Bottom设置到入场时表示从下边滑入,出场时表示滑出到下边。

事件

事件功能描述
onEnter(event: (type?: RouteType, progress?: number) => void)回调入参为当前入场动画的归一化进度[0 - 1]。 - type:跳转方法。 - progress:当前进度。
onExit(event: (type?: RouteType, progress?: number) => void)回调入参为当前退场动画的归一化进度[0 - 1]。 - type:跳转方法。 - progress:当前进度。

组件内转场

组件内转场主要通过 transition 属性配置转场参数,在组件插入和删除时显示过渡动效,主要用于容器组件中的子组件插入和删除时,提升用户体验(需要配合 animateTo) 才能生效,动效时长、曲线、延时跟随 animateTo 中的配置)。

属性

名称参数类型参数描述
transitionTransitionOptions所有参数均为可选参数,详细描述见 TransitionOptions 参数说明。

TransitionOptions 参数说明

参数名称参数类型必填参数描述
typeTransitionType**默认包括组件新增和删除。 默认值:TransitionType.All****说明:**不指定 Type 时说明插入删除使用同一种效果。
opacitynumber设置组件转场时的透明度效果,为插入时起点和删除时终点的值。 默认值:1
translate{ x? : number, y? : number, z? : number }设置组件转场时的平移效果,为插入时起点和删除时终点的值。 -x:横向的平移距离。 -y:纵向的平移距离。 -z:竖向的平移距离。
scale{ x? : number, y? : number, z? : number, centerX? : number, centerY? : number }设置组件转场时的缩放效果,为插入时起点和删除时终点的值。 -x:横向放大倍数(或缩小比例)。 -y:纵向放大倍数(或缩小比例)。 -z:竖向放大倍数(或缩小比例)。 - centerX、centerY 缩放中心点。 - 中心点为 0 时,默认的是组件的左上角。
rotate{ x?: number, y?: number, z?: number, angle?: Angle, centerX?: Length, centerY?: Length }设置组件转场时的旋转效果,为插入时起点和删除时终点的值。 -x:横向的旋转向量。 -y:纵向的旋转向量。 -z:竖向的旋转向量。 - centerX,centerY 指旋转中心点。 - 中心点为(0,0)时,默认的是组件的左上角。

示例

// xxx.ets
@Entry
@Component
struct TransitionExample {
  @State flag: boolean = true
  @State show: string = 'show'

  build() {
    Column() {
      Button(this.show).width(80).height(30).margin(30)
        .onClick(() => {
          // 点击Button控制Image的显示和消失
          animateTo({ duration: 1000 }, () => {
            if (this.flag) {
              this.show = 'hide'
            } else {
              this.show = 'show'
            }
            this.flag = !this.flag
          })
        })
      if (this.flag) {
        // Image的显示和消失配置为不同的过渡效果
        Image($r('app.media.testImg')).width(300).height(300)
          .transition({ type: TransitionType.Insert, scale: { x: 0, y: 1.0 } })
          .transition({ type: TransitionType.Delete, rotate: { angle: 180 } })
      }
    }.width('100%')
  }
}

共享元素转场

设置页面间转场时共享元素的转场动效。

属性

名称参数参数描述
sharedTransitionid: string, { duration?: number, curve?: Curve**string, delay?: number, motionPath?: { path: string, form?: number, to?: number, rotatable?: boolean }, zIndex?: number, type?😗*SharedTransitionEffectType}

示例

示例代码为点击图片跳转页面时,显示共享元素图片的自定义转场动效。

// xxx.ets
@Entry
@Component
struct SharedTransitionExample {
  @State active: boolean = false

  build() {
    Column() {
      Navigator({ target: 'pages/PageB', type: NavigationType.Push }) {
        Image($r('app.media.ic_health_heart')).width(50).height(50)
          .sharedTransition('sharedImage', { duration: 800, curve: Curve.Linear, delay: 100 })
      }.padding({ left: 20, top: 20 })
      .onClick(() => {
        this.active = true
      })
    }
  }
}

// PageB.ets
@Entry
@Component
struct pageBExample {
  build() {
    Stack() {
      Image($r('app.media.ic_health_heart')).width(150).height(150).sharedTransition('sharedImage')
    }.width('100%').height('100%')
  }
}

路径动画

设置组件进行位移动画时的运动路径。

属性

名称参数类型默认值描述
motionPath**{ path: string, from?: number, to?: number, rotatable?: boolean }****说明:**path 中支持使用 start 和 end 进行起点和终点的替代,如: ‘Mstart.x start.y L50 50 Lend.x end.y Z’{ ‘’, 0.0, 1.0, false }设置组件的运动路径,入参说明如下: - path:位移动画的运动路径,使用 svg 路径字符串。 - from:运动路径的起点,默认为 0.0。 - to:运动路径的终点,默认为 1.0。 - rotatable:是否跟随路径进行旋转。

示例

// xxx.ets
@Entry
@Component
struct MotionPathExample {
  @State toggle: boolean = true

  build() {
    Column() {
      Button('click me')
        // 执行动画:从起点移动到(300,200),再到(300,500),再到终点
        .motionPath({ path: 'Mstart.x start.y L300 200 L300 500 Lend.x end.y', from: 0.0, to: 1.0, rotatable: true })
        .onClick(() => {
          animateTo({ duration: 4000, curve: Curve.Linear }, () => {
            this.toggle = !this.toggle // 通过this.toggle变化组件的位置
          })
        })
    }.width('100%').height('100%').alignItems(this.toggle ? HorizontalAlign.Start : HorizontalAlign.Center)
  }
}

窗口动画

窗口动画管理器,可以监听应用启动退出时应用的动画窗口,提供启动退出过程中控件动画和应用窗口联动动画能力。

导入模块

import windowAnimationManager from '@ohos.animation.windowAnimationManager'

windowAnimationManager.setController

setController(controller: WindowAnimationController): void

设置窗口动画控制器

在使用 windowAnimationManager 的其他接口前,需要预先调用本接口设置窗口动画控制器。

参数:

参数名类型必填说明
controllerWindowAnimationController窗口动画的控制器。

windowAnimationManager.minimizeWindowWithAnimation

minimizeWindowWithAnimation(windowTarget: WindowAnimationTarget): Promise

最小化动画目标窗口,并返回动画完成的回调。使用 Promise 异步回调。

参数:

参数名类型必填说明
windowTargetWindowAnimationTarget动画目标窗口。

返回值:

类型说明
PromiseWindowAnimationFinishedCallbackPromise 对象,返回动画完成的回调。

WindowAnimationController

窗口动画控制器。在创建一个 WindowAnimationController 对象时,需要实现其中的所有回调函数。

onStartAppFromLauncher

onStartAppFromLauncher(startingWindowTarget: WindowAnimationTarget,finishCallback: WindowAnimationFinishedCallback): void

从桌面启动应用时的回调。

参数名类型必填说明
startingWindowTargetWindowAnimationTarget动画目标窗口。
finishCallbackWindowAnimationFinishedCallback动画完成后的回调。

onStartAppFromRecent

onStartAppFromRecent(startingWindowTarget: WindowAnimationTarget,finishCallback:WindowAnimationFinishedCallback): void

从最近任务列表启动应用时的回调。

参数名类型必填说明
startingWindowTargetWindowAnimationTarget动画目标窗口。
finishCallbackWindowAnimationFinishedCallback动画完成后的回调。

onStartAppFromOther

onStartAppFromOther(startingWindowTarget: WindowAnimationTarget,finishCallback: WindowAnimationFinishedCallback): void

从除了桌面和最近任务列表以外其他地方启动应用时的回调。

参数名类型必填说明
startingWindowTargetWindowAnimationTarget动画目标窗口。
finishCallbackWindowAnimationFinishedCallback动画完成后的回调。

onAppTransition

onAppTransition(fromWindowTarget: WindowAnimationTarget, toWindowTarget: WindowAnimationTarget,finishCallback: WindowAnimationFinishedCallback): void

应用转场时的回调。

参数名类型必填说明
fromWindowTargetWindowAnimationTarget转场前的动画窗口。
toWindowTargetWindowAnimationTarget转场后的动画窗口。
finishCallbackWindowAnimationFinishedCallback动画完成后的回调。

onMinimizeWindow

onMinimizeWindow(minimizingWindowTarget: WindowAnimationTarget,finishCallback: WindowAnimationFinishedCallback): void

最小化窗口时的回调。

参数名类型必填说明
minimizingWindowTargetWindowAnimationTarget动画目标窗口。
finishCallbackWindowAnimationFinishedCallback动画完成后的回调。

onCloseWindow

onCloseWindow(closingWindowTarget: WindowAnimationTarget,finishCallback: WindowAnimationFinishedCallback): void

关闭窗口时的回调。

参数名类型必填说明
closingWindowTargetWindowAnimationTarget动画目标窗口。
finishCallbackWindowAnimationFinishedCallback动画完成后的回调。

onScreenUnlock

onScreenUnlock(finishCallback: WindowAnimationFinishedCallback): void

屏幕解锁时的回调。

参数名类型必填说明
finishCallbackWindowAnimationFinishedCallback动画完成后的回调。

onWindowAnimationTargetsUpdate

onWindowAnimationTargetsUpdate(fullScreenWindowTarget: WindowAnimationTarget, floatingWindowTargets: Array): void

动画目标窗口更新时的回调

参数名类型必填说明
fullScreenWindowTargetWindowAnimationTarget全屏状态的动画目标窗口。
floatingWindowTargetsArray WindowAnimationTarget悬浮状态的动画目标窗口

WindowAnimationFinishedCallback

动画完成后的回调。
onAnimationFinish
onAnimationFinish():void

结束本次动画。

应用层使用

OpenHarmony 中应用层的窗口动画定义在 Launcher 系统应用中,由 Launcher 应用统一规范应用的窗口动画

WindowController 的实现见 WindowAnimationControllerImpl.ts,定义了 onStartAppFromLauncher、onStartAppFromRecent、onStartAppFromOther、onAppTransition、onMinimizeWindow、onCloseWindow、onScreenUnlock 等实现方式

import Prompt from '@ohos.prompt';
import windowAnimationManager from '@ohos.animation.windowAnimationManager';
import { CheckEmptyUtils } from '@ohos/common';
import { Log } from '@ohos/common';
import RemoteConstants from '../../constants/RemoteConstants';

const TAG = 'WindowAnimationControllerImpl';

class WindowAnimationControllerImpl implements windowAnimationManager.WindowAnimationController {
  onStartAppFromLauncher(startingWindowTarget: windowAnimationManager.WindowAnimationTarget,
                         finishCallback: windowAnimationManager.WindowAnimationFinishedCallback): void
  {
    Log.showInfo(TAG, `remote window animaion onStartAppFromLauncher`);
    this.setRemoteAnimation(startingWindowTarget, null, finishCallback, RemoteConstants.TYPE_START_APP_FROM_LAUNCHER);
    this.printfTarget(startingWindowTarget);
    finishCallback.onAnimationFinish();
  }

  onStartAppFromRecent(startingWindowTarget: windowAnimationManager.WindowAnimationTarget,
                       finishCallback: windowAnimationManager.WindowAnimationFinishedCallback): void {
    Log.showInfo(TAG, `remote window animaion onStartAppFromRecent`);
    this.setRemoteAnimation(startingWindowTarget, null, finishCallback, RemoteConstants.TYPE_START_APP_FROM_RECENT);
    this.printfTarget(startingWindowTarget);
    finishCallback.onAnimationFinish();
  }

  onStartAppFromOther(startingWindowTarget: windowAnimationManager.WindowAnimationTarget,
                      finishCallback: windowAnimationManager.WindowAnimationFinishedCallback): void {
    Log.showInfo(TAG, `remote window animaion onStartAppFromOther`);
    this.setRemoteAnimation(startingWindowTarget, null, finishCallback, RemoteConstants.TYPE_START_APP_FROM_OTHER);
    this.printfTarget(startingWindowTarget);
    finishCallback.onAnimationFinish();
  }

  onAppTransition(fromWindowTarget: windowAnimationManager.WindowAnimationTarget,
                  toWindowTarget: windowAnimationManager.WindowAnimationTarget,
                  finishCallback: windowAnimationManager.WindowAnimationFinishedCallback): void{
    Log.showInfo(TAG, `remote window animaion onAppTransition`);
    this.setRemoteAnimation(toWindowTarget, fromWindowTarget, finishCallback, RemoteConstants.TYPE_APP_TRANSITION);
    this.printfTarget(fromWindowTarget);
    this.printfTarget(toWindowTarget);
    finishCallback.onAnimationFinish();
  }

  onMinimizeWindow(minimizingWindowTarget: windowAnimationManager.WindowAnimationTarget,
                   finishCallback: windowAnimationManager.WindowAnimationFinishedCallback): void {
    Log.showInfo(TAG, `remote window animaion onMinimizeWindow`);
    this.setRemoteAnimation(null, minimizingWindowTarget, finishCallback, RemoteConstants.TYPE_MINIMIZE_WINDOW);
    this.printfTarget(minimizingWindowTarget);
    finishCallback.onAnimationFinish();
  }

  onCloseWindow(closingWindowTarget: windowAnimationManager.WindowAnimationTarget,
                finishCallback: windowAnimationManager.WindowAnimationFinishedCallback): void {
    Log.showInfo(TAG, `remote window animaion onCloseWindow`);
    this.setRemoteAnimation(null, closingWindowTarget, finishCallback, RemoteConstants.TYPE_CLOSE_WINDOW);
    this.printfTarget(closingWindowTarget);
    finishCallback.onAnimationFinish();
  }

  onScreenUnlock(finishCallback: windowAnimationManager.WindowAnimationFinishedCallback): void {
    Log.showInfo(TAG, `remote window animaion onScreenUnlock`);
    this.setRemoteAnimation(null, null, finishCallback, RemoteConstants.TYPE_SCREEN_UNLOCK);
    finishCallback.onAnimationFinish();
  }

  onWindowAnimationTargetsUpdate(fullScreenWindowTarget: windowAnimationManager.WindowAnimationTarget, floatingWindowTargets: Array<windowAnimationManager.WindowAnimationTarget>): void {}

  printfTarget(target: windowAnimationManager.WindowAnimationTarget): void {
    if (CheckEmptyUtils.isEmpty(target) || CheckEmptyUtils.isEmpty(target.windowBounds)) {
      Log.showInfo(TAG, `remote window animaion with invalid target`);
      return;
    }
    Log.showInfo(TAG, `remote window animaion bundleName: ${target.bundleName} abilityName: ${target.abilityName}`);
    Log.showInfo(TAG, `remote window animaion windowBounds left: ${target.windowBounds.left} top: ${target.windowBounds.top}
      width: ${target.windowBounds.width} height: ${target.windowBounds.height} radius: ${target.windowBounds.radius}`);
  }

  private setRemoteAnimation(startingWindowTarget: windowAnimationManager.WindowAnimationTarget,
                             closingWindowTarget: windowAnimationManager.WindowAnimationTarget,
                             finishCallback: windowAnimationManager.WindowAnimationFinishedCallback,
                             remoteAnimationType: number): void {
    if (!CheckEmptyUtils.isEmpty(startingWindowTarget)) {
      AppStorage.SetOrCreate('startingWindowTarget', startingWindowTarget);
    }

    if (!CheckEmptyUtils.isEmpty(closingWindowTarget)) {
      AppStorage.SetOrCreate('closingWindowTarget', closingWindowTarget);
    }

    if (!CheckEmptyUtils.isEmpty(finishCallback)) {
      AppStorage.SetOrCreate('remoteAnimationFinishCallback', finishCallback);
    }

    AppStorage.SetOrCreate('remoteAnimationType', remoteAnimationType);
  }
}

export default WindowAnimationControllerImpl

RemoteWindowWrapper.ets 中定义了具体的窗口动画实现效果(通过 animateTo 实现具体的动画效果)

calculateAppProperty(remoteVo: RemoteVo, finishCallback: windowAnimationManager.WindowAnimationFinishedCallback) {
  Log.showDebug(TAG, `calculateAppProperty ${remoteVo.remoteAnimationType}`);
  if (remoteVo.remoteAnimationType == RemoteConstants.TYPE_START_APP_FROM_LAUNCHER) {
    Trace.start(Trace.CORE_METHOD_START_APP_ANIMATION);
    const callback = Object.assign(finishCallback);
    const count = remoteVo.count;
    localEventManager.sendLocalEventSticky(EventConstants.EVENT_ANIMATION_START_APPLICATION, null);
    animateTo({
      duration: 180,
      delay: 100,
      curve: Curve.Friction,
      onFinish: () => {
      }
    }, () => {
      remoteVo.startAppIconWindowAlpha = 0.0;
      remoteVo.remoteWindowWindowAlpha = 1.0;
    })

    animateTo({
      duration: 500,
      // @ts-ignore
      curve: curves.springMotion(0.32, 0.99, 0),
      onFinish: () => {
        callback.onAnimationFinish();
        Trace.end(Trace.CORE_METHOD_START_APP_ANIMATION);
        const startCount: number = AppStorage.Get(remoteVo.remoteWindowKey);
        Log.showDebug(TAG, `calculateAppProperty ${remoteVo.remoteAnimationType}, count: ${count}, startCount: ${startCount}`);
        if (startCount === count || count == 0) {
          this.removeRemoteWindowFromList(remoteVo.remoteWindowKey);
          AppStorage.SetOrCreate(remoteVo.remoteWindowKey, 0);
        }
      }
    }, () => {
      remoteVo.remoteWindowScaleX = 1.0;
      remoteVo.remoteWindowScaleY = 1.0;
      remoteVo.remoteWindowTranslateX = 0.0;
      remoteVo.remoteWindowTranslateY = 0.0;
      remoteVo.startAppIconScaleX = remoteVo.mScreenWidth / remoteVo.iconInfo?.appIconSize;
      remoteVo.startAppIconTranslateX = remoteVo.mScreenWidth / 2 - remoteVo.iconInfo?.appIconPositionX - remoteVo.iconInfo?.appIconSize / 2;
      remoteVo.remoteWindowRadius = 0;
      if (remoteVo.startAppTypeFromPageDesktop === CommonConstants.OVERLAY_TYPE_CARD) {
        remoteVo.startAppIconScaleY = remoteVo.mScreenHeight / remoteVo.iconInfo?.appIconHeight;
        remoteVo.startAppIconTranslateY = remoteVo.mScreenHeight / 2 + px2vp(remoteVo.target.windowBounds.top) - remoteVo.iconInfo?.appIconPositionY - remoteVo.iconInfo?.appIconHeight / 2;
      } else {
        remoteVo.startAppIconScaleY = remoteVo.mScreenHeight / remoteVo.iconInfo?.appIconSize;
        remoteVo.startAppIconTranslateY = remoteVo.mScreenHeight / 2 + px2vp(remoteVo.target.windowBounds.top) - remoteVo.iconInfo?.appIconPositionY - remoteVo.iconInfo?.appIconSize / 2;
      }
    })
  } else if (remoteVo.remoteAnimationType  == RemoteConstants.TYPE_MINIMIZE_WINDOW) {
    Trace.start(Trace.CORE_METHOD_CLOSE_APP_ANIMATION);
    const res = remoteVo.calculateCloseAppProperty();
    const callback = Object.assign(finishCallback);
    const count = remoteVo.count;
    localEventManager.sendLocalEventSticky(EventConstants.EVENT_ANIMATION_CLOSE_APPLICATION, null);
    animateTo({
      duration: 700,
      // @ts-ignore
      curve: curves.springMotion(0.40, 0.99, 0),
      onFinish: () => {
        callback.onAnimationFinish();
        Trace.end(Trace.CORE_METHOD_CLOSE_APP_ANIMATION);
        const startCount: number = AppStorage.Get(remoteVo.remoteWindowKey);
        Log.showDebug(TAG, `calculateAppProperty ${remoteVo.remoteAnimationType}, count: ${count}, startCount: ${startCount}`);
        if (startCount === count || count == 0) {
          this.removeRemoteWindowFromList(remoteVo.remoteWindowKey);
          AppStorage.SetOrCreate(remoteVo.remoteWindowKey, 0);
        }
      }
    }, () => {
      remoteVo.remoteWindowScaleX = 1 / res.closeAppCalculateScaleX;
      remoteVo.remoteWindowScaleY = 1 / res.closeAppCalculateScaleY;
      remoteVo.remoteWindowTranslateX = res.closeAppCalculateTranslateX;
      remoteVo.remoteWindowTranslateY = res.closeAppCalculateTranslateY;

      remoteVo.startAppIconScaleX = 1.0;
      remoteVo.startAppIconScaleY = 1.0;
      remoteVo.startAppIconTranslateX = 0.0;
      remoteVo.startAppIconTranslateY = 0.0;
      remoteVo.remoteWindowRadius = 96;
    })

    animateTo({
      duration: 140,
      delay: 350,
      curve: Curve.Friction,
      onFinish: () => {
      }
    }, () => {
      remoteVo.startAppIconWindowAlpha = 1.0;
      remoteVo.remoteWindowWindowAlpha = 0;
    })
  } else if (remoteVo.remoteAnimationType  == RemoteConstants.TYPE_CLOSE_WINDOW) {
  } else if (remoteVo.remoteAnimationType  == RemoteConstants.TYPE_APP_TRANSITION) {
    const callback = Object.assign(finishCallback);
    animateTo({
      duration: 500,
      curve: Curve.Friction,
      onFinish: () => {
        callback.onAnimationFinish();
        this.removeRemoteWindowFromList(remoteVo.remoteWindowKey);
      }
    }, () => {
      remoteVo.remoteWindowRadius = 0;
      remoteVo.remoteWindowTranslateX = 0;
      remoteVo.fromRemoteWindowTranslateX = px2vp(remoteVo.fromWindowTarget?.windowBounds.left - remoteVo.fromWindowTarget?.windowBounds.width);
    })

    animateTo({
      duration: 150,
      curve: Curve.Friction,
      onFinish: () => {
        callback.onAnimationFinish();
        this.removeRemoteWindowFromList(remoteVo.remoteWindowKey);
      }
    }, () => {
      remoteVo.remoteWindowScaleX = 0.9
      remoteVo.remoteWindowScaleY = 0.9
      remoteVo.fromRemoteWindowScaleX = 0.9
      remoteVo.fromRemoteWindowScaleY = 0.9
    })

    animateTo({
      duration: 350,
      delay: 150,
      curve: Curve.Friction,
      onFinish: () => {
        callback.onAnimationFinish();
        this.removeRemoteWindowFromList(remoteVo.remoteWindowKey);
      }
    }, () => {
      remoteVo.remoteWindowScaleX = 1.0
      remoteVo.remoteWindowScaleY = 1.0
      remoteVo.fromRemoteWindowScaleX = 1.0
      remoteVo.fromRemoteWindowScaleY = 1.0
    })
  }
}

注:若想修改系统的窗口动画效果,可通过修改对应的动画实现

底层实现

窗口动画的底层实现具体见:foundation\window\window_manager\wmserver\src\remote_animation.cpp

WMError RemoteAnimation::SetWindowAnimationController(const sptr<RSIWindowAnimationController>& controller)
{
    WLOGFI("RSWindowAnimation: set window animation controller!");
    if (!isRemoteAnimationEnable_) {
        WLOGE("RSWindowAnimation: failed to set window animation controller, remote animation is not enabled");
        return WMError::WM_ERROR_NO_REMOTE_ANIMATION;
    }
    if (controller == nullptr) {
        WLOGFE("RSWindowAnimation: failed to set window animation controller, controller is null!");
        return WMError::WM_ERROR_NULLPTR;
    }

    if (windowAnimationController_ != nullptr) {
        WLOGFI("RSWindowAnimation: maybe user switch!");
    }

    windowAnimationController_ = controller;
    return WMError::WM_OK;
}

RSIWindowAnimationController 具体定义见:foundation\graphic\graphic_2d\interfaces\kits\napi\graphic\animation\window_animation_manager\rs_window_animation_controller.cpp

void RSWindowAnimationController::HandleOnStartApp(StartingAppType type,
    const sptr<RSWindowAnimationTarget>& startingWindowTarget,
    const sptr<RSIWindowAnimationFinishedCallback>& finishedCallback)
{
    WALOGD("Handle on start app.");
    NativeValue* argv[] = {
        RSWindowAnimationUtils::CreateJsWindowAnimationTarget(engine_, startingWindowTarget),
        RSWindowAnimationUtils::CreateJsWindowAnimationFinishedCallback(engine_, finishedCallback),
    };

    switch (type) {
        case StartingAppType::FROM_LAUNCHER:
            CallJsFunction("onStartAppFromLauncher", argv, ARGC_TWO);
            break;
        case StartingAppType::FROM_RECENT:
            CallJsFunction("onStartAppFromRecent", argv, ARGC_TWO);
            break;
        case StartingAppType::FROM_OTHER:
            CallJsFunction("onStartAppFromOther", argv, ARGC_TWO);
            break;
        default:
            WALOGE("Unknow starting app type.");
            break;
    }
}

void RSWindowAnimationController::HandleOnAppTransition(const sptr<RSWindowAnimationTarget>& fromWindowTarget,
    const sptr<RSWindowAnimationTarget>& toWindowTarget,
    const sptr<RSIWindowAnimationFinishedCallback>& finishedCallback)
{
    WALOGD("Handle on app transition.");
    NativeValue* argv[] = {
        RSWindowAnimationUtils::CreateJsWindowAnimationTarget(engine_, fromWindowTarget),
        RSWindowAnimationUtils::CreateJsWindowAnimationTarget(engine_, toWindowTarget),
        RSWindowAnimationUtils::CreateJsWindowAnimationFinishedCallback(engine_, finishedCallback),
    };
    CallJsFunction("onAppTransition", argv, ARGC_THREE);
}
...

通过 CallJsFunction 实现具体的 napi 调用,实现具体的动画效果

为了能让大家更好的学习鸿蒙(HarmonyOS NEXT)开发技术,这边特意整理了《鸿蒙开发学习手册》(共计890页),希望对大家有所帮助:https://qr21.cn/FV7h05

《鸿蒙开发学习手册》:

如何快速入门:https://qr21.cn/FV7h05

  1. 基本概念
  2. 构建第一个ArkTS应用
  3. ……

开发基础知识:https://qr21.cn/FV7h05

  1. 应用基础知识
  2. 配置文件
  3. 应用数据管理
  4. 应用安全管理
  5. 应用隐私保护
  6. 三方应用调用管控机制
  7. 资源分类与访问
  8. 学习ArkTS语言
  9. ……

基于ArkTS 开发:https://qr21.cn/FV7h05

  1. Ability开发
  2. UI开发
  3. 公共事件与通知
  4. 窗口管理
  5. 媒体
  6. 安全
  7. 网络与链接
  8. 电话服务
  9. 数据管理
  10. 后台任务(Background Task)管理
  11. 设备管理
  12. 设备使用信息统计
  13. DFX
  14. 国际化开发
  15. 折叠屏系列
  16. ……

鸿蒙开发面试真题(含参考答案):https://qr18.cn/F781PH

鸿蒙开发面试大盘集篇(共计319页):https://qr18.cn/F781PH

1.项目开发必备面试题
2.性能优化方向
3.架构方向
4.鸿蒙开发系统底层方向
5.鸿蒙音视频开发方向
6.鸿蒙车载开发方向
7.鸿蒙南向开发方向

  • 23
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值