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