一、轉場動畫概述

轉場動畫是指對將要出現或消失的組件做動畫,對始終出現的組件做動畫應使用屬性動畫。轉場動畫主要為了讓開發者從繁重的消失節點管理中解放出來,如果用屬性動畫做組件轉場,開發者需要在動畫結束回調中刪除組件節點。同時,由於動畫結束前已經刪除的組件節點可能會重新出現,還需要在結束回調中增加對節點狀態的判斷。

轉場動畫分為基礎轉場和高級模板化轉場,有如下幾類:

  • 出現/消失轉場:對新增、消失的控件實現動畫效果,是通用的基礎轉場效果。
  • 導航轉場:頁面的路由轉場方式,對應一個界面消失,另外一個界面出現的動畫效果,如設置應用一級菜單切換到二級界面。
  • 模態轉場:新的界面覆蓋在舊的界面之上的動畫,舊的界面不消失,新的界面出現,如彈框就是典型的模態轉場動畫。
  • 共享元素轉場 (一鏡到底):共享元素轉場是一種界面切換時對相同或者相似的元素做的一種位置和大小匹配的過渡動畫效果。
  • 頁面轉場動畫(不推薦):頁面的路由轉場方式,可以通過在pageTransition函數中自定義頁面入場和頁面退場的轉場動效。為了實現更好的轉場效果,推薦使用導航轉場和模態轉場。
  • 旋轉屏動畫:旋轉屏動畫主要分為兩類:佈局切換的旋轉屏動畫和透明度變化的旋轉屏動畫,旨在實現屏幕顯示方向變化時的自然過渡。

二、出現/消失轉場

transition是基礎的組件轉場接口,用於實現一個組件出現或者消失時的動畫效果。可以通過TransitionEffect對象的組合使用,定義出各式效果。

轉場效果

説明

動畫

IDENTITY

禁用轉場效果。

無。

OPACITY

默認的轉場效果,透明度轉場。

出現時透明度從0到1,消失時透明度從1到0。

SLIDE

滑動轉場效果。

出現時從窗口左側滑入,消失時從窗口右側滑出。

translate

通過設置組件平移創建轉場效果。

出現時為translate接口設置的值到默認值0,消失時為默認值0到translate接口設置的值。

rotate

通過設置組件旋轉創建轉場效果。

出現時為rotate接口設置的值到默認值0,消失時為默認值0到rotate接口設置的值。

opacity

通過設置透明度參數創建轉場效果。

出現時為opacity設置的值到默認透明度1,消失時為默認透明度1到opacity設置的值。

move

通過TransitionEdge創建從窗口哪條邊緣出來的效果。

出現時從TransitionEdge方向滑入,消失時滑出到TransitionEdge方向。

asymmetric

通過此方法組合非對稱的出現消失轉場效果。

- appear:出現轉場的效果。

- disappear:消失轉場的效果。

出現時採用appear設置的TransitionEffect出現效果,消失時採用disappear設置的TransitionEffect消失效果。

combine

組合其他TransitionEffect。

組合其他TransitionEffect,一起生效。

animation

定義轉場效果的動畫參數:

- 如果不定義會跟隨animateTo的動畫參數。

- 不支持通過控件的animation接口配置動畫參數。

- TransitionEffect中animation的onFinish不生效。

調用順序時從上往下,上面TransitionEffect的animation也會作用到下面TransitionEffect。

  1. 創建TransitionEffect。
