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

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

#### 接口说明

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

**表1** 主要接口

| 接口名 | 描述 |
| --- | --- |
| onLiveFormCreate(liveFormInfo: LiveFormInfo, session: UIExtensionContentSession): void | 互动卡片界面对象创建的回调函数。 |
| onLiveFormDestroy(liveFormInfo: LiveFormInfo): void | 互动卡片界面对象销毁、资源清理的回调函数。 |
| LiveFormExtensionContext | LiveFormExtensionAbility的上下文，继承自ExtensionContext。 |
| startAbilityByLiveForm(want: Want): Promise | 拉起互动卡片提供方（应用）的页面。 |
| formProvider.requestOverflow(formId: string, overflowInfo: formInfo.OverflowInfo): Promise | 卡片提供方发起互动卡片动效请求。 |
| formProvider.cancelOverflow(formId: string): Promise | 卡片提供方发起取消互动卡片动效请求。 |
| formProvider.getFormRect(formId: string): Promise | 卡片提供方查询卡片位置、尺寸。 |

#### 开发流程

#### 卡片激活态UI开发

1.  创建互动卡片
    
    通过LiveFormExtensionAbility创建互动卡片，创建时加载互动卡片页面。
    
    ```
    // entry/src/main/ets/myliveformextensionability/MyLiveFormExtensionAbility.ets
    import { formInfo, LiveFormExtensionAbility, LiveFormInfo } 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 onLiveFormCreate 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标签下配置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"
      ]
    }
    ```
    

#### 卡片非激活态UI开发

1.  非激活态卡片页面实现
    
    非激活态卡片页面开发同普通卡片开发流程完全一致，在widgetCard.ets中完成。widgetCard.ets文件在卡片创建时自动生成，卡片创建流程可以参考创建ArkTS卡片。在非激活态卡片页面实现点击卡片时，发起卡片动效请求。
    
    ```
    // 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"
          }
        }
      ]
    }
    ```
    

#### 互动卡片动效实现

1.  触发互动卡片动效
    
    互动卡片通过调用formProvider.requestOverflow接口触发动效，调用时需要明确：（1）动效申请范围。（2）动效持续时间。（3）是否使用系统提供的默认切换动效。具体可参考formInfo.OverflowInfo。其中，互动卡片可以通过调用formProvider.getFormRect接口获取卡片尺寸和在窗口内的位置信息。卡片提供方以此计算动效申请范围，单位为vp。计算规则具体请参考互动卡片请求参数约束。
    
    ```
    // 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 接口，打断当前破框动效，卡片切换为非激活态。