• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Developing a Scene-based Widget
2
3This document outlines the development of scene-based widgets, covering UI designs for both inactive and active states, as well as related configuration files.
4
5## Available APIs
6
7The following table lists the key APIs for a scene-based widget.
8
9**Table 1** Main APIs
10
11| API                                                                                                                                                                                                | Description                |
12|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------|
13| [onLiveFormCreate(liveFormInfo: LiveFormInfo, session: UIExtensionContentSession): void](../reference/apis-form-kit/js-apis-app-form-LiveFormExtensionAbility.md#onliveformcreate)                  | Called when a widget UI object is created.  |
14| [onLiveFormDestroy(liveFormInfo: LiveFormInfo): void](../reference/apis-form-kit/js-apis-app-form-LiveFormExtensionAbility.md#onliveformdestroy)                                                    | Called when a widget UI object is destroyed and related resources are cleared. |
15| [formProvider.requestOverflow(formId: string, overflowInfo: formInfo.OverflowInfo): Promise<void>](../reference/apis-form-kit/js-apis-app-form-formProvider.md#formproviderrequestoverflow20) | Called by the widget provider to request interactive widget animations.  |
16| [formProvider.cancelOverflow(formId: string): Promise<void>](../reference/apis-form-kit/js-apis-app-form-formProvider.md#formprovidercanceloverflow20)                                        | Called by the widget provider to cancel interactive widget animations.|
17| [formProvider.getFormRect(formId: string): Promise<formInfo.Rect>](../reference/apis-form-kit/js-apis-app-form-formProvider.md#formprovidergetformrect20)                                        | Called by the widget provider to query the widget position and dimensions.|
18
19## How to Develop
20
21### Widget UI in Active State
22
231. Create an interactive widget.
24
25    Create an interactive widget through [LiveFormExtensionAbility](../reference/apis-form-kit/js-apis-app-form-LiveFormExtensionAbility.md) and load the widget page.
26
27    ```ts
28    // entry/src/main/ets/myliveformextensionability/MyLiveFormExtensionAbility.ets
29    import { formInfo, LiveFormInfo, LiveFormExtensionAbility } from '@kit.FormKit';
30    import { UIExtensionContentSession } from '@kit.AbilityKit';
31
32    export default class MyLiveFormExtensionAbility extends LiveFormExtensionAbility {
33      onLiveFormCreate(liveFormInfo: LiveFormInfo, session: UIExtensionContentSession) {
34        let storage: LocalStorage = new LocalStorage();
35        storage.setOrCreate('session', session);
36        let formId: string = liveFormInfo.formId;
37        storage.setOrCreate('formId', formId);
38
39        // Obtain the rounded corner information of the widget.
40        let borderRadius: number = liveFormInfo.borderRadius;
41        storage.setOrCreate('borderRadius', borderRadius);
42
43        // The liveFormInfo.rect field indicates the position and dimensions of the widget relative to the active UI.
44        let formRect: formInfo.Rect = liveFormInfo.rect;
45        storage.setOrCreate('formRect', formRect);
46        console.log(`MyLiveFormExtensionAbility onSessionCreate formId: ${formId}` +
47          `, borderRadius: ${borderRadius}, formRectInfo: ${JSON.stringify(formRect)}`);
48
49        // Load the interactive page.
50        session.loadContent('myliveformextensionability/pages/MyLiveFormPage', storage);
51      }
52
53      onLiveFormDestroy(liveFormInfo: LiveFormInfo) {
54        console.log(`MyLiveFormExtensionAbility onDestroy`);
55      }
56    };
57    ```
58
592. Implement an interactive widget page.
60
61    ```ts
62    // entry/src/main/ets/myliveformextensionability/pages/MyLiveFormPage.ets
63    import { formInfo, formProvider } from '@kit.FormKit';
64    import { Constants } from '../../common/Constants';
65
66    const ANIMATION_RECT_SIZE: number = 100;
67    const END_SCALE: number = 1.5;
68    const END_TRANSLATE: number = -300;
69
70    @Entry
71    @Component
72    struct MyLiveFormPage {
73      @State columnScale: number = 1.0;
74      @State columnTranslate: number = 0.0;
75
76      private uiContext: UIContext | undefined = undefined;
77      private storageForMyLiveFormPage: LocalStorage | undefined = undefined;
78      private formId: string | undefined = undefined;
79      private formRect: formInfo.Rect | undefined = undefined;
80      private formBorderRadius: number | undefined = undefined;
81
82      aboutToAppear(): void {
83        this.uiContext = this.getUIContext();
84        if (!this.uiContext) {
85          console.warn("no uiContext");
86          return;
87        }
88        this.initParams();
89      }
90
91      private initParams(): void {
92        this.storageForMyLiveFormPage = this.uiContext?.getSharedLocalStorage();
93        this.formId = this.storageForMyLiveFormPage?.get<string>('formId');
94        this.formRect = this.storageForMyLiveFormPage?.get<formInfo.Rect>('formRect');
95        this.formBorderRadius = this.storageForMyLiveFormPage?.get<number>('borderRadius');
96      }
97
98      // Execute the animation.
99      private runAnimation(): void {
100        this.uiContext?.animateTo({
101          duration: Constants.OVERFLOW_DURATION,
102          curve: Curve.Ease
103        }, () => {
104          this.columnScale = END_SCALE;
105          this.columnTranslate = END_TRANSLATE;
106        });
107      }
108
109      build() {
110        Stack({alignContent: Alignment.TopStart}) {
111          // Background component, whose size is the same as that of a common widget.
112          Column()
113            .width(this.formRect? this.formRect.width : 0)
114            .height(this.formRect? this.formRect.height : 0)
115            .offset({
116              x: this.formRect? this.formRect.left : 0,
117              y: this.formRect? this.formRect.top : 0,
118            })
119            .borderRadius(this.formBorderRadius ? this.formBorderRadius : 0)
120            .backgroundColor('#2875F5')
121          Stack() {
122            this.buildContent();
123          }
124          .width('100%')
125          .height('100%')
126        }
127        .width('100%')
128        .height('100%')
129      }
130
131      @Builder
132      buildContent() {
133        Stack()
134          .width(ANIMATION_RECT_SIZE)
135          .height(ANIMATION_RECT_SIZE)
136          .backgroundColor(Color.White)
137          .scale({
138            x: this.columnScale,
139            y: this.columnScale,
140          })
141          .translate({
142            y: this.columnTranslate
143          })
144          .onAppear(() => {
145            // Execute the animation when the page is displayed.
146            this.runAnimation();
147          })
148
149        Button('Cancel the animation forcibly')
150          .backgroundColor(Color.Grey)
151          .onClick(() => {
152            if (!this.formId) {
153              console.log('MyLiveFormPage formId is empty, cancel overflow failed');
154              return;
155            }
156            console.log('MyLiveFormPage cancel overflow animation');
157            formProvider.cancelOverflow(this.formId);
158          })
159      }
160    }
161    ```
162
1633. Configure LiveFormExtensionAbility for interactive widgets.
164
165    Configure LiveFormExtensionAbility in [extensionAbilities](../quick-start/module-configuration-file.md#extensionabilities) of the **module.json5** file.
166
167    ```ts
168    // entry/src/main/module.json5
169        ...
170        "extensionAbilities": [
171          {
172            "name": "MyLiveFormExtensionAbility",
173            "srcEntry": "./ets/myliveformextensionability/MyLiveFormExtensionAbility.ets",
174            "description": "MyLiveFormExtensionAbility",
175            "type": "liveForm"
176          }
177        ]
178        ...
179    ```
180
181    Declare the interactive widget page in the **main_pages.json** file.
182
183    ```ts
184    // entry/src/main/resources/base/profile/main_pages.json
185    {
186      "src": [
187        "pages/Index",
188        "myliveformextensionability/pages/MyLiveFormPage"
189      ]
190    }
191    ```
192
193### Widget UI in Inactive State
194
1951. Implement a widget page in the inactive state.
196
197    The page development process of a widget in the inactive state is the same as that of a common widget and is completed in **widgetCard.ets**, which is automatically generated when a widget is created. For details about the widget creation process, see [Creating an ArkTS Widget](arkts-ui-widget-creation.md). On the inactive widget page, request the widget animation when the widget is tapped.
198
199    ```ts
200    // entry/src/main/ets/widget/pages/WidgetCard.ets
201    @Entry
202    @Component
203    struct WidgetCard {
204      build() {
205        Row() {
206          Column() {
207            Text('Tap to trigger interactive widget animation')
208              .fontSize($r('app.float.font_size'))
209              .fontWeight(FontWeight.Medium)
210              .fontColor($r('sys.color.font_primary'))
211          }
212          .width('100%')
213        }
214        .height('100%')
215        .onClick(() => {
216          // When a widget is tapped, send a message to EntryFormAbility and call formProvider.requestOverflow in the onFormEvent callback to request the widget animation.
217          postCardAction(this, {
218            action: 'message',
219            abilityName: 'EntryFormAbility',
220            params: {
221              message: 'requestOverflow'
222            }
223          });
224        })
225      }
226    }
227    ```
228
2292. Configure the **form_config.json** file.
230
231    Add the **sceneAnimationParams** configuration item to the **form_config.json** file.
232
233    ```ts
234    // entry/src/main/resources/base/profile/form_config.json
235    {
236      "forms": [
237        {
238          "name": "widget",
239          "displayName": "$string:widget_display_name",
240          "description": "$string:widget_desc",
241          "src": "./ets/widget/pages/WidgetCard.ets",
242          "uiSyntax": "arkts",
243          "window": {
244            "designWidth": 720,
245            "autoDesignWidth": true
246          },
247          "colorMode": "auto",
248          "isDefault": true,
249          "updateEnabled": true,
250          "scheduledUpdateTime": "10:30",
251          "updateDuration": 1,
252          "defaultDimension": "2*2",
253          "supportDimensions": [
254            "2*2"
255          ],
256          "formConfigAbility": "ability://EntryAbility",
257          "dataProxyEnabled": false,
258          "isDynamic": true,
259          "transparencyEnabled": false,
260          "metadata": [],
261          "sceneAnimationParams": {
262            "abilityName": "MyLiveFormExtensionAbility"
263          }
264        }
265      ]
266    }
267    ```
268
269### Interactive Widget Animation
270
2711. Trigger interactive widget animations.
272
273    Call the [formProvider.requestOverflow](../reference/apis-form-kit/js-apis-app-form-formProvider.md#formproviderrequestoverflow20) API, and specify the animation request range, animation duration, and whether to use the default switching animation provided by the system. For details, see [formInfo.OverflowInfo](../reference/apis-form-kit/js-apis-app-form-formInfo.md#overflowinfo20). You can call the [formProvider.getFormRect](../reference/apis-form-kit/js-apis-app-form-formProvider.md#formprovidergetformrect20) API to obtain the dimensions and position of the interactive widget in the window. The widget provider calculates the animation request range (in vp) based on these dimensions. For details about the calculation rules, see the [constraints on widget parameter request](arkts-ui-liveform-sceneanimation-overview.md#parameter-request).
274
275    ```ts
276    // entry/src/main/ets/entryformability/EntryFormAbility.ets
277    import {
278      formInfo,
279      formProvider,
280      FormExtensionAbility,
281    } from '@kit.FormKit';
282    import { BusinessError } from '@kit.BasicServicesKit';
283    import { Constants } from '../common/Constants';
284
285    export default class EntryFormAbility extends FormExtensionAbility {
286      async onFormEvent(formId: string, message: string) {
287        let shortMessage: string = JSON.parse(message)['message'];
288
289        // Trigger the interactive widget animation when the received message is requestOverflow.
290        if (shortMessage === 'requestOverflow') {
291          let formRect: formInfo.Rect = await formProvider.getFormRect(formId);
292          this.requestOverflow(formId, formRect.width, formRect.height);
293          return;
294        }
295      }
296
297      private requestOverflow(formId: string, formWidth: number, formHeight: number): void {
298        if (formWidth <= 0 || formHeight <= 0) {
299          console.log('requestOverflow failed, form size is not correct.');
300          return;
301        }
302
303        // Calculate the animation rendering area of the widget based on the dimension.
304        let left: number = -Constants.OVERFLOW_LEFT_RATIO * formWidth;
305        let top: number = -Constants.OVERFLOW_TOP_RATIO * formHeight;
306        let width: number = Constants.OVERFLOW_WIDTH_RATIO * formWidth;
307        let height: number = Constants.OVERFLOW_HEIGHT_RATIO * formHeight;
308        let duration: number = Constants.OVERFLOW_DURATION;
309
310        // Request an interactive widget animation.
311        try {
312          formProvider.requestOverflow(formId, {
313            // Animation request range
314            area: { left: left, top: top, width: width, height: height },
315            // Animation duration
316            duration: duration,
317            // Specify whether to use the default switching animation provided by the system.
318            useDefaultAnimation: true,
319          }).then(() => {
320            console.log('requestOverflow requestOverflow succeed');
321          }).catch((error: BusinessError) => {
322            console.log(`requestOverflow requestOverflow catch error` + `, code: ${error.code}, message: ${error.message}`);
323          })
324        } catch (e) {
325          console.log(`requestOverflow call requestOverflow catch error` + `, code: ${e.code}, message: ${e.message}`);
326        }
327      }
328    }
329    ```
330
3312. Implement the tool functions of the interactive widget animation.
332    ```ts
333    // entry/src/main/ets/common/Constants.ets
334    // Develop animation-related constants.
335    export class Constants {
336      // The interactive widget animation is out of range. Left offset percentage = Offset value/Widget width
337      public static readonly OVERFLOW_LEFT_RATIO: number = 0.1;
338
339      // The interactive widget animation is out of range. Top offset percentage = Offset value/Widget height
340      public static readonly OVERFLOW_TOP_RATIO: number = 0.15;
341
342      // The interactive widget animation is out of range. The width is enlarged by percentage.
343      public static readonly OVERFLOW_WIDTH_RATIO: number = 1.2;
344
345      // The interactive widget animation is out of range. The height is enlarged by percentage.
346      public static readonly OVERFLOW_HEIGHT_RATIO: number = 1.3;
347
348      // The interactive widget animation is out of range. Specify the animation duration.
349      public static readonly OVERFLOW_DURATION: number = 3500;
350    }
351    ```
352
353## Effect
354The following is a demo developed based on the code examples in this document. When the demo is executed, the [formProvider.cancelOverflow](../reference/apis-form-kit/js-apis-app-form-formProvider.md#formprovidercanceloverflow20) API is called to interrupt the current overflow animation and the widget is switched to the inactive state.
355
356![live-form-base-demo.gif](figures/live-form-base-demo.gif)
357