harmony 鸿蒙场景动效类型互动卡片开发指导

  • 2025-06-12
  • 浏览 (4)

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

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

接口说明

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

表1 主要接口

接口名 描述
onLiveFormCreate(liveFormInfo: LiveFormInfo, session: UIExtensionContentSession): void 互动卡片界面对象创建的回调函数。
onLiveFormDestroy(liveFormInfo: LiveFormInfo): void 互动卡片界面对象销毁、资源清理的回调函数。
formProvider.requestOverflow(formId: string, overflowInfo: formInfo.OverflowInfo): Promise<void> 卡片提供方发起互动卡片动效请求。
formProvider.cancelOverflow(formId: string): Promise<void> 卡片提供方发起取消互动卡片动效请求。

开发流程

卡片激活态UI开发

  1. 创建互动卡片

通过LiveFormExtensionAbility创建互动卡片,创建时加载互动卡片页面。

// entry/src/main/ets/myliveformextensionability/MyLiveFormExtensionAbility.ets
import { formInfo, LiveFormInfo, LiveFormExtensionAbility } from '@kit.FormKit';
import { UIExtensionContentSession } from '@kit.AbilityKit';

export default class MyLiveFormExtensionAbility extends LiveFormExtensionAbility {
  onLiveFormCreate(liveFormInfo: LiveFormInfo, session: UIExtensionContentSession) {
    let storage: LocalStorage = new LocalStorage();
    storage.setOrCreate('session', session);

    // 获取卡片信息
    let formId: string = liveFormInfo.formId;
    storage.setOrCreate('formId', formId);
    let formRect: formInfo.Rect = liveFormInfo.rect;
    storage.setOrCreate('formRect', formRect);
    let borderRadius: number = liveFormInfo.borderRadius;
    storage.setOrCreate('borderRadius', borderRadius);
    console.log(`MyLiveFormExtensionAbility onSessionCreate formId: ${formId}, borderRadius: ${borderRadius}` +
      `, formRect: ${JSON.stringify(formRect)}`);

    // 加载互动页面
    session.loadContent('myliveformextensionability/pages/MyLiveFormPage', storage);
  }

  onLiveFormDestroy(liveFormInfo: LiveFormInfo) {
    console.log(`MyLiveFormExtensionAbility onDestroy`);
  }
};
  1. 互动卡片页面实现
// entry/src/main/ets/myliveformextensionability/pages/MyLiveFormPage.ets
import { formInfo, formProvider } from '@kit.FormKit';
import { Constants } from '../../common/Constants';

const ANIMATION_RECT_SIZE: number = 100;
const END_SCALE: number = 1.5;
const END_TRANSLATE: number = -300;

@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;

  aboutToAppear(): void {
    this.uiContext = this.getUIContext();
    if (!this.uiContext) {
      console.warn("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');
  }

  // 执行动效
  private runAnimation(): void {
    this.uiContext?.animateTo({
      duration: Constants.OVERFLOW_DURATION,
      curve: Curve.Ease
    }, () => {
      this.columnScale = END_SCALE;
      this.columnTranslate = END_TRANSLATE;
    });
  }

  build() {
    Stack() {
      // 背景组件,和普通卡片等大
      Column()
        .width(this.formRect ? this.formRect.width : 0)
        .height(this.formRect ? this.formRect.height : 0)
        .borderRadius(this.formBorderRadius ? this.formBorderRadius : 0)
        .backgroundColor('#2875F5')

      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();
        })

      // 点击按钮,调用 formProvider.cancelOverflow 接口,打断当前破框动效,卡片切换为非激活态
      Button('强制取消动效')
        .backgroundColor(Color.Grey)
        .onClick(() => {
          if (!this.formId) {
            console.log('MyLiveFormPage formId is empty, cancel overflow failed');
            return;
          }
          console.log('MyLiveFormPage cancel overflow animation');
          formProvider.cancelOverflow(this.formId);
        })
    }
    .width('100%')
    .height('100%')
  }
}
  1. 互动卡片LiveFormExtensionAbility配置

module.json5配置文件extensionAbilities标签下配置LiveFormExtensionAbility。

// entry/src/main/module.json5
    ...
    "extensionAbilities": [
      {
        "name": "MyLiveFormExtensionAbility",
        "srcEntry": "./ets/myliveformextensionability/MyLiveFormExtensionAbility.ets",
        "description": "$string:MyLiveFormExtensionAbility_desc",
        "label": "$string:MyLiveFormExtensionAbility_label",
        "type": "liveForm"
      }
    ]
    ...

卡片非激活态UI开发

  1. 非激活态卡片页面实现

非激活态卡片页面开发同普通卡片开发流程完全一致,在widgetCard.ets中完成。在非激活态卡片页面实现,点击卡片时,请求卡片动效。

// entry/src/main/ets/widget/pages/WidgetCard.ets
@Entry
@Component
struct WidgetCard {
  build() {
    Row() {
      Column() {
        Text('点击触发互动卡片动效')
          .fontSize($r('app.float.font_size'))
          .fontWeight(FontWeight.Medium)
          .fontColor($r('sys.color.font_primary'))
      }
      .width('100%')
    }
    .height('100%')
    .onClick(() => {
      // 点击卡片时,选择向EntryFormAbility发送消息,并在其onFormEvent回调中调用formProvider.requestOverflow,请求卡片动效
      postCardAction(this, {
        action: 'message',
        abilityName: 'EntryFormAbility',
        params: {
          message: 'requestOverflow'
        }
      });
    })
  }
}
  1. 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. 触发互动卡片动效