// 出現時會是所有轉場效果的出現效果疊加,消失時會是所有消失轉場效果的疊加
// 説明各個effect跟隨的動畫參數
private effect: object =
  TransitionEffect.OPACITY // 創建了透明度轉場效果,這裏沒有調用animation接口,會跟隨animateTo的動畫參數
    // 通過combine方法,添加縮放轉場效果,並指定了springMotion(0.6, 1.2)曲線
    .combine(TransitionEffect.scale({ x: 0, y: 0 }).animation({ curve: curves.springMotion(0.6, 1.2) }))
    // 添加旋轉轉場效果,這裏的動畫參數會跟隨上面的TransitionEffect,也就是springMotion(0.6, 1.2)
    .combine(TransitionEffect.rotate({ angle: 90 }))
    // 添加平移轉場效果,動畫參數會跟隨其之上帶animation的TransitionEffect,也就是springMotion(0.6, 1.2)
    .combine(TransitionEffect.translate({ x: 150, y: 150 }))
    // 添加move轉場效果,並指定了springMotion曲線
    .combine(TransitionEffect.move(TransitionEdge.END)).animation({curve: curves.springMotion()})
    // 添加非對稱的轉場效果,由於這裏沒有設置animation,會跟隨上面的TransitionEffect的animation效果,也就是springMotion
    .combine(TransitionEffect.asymmetric(TransitionEffect.scale({ x: 0, y: 0 }), TransitionEffect.rotate({ angle: 90 })));
  1. 將轉場效果通過transition接口設置到組件。
Text('test')
  .transition(this.effect)
  1. 新增或者刪除組件觸發轉場。
@State isPresent: boolean = true;
// ...
if (this.isPresent) {
  Text('test')
    .transition(this.effect)
}
// ...
// 控制新增或者刪除組件
// 方式一:將控制變量放到animateTo閉包內,未通過animation接口定義動畫參數的TransitionEffect將跟隨animateTo的動畫參數
this.getUIContext()?.animateTo({ curve: curves.springMotion() }, () => {
  this.isPresent = false;
})

// 方式二:直接控制刪除或者新增組件,動畫參數由TransitionEffect的animation接口配置
this.isPresent = false;

完整的示例代碼和效果如下,示例中採用直接刪除或新增組件的方式觸發轉場,也可以替換為在animateTo閉包內改變控制變量觸發轉場。

效果圖

HarmonyOS:轉場動畫_HarmonyOS

示例代碼

import { curves } from '@kit.ArkUI';

@Entry
@Component
struct TransitionEffectDemo {
  @State isPresent: boolean = false;
  // 第一步,創建TransitionEffect
  private effect: TransitionEffect =
    // 創建默認透明度轉場效果,並指定了springMotion(0.6, 0.8)曲線
    TransitionEffect.OPACITY.animation({
      curve: curves.springMotion(0.6, 0.8)
    })// 通過combine方法,這裏的動畫參數會跟隨上面的TransitionEffect,也就是springMotion(0.6, 0.8)
      .combine(TransitionEffect.scale({
        x: 0,
        y: 0
      }))// 添加旋轉轉場效果,這裏的動畫參數會跟隨上面帶animation的TransitionEffect,也就是springMotion(0.6, 0.8)
      .combine(TransitionEffect.rotate({ angle: 90 }))// 添加平移轉場效果,這裏的動畫參數使用指定的springMotion()
      .combine(TransitionEffect.translate({ y: 150 })
        .animation({ curve: curves.springMotion() }))// 添加move轉場效果,這裏的動畫參數會跟隨上面的TransitionEffect,也就是springMotion()
      .combine(TransitionEffect.move(TransitionEdge.END));

  build() {
    Stack() {
      if (this.isPresent) {
        Column() {
          Text('ArkUI')
            .fontWeight(FontWeight.Bold)
            .fontSize(20)
            .fontColor(Color.White)
        }
        .justifyContent(FlexAlign.Center)
        .width(150)
        .height(150)
        .borderRadius(10)
        .backgroundColor(0xf56c6c)
        // 第二步:將轉場效果通過transition接口設置到組件
        .transition(this.effect)
      }

      // 邊框
      Column()
        .width(155)
        .height(155)
        .border({
          width: 5,
          radius: 10,
          color: Color.Black
        })

      // 第三步:新增或者刪除組件觸發轉場,控制新增或者刪除組件
      Button('Click')
        .margin({ top: 320 })
        .onClick(() => {
          this.isPresent = !this.isPresent;
        })
    }
    .width('100%')
    .height('60%')
  }
}

對多個組件添加轉場效果時,可以在animation動畫參數中配置不同的delay值,實現組件漸次出現消失的效果:

效果圖

HarmonyOS:轉場動畫_鴻蒙_02

示例代碼

const ITEM_COUNTS = 9;
const ITEM_COLOR = '#ED6F21';
const INTERVAL = 30;
const DURATION = 300;

@Entry
@Component
struct Index1 {
  @State isGridShow: boolean = false;
  private dataArray: number[] = new Array(ITEM_COUNTS);

  aboutToAppear(): void {
    for (let i = 0; i < ITEM_COUNTS; i++) {
      this.dataArray[i] = i;
    }
  }

  build() {
    Stack() {
      if (this.isGridShow) {
        Grid() {
          ForEach(this.dataArray, (item: number, index: number) => {
            GridItem() {
              Stack() {
                Text((item + 1).toString())
              }
              .size({ width: 50, height: 50 })
              .backgroundColor(ITEM_COLOR)
              .transition(TransitionEffect.OPACITY
                .combine(TransitionEffect.scale({ x: 0.5, y: 0.5 }))// 對每個方格的轉場添加delay,實現組件的漸次出現消失效果
                .animation({ duration: DURATION, curve: Curve.Friction, delay: INTERVAL * index }))
              .borderRadius(10)
            }
            // 消失時,如果不對方格的所有父控件添加轉場效果,則方格的消失轉場不會生效
            // 此處讓方格的父控件在出現消失轉場時一直以0.99的透明度顯示,使得方格的轉場效果不受影響
            .transition(TransitionEffect.opacity(0.99))
          }, (item: number) => item.toString())
        }
        .columnsTemplate('1fr 1fr 1fr')
        .rowsGap(15)
        .columnsGap(15)
        .size({ width: 180, height: 180 })
        // 消失時,如果不對方格的所有父控件添加轉場效果,則方格的消失轉場不會生效
        // 此處讓父控件在出現消失轉場時一直以0.99的透明度顯示,使得方格的轉場效果不受影響
        .transition(TransitionEffect.opacity(0.99))
      }
    }
    .size({ width: '100%', height: '100%' })
    .onClick(() => {
      this.getUIContext()?.animateTo({
        duration: DURATION + INTERVAL * (ITEM_COUNTS - 1),
        curve: Curve.Friction
      }, () => {
        this.isGridShow = !this.isGridShow;
      })
    })
  }
}

三、旋轉屏動畫

旋轉屏動畫主要分為兩類:佈局切換的旋轉屏動畫透明度變化的旋轉屏動畫,旨在實現屏幕顯示方向變化時的自然過渡。佈局切換的旋轉屏動畫實現較為簡便,例如在module.json5中配置自動旋轉(或設置窗口顯示方向)即可實現。而透明度變化的旋轉屏動畫則需在module.json5配置的基礎上,預備兩套視圖,在屏幕旋轉時,通過視圖切換,使消失的視圖呈現漸隱效果,新出現的視圖則漸顯,從而營造流暢的視覺體驗。

3.1 佈局切換的旋轉屏動畫

佈局切換時的旋轉屏動畫,是在屏幕顯示方向改變時,為窗口與應用視圖同步旋轉而設計的大小和位置過渡動畫。這種佈局切換的旋轉屏動畫是系統默認的,便於開發者實現。當屏幕顯示方向變化時,系統會生成窗口旋轉動畫,並自動調整窗口大小以匹配旋轉後的尺寸。在此過程中,窗口會通知對應的應用,要求其根據新的窗口大小重新佈局,產生與窗口旋轉動畫參數相同的佈局動畫。

  • 切換屏幕方向即可實現佈局切換的旋轉屏動畫效果。
效果圖

HarmonyOS:轉場動畫_HarmonyOS_03

@Entry
@Component
struct Rotation1 {

  build() {
    Stack() {
      Image($r('app.media.mount'))
        .position({ x: 100, y: 100 })
        .size({ width: 150, height: 100 })
        .id('image1')
    }
    .backgroundColor(Color.White)
    .size({ width: '100%', height: '100%' })
  }
}

