场景动效类型互动卡片开发指导
==============

本文档提供了场景动效类型互动卡片的开发指导，包括卡片非激活态和激活态UI界面开发、卡片配置文件开发。

#### 接口说明

场景动效类型互动卡片关键接口如下表所示。

**表1** 主要接口

| 接口名 | 描述 |
| :-- | :-- |
| [onLiveFormCreate(liveFormInfo: LiveFormInfo, session: UIExtensionContentSession): void](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-app-form-liveformextensionability#onliveformcreate) | 互动卡片界面对象创建的回调函数。 |
| [onLiveFormDestroy(liveFormInfo: LiveFormInfo): void](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-app-form-liveformextensionability#onliveformdestroy) | 互动卡片界面对象销毁、资源清理的回调函数。 |
| [LiveFormExtensionContext](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-application-liveformextensioncontext) | LiveFormExtensionAbility的上下文，继承自ExtensionContext。 |
| [startAbilityByLiveForm(want: Want): Promise<void>](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-application-liveformextensioncontext#startabilitybyliveform) | 拉起互动卡片提供方（应用）的页面。 |
| [formProvider.requestOverflow(formId: string, overflowInfo: formInfo.OverflowInfo): Promise<void>](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-app-form-formprovider#formproviderrequestoverflow20) | 卡片提供方发起互动卡片动效请求。 |
| [formProvider.cancelOverflow(formId: string): Promise<void>](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-app-form-formprovider#formprovidercanceloverflow20) | 卡片提供方发起取消互动卡片动效请求。 |
| [formProvider.getFormRect(formId: string): Promise<formInfo.Rect>](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-app-form-formprovider#formprovidergetformrect20) | 卡片提供方查询卡片位置、尺寸。 |

#### 开发流程

#### \[h2\]卡片激活态UI开发

1.  创建互动卡片
    
    通过[LiveFormExtensionAbility](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-app-form-liveformextensionability)创建互动卡片，创建时加载互动卡片页面。
    
    // entry/src/main/ets/myliveformextensionability/MyLiveFormExtensionAbility.ets
    import { formInfo, LiveFormInfo, LiveFormExtensionAbility } from '@kit.FormKit';
    import { UIExtensionContentSession } from '@kit.AbilityKit';
    import { hilog } from '@kit.PerformanceAnalysisKit';
    
    const DOMAIN = 0x0000;
    
    export default class MyLiveFormExtensionAbility extends LiveFormExtensionAbility {
      onLiveFormCreate(liveFormInfo: LiveFormInfo, session: UIExtensionContentSession) {
        let storage: LocalStorage = new LocalStorage();
        storage.setOrCreate('context', this.context);
        storage.setOrCreate('session', session);
        let formId: string = liveFormInfo.formId;
        storage.setOrCreate('formId', formId);
    
        // 获取卡片圆角信息
        let borderRadius: number = liveFormInfo.borderRadius;
        storage.setOrCreate('borderRadius', borderRadius);
    
        // liveFormInfo.rect字段表示非激活态卡片组件相对激活态UI的位置和尺寸信息
        let formRect: formInfo.Rect = liveFormInfo.rect;
        storage.setOrCreate('formRect', formRect);
        hilog.info(DOMAIN, 'testTag', \`MyLiveFormExtensionAbility onSessionCreate formId: ${formId}\` +
          \`, borderRadius: ${borderRadius}, formRectInfo: ${JSON.stringify(formRect)}\`);
    
        // 加载互动页面
        session.loadContent('myliveformextensionability/pages/MyLiveFormPage', storage);
      }
    
      onLiveFormDestroy(liveFormInfo: LiveFormInfo) {
        hilog.info(DOMAIN, 'testTag', \`MyLiveFormExtensionAbility onDestroy\`);
      }
    };
    
2.  实现互动卡片页面
    
    // entry/src/main/ets/myliveformextensionability/pages/MyLiveFormPage.ets
    import { formInfo, formProvider } from '@kit.FormKit';
    import { BusinessError } from '@kit.BasicServicesKit';
    import { common } from '@kit.AbilityKit';
    // Constants实现参考“互动卡片动效工具函数实现”小节
    import { Constants } from '../../common/Constants';
    import { hilog } from '@kit.PerformanceAnalysisKit';
    
    const ANIMATION\_RECT\_SIZE: number = 100;
    const END\_SCALE: number = 1.5;
    const END\_TRANSLATE: number = -300;
    const DOMAIN = 0x0000;
    
    @Entry
    @Component
    struct MyLiveFormPage {
      @State columnScale: number = 1.0;
      @State columnTranslate: number = 0.0;
      private uiContext: UIContext | undefined = undefined;
      private storageForMyLiveFormPage: LocalStorage | undefined = undefined;
      private formId: string | undefined = undefined;
      private formRect: formInfo.Rect | undefined = undefined;
      private formBorderRadius: number | undefined = undefined;
      private liveFormContext: common.LiveFormExtensionContext | undefined = undefined;
    
      aboutToAppear(): void {
        this.uiContext = this.getUIContext();
        if (!this.uiContext) {
          hilog.error(DOMAIN, 'testTag', 'no uiContext');
          return;
        }
        this.initParams();
      }
    
      private initParams(): void {
        this.storageForMyLiveFormPage = this.uiContext?.getSharedLocalStorage();
        this.formId = this.storageForMyLiveFormPage?.get<string>('formId');
        this.formRect = this.storageForMyLiveFormPage?.get<formInfo.Rect>('formRect');
        this.formBorderRadius = this.storageForMyLiveFormPage?.get<number>('borderRadius');
        this.liveFormContext = this.storageForMyLiveFormPage?.get<common.LiveFormExtensionContext>('context');
      }
    
      // 执行动效
      private runAnimation(): void {
        this.uiContext?.animateTo({
          duration: Constants.OVERFLOW\_DURATION,
          curve: Curve.Ease
        }, () => {
          this.columnScale = END\_SCALE;
          this.columnTranslate = END\_TRANSLATE;
        });
      }
    
      private startAbilityByLiveForm(): void {
        try {
          // 请开发者替换为实际的want信息
          this.liveFormContext?.startAbilityByLiveForm({
            bundleName: 'com.samples.formlivedemo',
            abilityName: 'EntryAbility',
          })
            .then(() => {
              hilog.info(DOMAIN, 'testTag', 'startAbilityByLiveForm succeed');
            })
            .catch((err: BusinessError) => {
              hilog.error(DOMAIN, 'testTag',
                \`startAbilityByLiveForm failed, code is ${err?.code}, message is ${err?.message}\`);
            });
        } catch (e) {
          hilog.error(DOMAIN, 'testTag', \`startAbilityByLiveForm failed, code is ${e?.code}, message is ${e?.message}\`);
        }
      }
    
      build() {
        Stack({ alignContent: Alignment.TopStart }) {
          // 背景组件和普通卡片一样大
          Column()
            .width(this.formRect ? this.formRect.width : 0)
            .height(this.formRect ? this.formRect.height : 0)
            .offset({
              x: this.formRect ? this.formRect.left : 0,
              y: this.formRect ? this.formRect.top : 0,
            })
            .borderRadius(this.formBorderRadius ? this.formBorderRadius : 0)
            .backgroundColor('#2875F5')
          Stack() {
            this.buildContent();
          }
          .width('100%')
          .height('100%')
        }
        .width('100%')
        .height('100%')
        .onClick(() => {
          hilog.info(DOMAIN, 'testTag', 'MyLiveFormPage click to start ability');
          if (!this.liveFormContext) {
            hilog.info(DOMAIN, 'testTag', 'MyLiveFormPage liveFormContext is empty');
            return;
          }
          this.startAbilityByLiveForm();
        })
      }
    
      @Builder
      buildContent() {
        Stack()
          .width(ANIMATION\_RECT\_SIZE)
          .height(ANIMATION\_RECT\_SIZE)
          .backgroundColor(Color.White)
          .scale({
            x: this.columnScale,
            y: this.columnScale,
          })
          .translate({
            y: this.columnTranslate
          })
          .onAppear(() => {
            // 在页面出现时执行动效
            this.runAnimation();
          })
        // $r('app.string.button\_cancel')需要在相应的资源文件string.json中定义
        Button($r('app.string.button\_cancel'))
          .backgroundColor(Color.Grey)
          .onClick(() => {
            if (!this.formId) {
              hilog.info(DOMAIN, 'testTag', 'MyLiveFormPage formId is empty, cancel overflow failed');
              return;
            }
            hilog.info(DOMAIN, 'testTag', 'MyLiveFormPage cancel overflow animation');
            formProvider.cancelOverflow(this.formId);
          })
      }
    }
    
3.  互动卡片LiveFormExtensionAbility配置
    
    在module.json5配置文件中[extensionAbilities标签](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/module-configuration-file#extensionabilities标签)下配置LiveFormExtensionAbility。
    
    // entry/src/main/module.json5
    // ...
        "extensionAbilities": \[
          // ...
          {
            "name": "MyLiveFormExtensionAbility",
            "srcEntry": "./ets/myliveformextensionability/MyLiveFormExtensionAbility.ets",
            "description": "MyLiveFormExtensionAbility",
            "type": "liveForm"
          }
        \],
        // ...
    
    在main\_pages.json文件中声明互动卡片页面。
    
    // entry/src/main/resources/base/profile/main\_pages.json
    {
      "src": \[
        "pages/Index",
        "myliveformextensionability/pages/MyLiveFormPage"
      \]
    }
    

#### \[h2\]卡片非激活态UI开发

1.  非激活态卡片页面实现
    
    非激活态卡片页面开发同普通卡片开发流程完全一致，在widgetCard.ets中完成。widgetCard.ets文件在卡片创建时自动生成，卡片创建流程可以参考[创建ArkTS卡片](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-ui-widget-creation)。在非激活态卡片页面实现点击卡片时，发起卡片动效请求。
    
    // entry/src/main/ets/widget/pages/WidgetCard.ets
    @Entry
    @Component
    struct WidgetCard {
      build() {
        Row() {
          Column() {
            // $r('app.string.liveform\_click1')需要在相应的资源文件string.json中定义
            Text($r('app.string.liveform\_click1'))
              // $r('app.float.font\_size')需开发者根据实际情况替换相应的资源或值
              .fontSize($r('app.float.font\_size'))
              .fontWeight(FontWeight.Medium)
              // $r('sys.color.font\_primary')需开发者根据实际情况替换相应的资源或值
              .fontColor($r('sys.color.font\_primary'))
          }
          .width('100%')
        }
        .height('100%')
        .onClick(() => {
          // 点击卡片时，选择向EntryFormAbility发送消息，并在其onFormEvent回调中调用formProvider.requestOverflow，请求卡片动效
          postCardAction(this, {
            action: 'message',
            abilityName: 'EntryFormAbility',
            params: {
              message: 'requestOverflow'
            }
          });
        })
      }
    }
    
2.  form\_config.json配置
    
    在form\_config.json配置文件中新增sceneAnimationParams配置项。
    
    // entry/src/main/resources/base/profile/form\_config.json
    {
      "forms": \[
        {
          "name": "widget",
          "displayName": "$string:widget\_display\_name",
          "description": "$string:widget\_desc",
          "src": "./ets/widget/pages/WidgetCard.ets",
          "uiSyntax": "arkts",
          "window": {
            "designWidth": 720,
            "autoDesignWidth": true
          },
          "colorMode": "auto",
          "isDefault": true,
          "updateEnabled": true,
          "scheduledUpdateTime": "10:30",
          "updateDuration": 1,
          "defaultDimension": "2\*2",
          "supportDimensions": \[
            "2\*2"
          \],
          "formConfigAbility": "ability://EntryAbility",
          "dataProxyEnabled": false,
          "isDynamic": true,
          "transparencyEnabled": false,
          "metadata": \[\],
          "sceneAnimationParams": {
            "abilityName": "MyLiveFormExtensionAbility"
          }
        }
      \]
    }
    

#### \[h2\]互动卡片动效实现

1.  触发互动卡片动效
    
    互动卡片通过调用[formProvider.requestOverflow](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-app-form-formprovider#formproviderrequestoverflow20)接口触发动效，调用时需要明确：（1）动效申请范围。（2）动效持续时间。（3）是否使用系统提供的默认切换动效。具体可参考[formInfo.OverflowInfo](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-app-form-forminfo#overflowinfo20)。其中，互动卡片可以通过调用[formProvider.getFormRect](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-app-form-formprovider#formprovidergetformrect20)接口获取卡片尺寸和在窗口内的位置信息。卡片提供方以此计算动效申请范围，单位为vp。计算规则具体请参考[互动卡片请求参数约束](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-ui-liveform-sceneanimation-overview#请求参数约束)。
    
    // entry/src/main/ets/entryformability/EntryFormAbility.ets
    import { FormExtensionAbility, formInfo, formProvider } from '@kit.FormKit';
    import { BusinessError } from '@kit.BasicServicesKit';
    // Constants实现参考“互动卡片动效工具函数实现”小节
    import { Constants } from '../common/Constants';
    import { hilog } from '@kit.PerformanceAnalysisKit';
    
    const TAG: string = 'EntryFormAbility';
    const DOMAIN\_NUMBER: number = 0xFF00;
    
    export default class EntryFormAbility extends FormExtensionAbility {
      async onFormEvent(formId: string, message: string) {
        let shortMessage: string = JSON.parse(message)\['message'\];
    
        // 当接收的message为requestOverflow，触发互动卡片动效
        if (shortMessage === 'requestOverflow') {
          let formRect: formInfo.Rect = await formProvider.getFormRect(formId);
          this.requestOverflow(formId, formRect.width, formRect.height);
          return;
        }
      }
    
      private requestOverflow(formId: string, formWidth: number, formHeight: number): void {
        if (formWidth <= 0 || formHeight <= 0) {
          hilog.info(DOMAIN\_NUMBER, TAG, 'requestOverflow failed, form size is not correct.');
          return;
        }
    
        // 基于卡片自身尺寸信息，计算卡片动效渲染区域
        let left: number = -Constants.OVERFLOW\_LEFT\_RATIO \* formWidth;
        let top: number = -Constants.OVERFLOW\_TOP\_RATIO \* formHeight;
        let width: number = Constants.OVERFLOW\_WIDTH\_RATIO \* formWidth;
        let height: number = Constants.OVERFLOW\_HEIGHT\_RATIO \* formHeight;
        let duration: number = Constants.OVERFLOW\_DURATION;
    
        // 发起互动卡片动效申请
        try {
          formProvider.requestOverflow(formId, {
            // 动效申请范围
            area: {
              left: left,
              top: top,
              width: width,
              height: height
            },
            // 动效持续时间
            duration: duration,
            // 指定是否使用系统提供的默认切换动效
            useDefaultAnimation: true,
          }).then(() => {
            hilog.info(DOMAIN\_NUMBER, TAG, 'requestOverflow requestOverflow succeed');
          }).catch((error: BusinessError) => {
            hilog.info(DOMAIN\_NUMBER, TAG, \`requestOverflow requestOverflow catch error\` + \`,
              code: ${error.code}, message: ${error.message}\`);
          })
        } catch (e) {
          hilog.info(DOMAIN\_NUMBER, TAG, \`requestOverflow call requestOverflow catch error\` + \`,
            code: ${e.code}, message: ${e.message}\`);
        }
      }
    }
    
2.  互动卡片动效工具函数实现
    
    // entry/src/main/ets/common/Constants.ets
    // 动效相关常量的开发
    export class Constants {
      // 互动卡片动效超范围，左侧偏移百分比 = 偏移值/卡片宽度
      public static readonly OVERFLOW\_LEFT\_RATIO: number = 0.1;
      // 互动卡片动效超范围，上侧偏移百分比 = 偏移值/卡片高度
      public static readonly OVERFLOW\_TOP\_RATIO: number = 0.15;
      // 互动卡片动效超范围，宽度放大百分比
      public static readonly OVERFLOW\_WIDTH\_RATIO: number = 1.2;
      // 互动卡片动效超范围，高度放大百分比
      public static readonly OVERFLOW\_HEIGHT\_RATIO: number = 1.3;
      // 互动卡片动效超范围，动效时长
      public static readonly OVERFLOW\_DURATION: number = 3500;
    }
    
3.  需要的资源文件string.json
    
    {
        "string": \[
          // ...
          {
            "name": "liveform\_click1",
            "value": "点击触发互动卡片动效"
          },
          {
            "name": "button\_cancel",
            "value": "强制取消动效"
          }
        \]
    }
    

#### 实现效果

以下是按照本文档代码示例开发而成的效果demo，demo执行动效时，点击按钮，将调用 [formProvider.cancelOverflow](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-app-form-formprovider#formprovidercanceloverflow20) 接口，打断当前破框动效，卡片切换为非激活态。

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/1d/v3/Ww0kePOSRGCOT-xjaLoKGg/zh-cn_image_0000002625108053.gif?HW-CC-KV=V1&HW-CC-Date=20260612T073343Z&HW-CC-Expire=86400&HW-CC-Sign=F82527557B3194AC85F65524411656382655C1F3A8C1241AAF3E91577ADB60D8)