在加桌时,互动卡片可以通过onUpdateForm生命周期回调中的wantParams参数获取卡片实际尺寸。卡片提供方以此计算动效申请范围,单位为vp。计算规则具体请参考互动卡片请求参数约束

// entry/src/main/ets/entryformability/EntryFormAbility.ets
import {
  formInfo,
  formProvider,
  FormExtensionAbility,
} from '@kit.FormKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { preferences } from '@kit.ArkData';
import { Constants } from '../common/Constants';
import { Utils } from '../common/Utils';

const DB_NAME: string = 'myStore'

export default class EntryFormAbility extends FormExtensionAbility {
  onUpdateForm(formId: string, wantParams?: Record<string, Object>) {
    // 当卡片加桌时,wantParams 参数将不为空,提供卡片尺寸信息
    if (wantParams) {
      this.saveFormSize(formId, wantParams);
    }
  }

  // 解析卡片尺寸信息,并保存在数据库中
  private saveFormSize(formId: string, wantParams: Record<string, Object>) {
    let width = 0;
    let height = 0;
    width = wantParams['ohos.extra.param.key.form_width_vp'] as number;
    height = wantParams['ohos.extra.param.key.form_height_vp'] as number;
    console.log(`onUpdateForm, formId: ${formId}, size:[${width}, ${height}]`);
    let promise: Promise<preferences.Preferences> = preferences.getPreferences(this.context, DB_NAME);

    // 将卡片尺寸信息写入数据库
    Utils.writeFormSize(promise, formId, width, height);
  }

  // 从数据库中读取卡片尺寸信息
  private async getFormSize(formId: string): Promise<number[]> {
    let storeDB: preferences.Preferences =  await preferences.getPreferences(this.context, DB_NAME);
    let formCardInfo: string[] = await Utils.readFormSize(formId, storeDB);
    return [Number.parseFloat(formCardInfo[0]), Number.parseFloat(formCardInfo[1])];
  }

  async onFormEvent(formId: string, message: string) {
    let shortMessage: string = JSON.parse(message)['message'];

    // 当接收的 message 为 requestOverflow,触发互动卡片动效
    if (shortMessage === 'requestOverflow') {
      let sizeInfo = await this.getFormSize(formId);
      this.requestOverflow(formId, sizeInfo[0], sizeInfo[1]);
      return;
    }
  }

  private requestOverflow(formId: string, formWidth: number, formHeight: number): void {
    if (formWidth <= 0||formHeight <= 0) {
      console.log('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
      }).then(() => {
        console.log('requestOverflow requestOverflow succeed');
      }).catch((error: BusinessError) => {
        console.log(`requestOverflow requestOverflow catch error` + `, code: ${error.code}, message: ${error.message}`);
      })
    } catch (e) {
      console.log(`requestOverflow call requestOverflow catch error` + `, code: ${e.code}, message: ${e.message}`);
    }
  }
}
  1. 互动卡片动效工具函数实现
// entry/src/main/ets/common/Utils.ets
import { preferences } from '@kit.ArkData';
import { BusinessError } from '@kit.BasicServicesKit';

export class Utils {
  // 卡片尺寸信息写数据库
  public static writeFormSize(promise: Promise<preferences.Preferences>, formId: string, width: number,
    height: number): void {
    promise.then(async (storeDB: preferences.Preferences) => {
      console.log('writeCardInfoSizeToDB, Succeeded to get preferences.');
      await storeDB.put('width_' + formId, `${width}`);
      await storeDB.put('height_' + formId, `${height}`);
      await storeDB.flush();
    }).catch((err: BusinessError) => {
      console.log(`writeCardInfoSizeToDB, Failed to get preferences. ${JSON.stringify(err)}`);
    });
  }

  // 从数据库中读取卡片信息
  public static async readFormSize(formId: string, storeDB: preferences.Preferences): Promise<string[]> {
    try {
      let widthInfo = await storeDB.get('width_' + formId, '-1');
      let heightInfo = await storeDB.get('height_' + formId, '-1');
      console.log(`Succeeded to get cardInfo: ${widthInfo}, ${heightInfo}`);
      return [widthInfo.toString(), heightInfo.toString()];
    } catch(err) {
      console.log(`Failed to get preferences. ${JSON.stringify(err)}`);
      return [''];
    }
  }
}
// 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;
}

实现效果

以下是按照本文档代码示例开发而成的效果demo,demo执行动效时,点击按钮,将调用 formProvider.cancelOverflow 接口,打断当前破框动效,卡片切换为非激活态。 live-form-base-demo.gif

你可能感兴趣的鸿蒙文章

harmony 鸿蒙Form Kit(卡片开发服务)

harmony 鸿蒙ArkTS卡片概述

harmony 鸿蒙趣味交互类型互动卡片开发指导

harmony 鸿蒙互动卡片概述

harmony 鸿蒙场景动效类型互动卡片开发指导(系统应用)

harmony 鸿蒙场景动效类型互动卡片概述

harmony 鸿蒙ArkTS卡片主动刷新

harmony 鸿蒙配置卡片的配置文件

harmony 鸿蒙卡片使用方主动请求刷新卡片内容(仅对系统应用开放)

harmony 鸿蒙创建ArkTS卡片

0  赞