需要在項目的module.json5文件中的abilities列表裏添加"orientation",指定為"auto_rotation"。
佈局切換的旋轉屏動畫,會對同步旋轉的窗口與應用視圖做大小和位置的過渡。

"orientation": "auto_rotation",
3.2 透明度變化的旋轉屏動畫

透明度變化的旋轉屏動畫在屏幕顯示方向變化時啓用,當窗口進行旋轉動畫時,為旋轉過程中新增或刪除的組件添加默認透明度轉場,以實現組件的優雅出現和消失。此功能通過監聽窗口旋轉事件,在事件中切換組件的視圖效果,如果消失視圖的根節點和新出現視圖的根節點未設置轉場效果,會為其自動添加默認透明度轉場(即TransitionEffect.OPACITY),展現出透明度的漸隱和漸顯效果。

效果圖豎屏

HarmonyOS:轉場動畫_HarmonyOS_04


旋轉為橫屏

HarmonyOS:轉場動畫_HarmonyOS_05

示例代碼

import { display } from '@kit.ArkUI';

@Entry
@Component
struct Rotation2 {
  // 獲取通過監聽窗口的windowsSizeChange事件得到的屏幕顯示方向
  @StorageLink('orientation') myOrientation: display.Orientation = display.Orientation.PORTRAIT;

  build() {
    Stack() {

      // 當屏幕顯示方向變化時,切換組件的視圖效果
      if (this.myOrientation == display.Orientation.PORTRAIT ||
        this.myOrientation == display.Orientation.PORTRAIT_INVERTED) {
        Image($r('app.media.Equipment0'))
          .size({ width: 158, height: 73 })
          .id('image1')

        // 開發者也可以通過自行設置transition的TransitionEffect.OPACITY轉場效果來實現旋轉屏動畫的透明度變化
        // .transition(TransitionEffect.OPACITY)
      } else {
        Image($r('app.media.Equipment1'))
          .position({ x: 200, y: 200 })
          .size({ width: 158, height: 73 })
          .id('image2')

        // 開發者也可以通過自行設置transition的TransitionEffect.OPACITY來實現旋轉屏動畫的透明度變化
        // .transition(TransitionEffect.OPACITY)
      }
    }
    .backgroundColor(Color.White)
    .size({ width: '100%', height: '100%' })
  }
}

監聽窗口旋轉的同步事件windowsSizeChange來實現視圖的切換。例如可在EntryAbility.ets文件的onWindowStageCreate方法中添加處理邏輯以獲取屏幕的顯示方向。

onWindowStageCreate(windowStage: window.WindowStage): void {

    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');

    let mainWindow: window.Window;
    try {
      mainWindow = windowStage.getMainWindowSync();
      let displayClass: display.Display = display.getDefaultDisplaySync();
      AppStorage.setOrCreate('orientation', displayClass.orientation);
      // 監聽窗口的windowsSizeChange事件,旋轉屏時會觸發該事件
      mainWindow.on('windowSizeChange', (data) => {
        console.info('Succeeded in enabling the listener for window size changes. Data: ' + JSON.stringify(data));
        let displayClass: display.Display | null = null;
        try {
          displayClass = display.getDefaultDisplaySync();
          console.info('display orientation is ' + JSON.stringify(displayClass.orientation));
          // 獲取屏幕的顯示方向
          AppStorage.set('orientation', displayClass.orientation);
        } catch {
          return;
        }
      })
    } catch {
      hilog.error(0x0000, 'testTag', '%{public}s', 'error');
      return;
    }

    windowStage.loadContent('pages/Rotation2', (err) => {
      if (err.code) {
        hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
        return;
      }
      hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.');
    });
}

需要在項目的 module.json5 文件中的 abilities 列表裏添加 "orientation",指定為 "auto_rotation"。

"orientation": "auto_rotation",

透明度變化的旋轉屏動畫,會對窗口做大小和位置的過渡,並同時對應用視圖做切換過渡,且為消失隱藏的應用視圖做漸隱效果,對新出現的視圖做漸顯的效果。