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 357