1# 管理系统窗口(仅Stage模型支持) 2 3## 管理系统窗口概述 4 5在`Stage`模型下, 允许系统应用创建和管理系统窗口,包括音量条、壁纸、通知栏、状态栏、导航栏等。具体支持的系统窗口类型见[API参考-WindowType](../reference/apis-arkui/arkts-apis-window-e.md#windowtype7)。 6 7在窗口显示、隐藏及窗口间切换时,窗口模块通常会添加动画效果,以使各个交互过程更加连贯流畅。 8 9在OpenHarmony中,应用窗口的动效为默认行为,不需要开发者进行设置或者修改。 10 11相对于应用窗口,在显示系统窗口过程中,开发者可以自定义窗口的显示动画、隐藏动画。 12 13> **说明:** 14> 15> 本文档涉及系统接口的使用,请使用full-SDK进行开发。<!--Del-->具体使用可见[full-SDK替换指南](../faqs/full-sdk-switch-guide.md)。<!--DelEnd--> 16 17 18## 接口说明 19 20更多API说明请参见[API参考](../reference/apis-arkui/js-apis-window-sys.md)。 21 22| 实例名 | 接口名 | 描述 | 23| ----------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | 24| window静态方法 | createWindow(config: Configuration, callback: AsyncCallback\<Window>): void | 创建子窗口或系统窗口。<br/>-`config`:创建窗口时的参数。 | 25| Window | resize(width: number, height: number, callback: AsyncCallback<void>): void | 改变当前窗口大小。 | 26| Window | moveWindowTo(x: number, y: number, callback: AsyncCallback<void>): void | 移动当前窗口位置。 | 27| Window | setUIContent(path: string, callback: AsyncCallback<void>): void | 根据当前工程中某个页面的路径为窗口加载具体的页面内容。<br>其中path为要加载到窗口中的页面内容的路径,在Stage模型下该路径需添加到工程的main_pages.json文件中。 | 28| Window | showWindow(callback: AsyncCallback\<void>): void | 显示当前窗口。 | 29| Window | on(type: 'touchOutside', callback: Callback<void>): void | 开启本窗口区域外的点击事件的监听。 | 30| Window | hide (callback: AsyncCallback\<void>): void | 隐藏当前窗口。此接口为系统接口。 | 31| Window | destroyWindow(callback: AsyncCallback<void>): void | 销毁当前窗口。 | 32| Window | getTransitionController(): TransitionController | 获取窗口属性转换控制器。此接口为系统接口。 | 33| TransitionContext | completeTransition(isCompleted: boolean): void | 设置属性转换的最终完成状态。该函数需要在动画函数[animateTo()](../reference/apis-arkui/arkui-ts/ts-explicit-animation.md)执行后设置。此接口为系统接口。 | 34| Window | showWithAnimation(callback: AsyncCallback\<void>): void | 显示当前窗口,过程中播放动画。此接口为系统接口。 | 35| Window | hideWithAnimation(callback: AsyncCallback\<void>): void | 隐藏当前窗口,过程中播放动画。此接口为系统接口。 | 36 37## 系统窗口的开发 38 39本文以音量条窗口为例,介绍系统窗口的基本开发和管理步骤。 40 41### 开发步骤 42 43 441. 创建系统窗口。 45 46 在[ServiceExtensionContext](../reference/apis-ability-kit/js-apis-inner-application-serviceExtensionContext-sys.md)下,使用`window.createWindow`接口创建音量条系统窗口。 47 482. 操作或设置系统窗口的属性。 49 50 系统窗口创建成功后,可以改变其大小、位置等,还可以根据需要设置系统窗口的背景色、亮度等属性。 51 523. 加载显示系统窗口的具体内容。 53 54 通过`setUIContent`和`showWindow`接口加载显示音量条窗口的具体内容。 55 564. 隐藏/销毁系统窗口。 57 58 当不再需要音量条窗口时,可根据具体实现逻辑,使用`hide`接口或`destroyWindow`接口对其进行隐藏或销毁。 59 60```ts 61import { Want, ServiceExtensionAbility } from '@kit.AbilityKit'; 62import { window } from '@kit.ArkUI'; 63import { BusinessError } from '@kit.BasicServicesKit'; 64 65export default class ServiceExtensionAbility1 extends ServiceExtensionAbility { 66 onCreate(want: Want) { 67 // 1.创建音量条窗口。 68 let windowClass: window.Window | null = null; 69 let config: window.Configuration = { 70 name: "volume", windowType: window.WindowType.TYPE_VOLUME_OVERLAY, ctx: this.context 71 }; 72 window.createWindow(config, (err: BusinessError, data) => { 73 let errCode: number = err.code; 74 if (errCode) { 75 console.error(`Failed to create the volume window. Code:${err.code}, message:${err.message}`); 76 return; 77 } 78 console.info('Succeeded in creating the volume window.') 79 windowClass = data; 80 if (!windowClass) { 81 console.error('windowClass is null'); 82 return; 83 } 84 // 2.创建音量条窗口成功之后,可以改变其大小、位置或设置背景色、亮度等属性。 85 windowClass.moveWindowTo(300, 300, (err: BusinessError) => { 86 let errCode: number = err.code; 87 if (errCode) { 88 console.error('Failed to move the window. Cause:' + JSON.stringify(err)); 89 return; 90 } 91 console.info('Succeeded in moving the window.'); 92 }); 93 windowClass.resize(500, 500, (err: BusinessError) => { 94 let errCode: number = err.code; 95 if (errCode) { 96 console.error('Failed to change the window size. Cause:' + JSON.stringify(err)); 97 return; 98 } 99 console.info('Succeeded in changing the window size.'); 100 }); 101 // 3.为音量条窗口加载对应的目标页面。 102 windowClass.setUIContent("pages/page_volume", (err: BusinessError) => { 103 let errCode: number = err.code; 104 if (errCode) { 105 console.error('Failed to load the content. Cause:' + JSON.stringify(err)); 106 return; 107 } 108 console.info('Succeeded in loading the content.'); 109 if (!windowClass) { 110 console.error('windowClass is null'); 111 return; 112 } 113 // 3.显示音量条窗口。 114 windowClass.showWindow((err: BusinessError) => { 115 let errCode: number = err.code; 116 if (errCode) { 117 console.error('Failed to show the window. Cause:' + JSON.stringify(err)); 118 return; 119 } 120 console.info('Succeeded in showing the window.'); 121 }); 122 }); 123 // 4.隐藏/销毁音量条窗口。当不再需要音量条时,可根据具体实现逻辑,对其进行隐藏或销毁。 124 // 此处以监听音量条区域外的点击事件为例实现音量条窗口的隐藏。 125 windowClass.on('touchOutside', () => { 126 console.info('touch outside'); 127 if (!windowClass) { 128 console.error('windowClass is null'); 129 return; 130 } 131 windowClass.hide((err: BusinessError) => { 132 let errCode: number = err.code; 133 if (errCode) { 134 console.error('Failed to hide the window. Cause: ' + JSON.stringify(err)); 135 return; 136 } 137 console.info('Succeeded in hiding the window.'); 138 }); 139 }); 140 }); 141 } 142}; 143``` 144 145## 自定义系统窗口的显示与隐藏动画 146 147在显示系统窗口过程中,开发者可以自定义窗口的显示动画。在隐藏系统窗口过程中,开发者可以自定义窗口的隐藏动画。本文以显示和隐藏动画为例介绍主要开发步骤。 148 149### 开发步骤 150 1511. 获取窗口属性转换控制器。 152 153 通过`getTransitionController`接口获取控制器。后续的动画操作都由属性控制器来完成。 154 1552. 配置窗口显示时的动画。 156 157 通过动画函数[animateTo()](../reference/apis-arkui/arkui-ts/ts-explicit-animation.md)配置具体的属性动画。 158 1593. 设置属性转换完成。 160 161 通过`completeTransition(true)`来设置属性转换的最终完成状态。如果传入false,则表示撤销本次转换。 162 1634. 显示或隐藏当前窗口,过程中播放动画。 164 165 调用`showWithAnimation`接口,来显示窗口并播放动画。调用`hideWithAnimation`接口,来隐藏窗口并播放动画。 166 167```ts 168// xxx.ts 实现使用ts文件引入showWithAnimation,hideWithAnimation方法 169import { window } from '@kit.ArkUI'; 170 171export class AnimationConfig { 172 private animationForShownCallFunc_: ((context: window.TransitionContext) => void) | undefined = undefined; 173 private animationForHiddenCallFunc_: ((context: window.TransitionContext) => void) | undefined = undefined; 174 175 ShowWindowWithCustomAnimation(windowClass: window.Window, callback: (context: window.TransitionContext) => void) { 176 if (!windowClass) { 177 console.error('LOCAL-TEST windowClass is undefined'); 178 return false; 179 } 180 this.animationForShownCallFunc_ = callback; 181 // 获取窗口属性转换控制器 182 let controller: window.TransitionController = windowClass.getTransitionController(); 183 controller.animationForShown = (context: window.TransitionContext)=> { 184 this.animationForShownCallFunc_(context); 185 }; 186 187 windowClass.showWithAnimation(()=>{ 188 console.info('LOCAL-TEST show with animation success'); 189 }) 190 return true; 191 } 192 193 HideWindowWithCustomAnimation(windowClass: window.Window, callback: (context: window.TransitionContext) => void) { 194 if (!windowClass) { 195 console.error('LOCAL-TEST window is undefined'); 196 return false; 197 } 198 this.animationForHiddenCallFunc_ = callback; 199 // 获取窗口属性转换控制器 200 let controller: window.TransitionController = windowClass.getTransitionController(); 201 controller.animationForHidden = (context : window.TransitionContext) => { 202 this.animationForHiddenCallFunc_(context); 203 }; 204 205 windowClass.hideWithAnimation(()=>{ 206 console.info('Hide with animation success'); 207 }) 208 return true; 209 } 210} 211``` 212 213```ts 214// xxx.ets 实现主窗口创建相关操作 215import { window } from '@kit.ArkUI'; 216import { UIAbility, Want, AbilityConstant, common } from '@kit.AbilityKit'; 217import { hilog } from '@kit.PerformanceAnalysisKit' 218 219export default class EntryAbility extends UIAbility { 220 onCreate(want: Want,launchParam:AbilityConstant.LaunchParam) { 221 hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate'); 222 } 223 224 onDestroy(): void { 225 hilog.info(0x0000, 'testTag', '%{public}s', 'Ability Destroy'); 226 } 227 228 onWindowStageCreate(windowStage:window.WindowStage): void{ 229 hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); 230 231 windowStage.loadContent('pages/transferControllerPage',(err, data)=>{ 232 if(err.code){ 233 hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause:%{public}s', JSON.stringify(err)??''); 234 return ; 235 } 236 hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data:%{public}s', JSON.stringify(data)??''); 237 238 }); 239 240 AppStorage.setOrCreate<common.UIAbilityContext>("currentContext",this.context); 241 } 242 243 onWindowStageDestroy(): void{ 244 hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy'); 245 } 246 247 onForeground(): void{ 248 hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground'); 249 } 250 251 onBackground(): void{ 252 hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground'); 253 } 254}; 255``` 256 257```ts 258// xxx.ets 实现子窗口的属性设置 259import { window, router } from '@kit.ArkUI'; 260import { common } from '@kit.AbilityKit'; 261 262@Entry 263@Component 264struct transferCtrlSubWindow { 265 @State message: string = 'transferCtrlSubWindow' 266 267 build() { 268 Column() { 269 Text("close") 270 .fontSize(24) 271 .fontWeight(FontWeight.Normal) 272 .margin({ left: 10, top: 10 }) 273 Button() { 274 Text("close") 275 .fontSize(24) 276 .fontWeight(FontWeight.Normal) 277 }.width(220).height(68) 278 .margin({ left: 10, top: 10 }) 279 .onClick(() => { 280 let subWin = AppStorage.get<window.Window>("TransferSubWindow"); 281 subWin?.destroyWindow(); 282 AppStorage.setOrCreate<window.Window>("TransferSubWindow", undefined); 283 }) 284 }.height('100%').width('100%').backgroundColor('#ff556243').shadow({radius: 30,color: '#ff555555',offsetX: 15,offsetY: 15}) 285 } 286} 287``` 288 289```ts 290// xxx.ets 实现子窗口的创建以及显示、隐藏窗口时的动效操作 291import { window, router } from '@kit.ArkUI'; 292import { common, Want } from '@kit.AbilityKit'; 293import { BusinessError } from '@kit.BasicServicesKit'; 294import { AnimationConfig } from '../entryability/AnimationConfig'; 295 296@Entry 297@Component 298struct Index { 299 @State message: string = 'transferControllerWindow'; 300 301 private animationConfig_?:AnimationConfig = undefined; 302 private subWindow_?:window.Window = undefined; 303 304 aboutToAppear(){ 305 if(!this.animationConfig_){ 306 this.animationConfig_ = new AnimationConfig(); 307 } 308 } 309 310 private CreateTransferSubWindow(){ 311 let context = AppStorage.get<common.UIAbilityContext>("currentContext"); 312 console.log('LOCAL-TEST try to CreateTransferSubWindow'); 313 let windowConfig:window.Configuration = { 314 name : "systemTypeWindow", 315 windowType : window.WindowType.TYPE_FLOAT, 316 ctx : context, 317 }; 318 let promise = window.createWindow(windowConfig); 319 promise.then(async(subWin: window.Window) => { 320 this.subWindow_ = subWin; 321 AppStorage.setOrCreate<window.Window>("systemTypeWindow", subWin); 322 await subWin.setUIContent("pages/transferCtrlSubWindow",()=>{}); 323 await subWin.moveWindowTo(100,100); 324 await subWin.resize(200,200); 325 }).catch((err:Error)=>{ 326 console.log('LOCAL-TEST createSubWindow err:' + JSON.stringify(err)); 327 }) 328 } 329 private ShowSUBWindow(){ 330 if(!this.subWindow_){ 331 console.log('LOCAL-TEST this.subWindow_is null'); 332 return ; 333 } 334 let animationConfig = new AnimationConfig(); 335 let systemTypeWindow = window.findWindow("systemTypeWindow"); 336 console.log("LOCAL-TEST try to ShowWindowWithCustomAnimation"); 337 animationConfig.ShowWindowWithCustomAnimation(systemTypeWindow, (context:window.TransitionContext) => { 338 console.info('LOCAL-TEST start show window animation'); 339 let toWindow = context.toWindow; 340 this.getUIContext()?.animateTo({ 341 duration: 200, // 动画时长 342 tempo: 0.5, // 播放速率 343 curve: Curve.EaseInOut, // 动画曲线 344 delay: 0, // 动画延迟 345 iterations: 1, // 播放次数 346 playMode: PlayMode.Normal, // 动画模式 347 onFinish: () => { 348 console.info('LOCAL-TEST onFinish in show animation'); 349 context.completeTransition(true); 350 } 351 },() => { 352 let obj: window.TranslateOptions = { 353 x: 100.0, 354 y: 0.0, 355 z: 0.0 356 }; 357 try { 358 toWindow?.translate(obj); // 设置动画过程中的属性转换 359 }catch(exception){ 360 console.error('Failed to translate. Cause: ' + JSON.stringify(exception)); 361 } 362 }); 363 console.info('LOCAL-TEST complete transition end'); 364 }); 365 } 366 367 private HideSUBWindow(){ 368 if(!this.subWindow_){ 369 console.log('LOCAL-TEST this.subWindow_is null'); 370 return ; 371 } 372 let animationConfig = new AnimationConfig(); 373 let systemTypeWindow = window.findWindow("systemTypeWindow"); 374 console.log("LOCAL-TEST try to HideWindowWithCustomAnimation"); 375 animationConfig.HideWindowWithCustomAnimation(systemTypeWindow, (context:window.TransitionContext) => { 376 console.info('LOCAL-TEST start hide window animation'); 377 let toWindow = context.toWindow; 378 this.getUIContext()?.animateTo({ 379 duration: 200, // 动画时长 380 tempo: 0.5, // 播放速率 381 curve: Curve.EaseInOut, // 动画曲线 382 delay: 0, // 动画延迟 383 iterations: 1, // 播放次数 384 playMode: PlayMode.Normal, // 动画模式 385 onFinish: () => { 386 console.info('LOCAL-TEST onFinish in hide animation'); 387 context.completeTransition(true); 388 } 389 },() => { 390 let obj: window.TranslateOptions = { 391 x: 500.0, 392 y: 0.0, 393 z: 0.0 394 }; 395 try { 396 toWindow?.translate(obj); // 设置动画过程中的属性转换 397 }catch(exception){ 398 console.error('Failed to translate. Cause: ' + JSON.stringify(exception)); 399 } 400 }); 401 console.info('LOCAL-TEST complete transition end'); 402 }); 403 } 404 405 build() { 406 Column() { 407 Text(this.message) 408 .fontSize(24) 409 .fontWeight(FontWeight.Normal) 410 .margin({left: 10, top: 10}) 411 412 Button(){ 413 Text("CreateSub") 414 .fontSize(24) 415 .fontWeight(FontWeight.Normal) 416 }.width(220).height(68) 417 .margin({left: 10, top: 10}) 418 .onClick(() => { 419 this.CreateTransferSubWindow(); 420 }) 421 422 Button(){ 423 Text("Show") 424 .fontSize(24) 425 .fontWeight(FontWeight.Normal) 426 }.width(220).height(68) 427 .margin({left: 10, top: 10}) 428 .onClick(() => { 429 this.ShowSUBWindow(); 430 }) 431 432 Button(){ 433 Text("Hide") 434 .fontSize(24) 435 .fontWeight(FontWeight.Normal) 436 }.width(220).height(68) 437 .margin({left: 10, top: 10}) 438 .onClick(() => { 439 this.HideSUBWindow(); 440 }) 441 }.width('100%').height('100%') 442 } 443} 444```