1# 组件导航 (Navigation)(推荐) 2 3组件导航(Navigation)主要用于实现页面间以及组件内部的页面跳转,支持在不同组件间传递跳转参数,提供灵活的跳转栈操作,从而更便捷地实现对不同页面的访问和复用。本文将从组件导航(Navigation)的显示模式、路由操作、子页面管理、跨包跳转以及跳转动效等几个方面进行详细介绍。 4 5[Navigation](../reference/apis-arkui/arkui-ts/ts-basic-components-navigation.md)是路由导航的根视图容器,一般作为页面(@Entry)的根容器,包括单栏(Stack)、分栏(Split)和自适应(Auto)三种显示模式。Navigation组件适用于模块内和跨模块的路由切换,通过组件级路由能力实现更加自然流畅的转场体验,并提供多种标题栏样式来呈现更好的标题和内容联动效果。[一次开发,多端部署](../key-features/multi-device-app-dev/introduction.md)场景下,Navigation组件能够自动适配窗口显示大小,在窗口较大的场景下自动切换分栏展示效果。 6 7Navigation组件主要包含导航页和子页。导航页由标题栏(包含菜单栏)、内容区和工具栏组成,可以通过[hideNavBar](../reference/apis-arkui/arkui-ts/ts-basic-components-navigation.md#hidenavbar9)属性进行隐藏,导航页不存在页面栈中,与子页,以及子页之间可以通过路由操作进行切换。 8 9在API Version 9上,Navigation需要配合[NavRouter](../reference/apis-arkui/arkui-ts/ts-basic-components-navrouter.md)组件实现页面路由。从API Version 10开始,更推荐使用[NavPathStack](../reference/apis-arkui/arkui-ts/ts-basic-components-navigation.md#navpathstack10)实现页面路由。 10 11 12## 设置页面显示模式 13 14Navigation组件通过mode属性设置页面的显示模式。 15 16- 自适应模式 17 18 Navigation组件默认为自适应模式,此时mode属性为NavigationMode.Auto。自适应模式下,当页面宽度大于等于一定阈值( API version 9及以前:520vp,API version 10及以后:600vp )时,Navigation组件采用分栏模式,反之采用单栏模式。 19 20 21 ``` 22 Navigation() { 23 // ... 24 } 25 .mode(NavigationMode.Auto) 26 ``` 27 28- 单页面模式 29 30 **图1** 单页面布局示意图 31 32  33 34 将mode属性设置为NavigationMode.Stack,Navigation组件即可设置为单页面显示模式。 35 36 37 ```ts 38 Navigation() { 39 // ... 40 } 41 .mode(NavigationMode.Stack) 42 ``` 43 44  45 46- 分栏模式 47 48 **图2** 分栏布局示意图 49 50  51 52 将mode属性设置为NavigationMode.Split,Navigation组件即可设置为分栏显示模式。 53 54 55 ```ts 56 @Entry 57 @Component 58 struct NavigationExample { 59 @State TooTmp: ToolbarItem = {'value': "func", 'icon': "./image/ic_public_highlights.svg", 'action': ()=> {}} 60 @Provide('pageInfos') pageInfos: NavPathStack = new NavPathStack() 61 private arr: number[] = [1, 2, 3]; 62 63 @Builder 64 PageMap(name: string) { 65 if (name === "NavDestinationTitle1") { 66 pageOneTmp() 67 } else if (name === "NavDestinationTitle2") { 68 pageTwoTmp() 69 } else if (name === "NavDestinationTitle3") { 70 pageThreeTmp() 71 } 72 } 73 74 build() { 75 Column() { 76 Navigation(this.pageInfos) { 77 TextInput({ placeholder: 'search...' }) 78 .width("90%") 79 .height(40) 80 .backgroundColor('#FFFFFF') 81 82 List({ space: 12 }) { 83 ForEach(this.arr, (item:number) => { 84 ListItem() { 85 Text("Page" + item) 86 .width("100%") 87 .height(72) 88 .backgroundColor('#FFFFFF') 89 .borderRadius(24) 90 .fontSize(16) 91 .fontWeight(500) 92 .textAlign(TextAlign.Center) 93 .onClick(()=>{ 94 this.pageInfos.pushPath({ name: "NavDestinationTitle" + item}) 95 }) 96 } 97 }, (item:number) => item.toString()) 98 } 99 .width("90%") 100 .margin({ top: 12 }) 101 } 102 .title("主标题") 103 .mode(NavigationMode.Split) 104 .navDestination(this.PageMap) 105 .menus([ 106 {value: "", icon: "./image/ic_public_search.svg", action: ()=> {}}, 107 {value: "", icon: "./image/ic_public_add.svg", action: ()=> {}}, 108 {value: "", icon: "./image/ic_public_add.svg", action: ()=> {}}, 109 {value: "", icon: "./image/ic_public_add.svg", action: ()=> {}}, 110 {value: "", icon: "./image/ic_public_add.svg", action: ()=> {}} 111 ]) 112 .toolbarConfiguration([this.TooTmp, this.TooTmp, this.TooTmp]) 113 } 114 .height('100%') 115 .width('100%') 116 .backgroundColor('#F1F3F5') 117 } 118 } 119 120 // PageOne.ets 121 @Component 122 export struct pageOneTmp { 123 @Consume('pageInfos') pageInfos: NavPathStack; 124 build() { 125 NavDestination() { 126 Column() { 127 Text("NavDestinationContent1") 128 }.width('100%').height('100%') 129 }.title("NavDestinationTitle1") 130 .onBackPressed(() => { 131 const popDestinationInfo = this.pageInfos.pop() // 弹出路由栈栈顶元素 132 console.log('pop' + '返回值' + JSON.stringify(popDestinationInfo)) 133 return true 134 }) 135 } 136 } 137 138 // PageTwo.ets 139 @Component 140 export struct pageTwoTmp { 141 @Consume('pageInfos') pageInfos: NavPathStack; 142 build() { 143 NavDestination() { 144 Column() { 145 Text("NavDestinationContent2") 146 }.width('100%').height('100%') 147 }.title("NavDestinationTitle2") 148 .onBackPressed(() => { 149 const popDestinationInfo = this.pageInfos.pop() // 弹出路由栈栈顶元素 150 console.log('pop' + '返回值' + JSON.stringify(popDestinationInfo)) 151 return true 152 }) 153 } 154 } 155 156 // PageThree.ets 157 @Component 158 export struct pageThreeTmp { 159 @Consume('pageInfos') pageInfos: NavPathStack; 160 build() { 161 NavDestination() { 162 Column() { 163 Text("NavDestinationContent3") 164 }.width('100%').height('100%') 165 }.title("NavDestinationTitle3") 166 .onBackPressed(() => { 167 const popDestinationInfo = this.pageInfos.pop() // 弹出路由栈栈顶元素 168 console.log('pop' + '返回值' + JSON.stringify(popDestinationInfo)) 169 return true 170 }) 171 } 172 } 173 ``` 174 175  176 177 178## 设置标题栏模式 179 180标题栏在界面顶部,用于呈现界面名称和操作入口,Navigation组件通过titleMode属性设置标题栏模式。 181 182> **说明:** 183> Navigation或NavDestination未设置主副标题并且没有返回键时,不显示标题栏。 184 185- Mini模式 186 187 普通型标题栏,用于一级页面不需要突出标题的场景。 188 189 **图3** Mini模式标题栏 190 191  192 193 194 ```ts 195 Navigation() { 196 // ... 197 } 198 .titleMode(NavigationTitleMode.Mini) 199 ``` 200 201 202- Full模式 203 204 强调型标题栏,用于一级页面需要突出标题的场景。 205 206 **图4** Full模式标题栏 207 208  209 210 211 ```ts 212 Navigation() { 213 // ... 214 } 215 .titleMode(NavigationTitleMode.Full) 216 ``` 217 218 219## 设置菜单栏 220 221菜单栏位于Navigation组件的右上角,开发者可以通过menus属性进行设置。menus支持Array<[NavigationMenuItem](../reference/apis-arkui/arkui-ts/ts-basic-components-navigation.md#navigationmenuitem)>和[CustomBuilder](../reference/apis-arkui/arkui-ts/ts-types.md#custombuilder8)两种参数类型。使用Array<NavigationMenuItem>类型时,竖屏最多支持显示3个图标,横屏最多支持显示5个图标,多余的图标会被放入自动生成的更多图标。 222 223**图5** 设置了3个图标的菜单栏 224 225 226 227```ts 228let TooTmp: NavigationMenuItem = {'value': "", 'icon': "./image/ic_public_highlights.svg", 'action': ()=> {}} 229Navigation() { 230 // ... 231} 232.menus([TooTmp, 233 TooTmp, 234 TooTmp]) 235``` 236 237图片也可以引用resources中的资源。 238 239```ts 240let TooTmp: NavigationMenuItem = {'value': "", 'icon': "resources/base/media/ic_public_highlights.svg", 'action': ()=> {}} 241Navigation() { 242 // ... 243} 244.menus([TooTmp, 245 TooTmp, 246 TooTmp]) 247``` 248 249**图6** 设置了4个图标的菜单栏 250 251 252 253```ts 254let TooTmp: NavigationMenuItem = {'value': "", 'icon': "./image/ic_public_highlights.svg", 'action': ()=> {}} 255Navigation() { 256 // ... 257} 258// 竖屏最多支持显示3个图标,多余的图标会被放入自动生成的更多图标。 259.menus([TooTmp, 260 TooTmp, 261 TooTmp, 262 TooTmp]) 263``` 264 265 266## 设置工具栏 267 268工具栏位于Navigation组件的底部,开发者可以通过[toolbarConfiguration](../reference/apis-arkui/arkui-ts/ts-basic-components-navigation.md#toolbarconfiguration10)属性进行设置。 269 270 271 **图7** 工具栏 272 273 274 275```ts 276let TooTmp: ToolbarItem = {'value': "func", 'icon': "./image/ic_public_highlights.svg", 'action': ()=> {}} 277let TooBar: ToolbarItem[] = [TooTmp,TooTmp,TooTmp] 278Navigation() { 279 // ... 280} 281.toolbarConfiguration(TooBar) 282``` 283 284## 路由操作 285 286Navigation路由相关的操作都是基于页面栈[NavPathStack](../reference/apis-arkui/arkui-ts/ts-basic-components-navigation.md#navpathstack10)提供的方法进行,每个Navigation都需要创建并传入一个NavPathStack对象,用于管理页面。主要涉及页面跳转、页面返回、页面替换、页面删除、参数获取、路由拦截等功能。 287 288从API version 12开始,页面栈允许被继承。开发者可以在派生类中自定义属性和方法,也可以重写父类的方法。派生类对象可以替代基类NavPathStack对象使用。具体示例代码参见:[页面栈继承示例代码](../reference/apis-arkui/arkui-ts/ts-basic-components-navigation.md#示例10定义路由栈派生类)。 289 290> **说明:** 291> 292> 不建议开发者通过监听生命周期的方式管理自己的页面栈。 293 294不建议开发者通过监听生命周期的方式管理自己的页面栈。 295 296```ts 297@Entry 298@Component 299struct Index { 300 // 创建一个页面栈对象并传入Navigation 301 pageStack: NavPathStack = new NavPathStack() 302 303 build() { 304 Navigation(this.pageStack) { 305 } 306 .title('Main') 307 } 308} 309``` 310 311### 页面跳转 312 313NavPathStack通过Push相关的接口去实现页面跳转的功能,主要分为以下三类: 314 3151. 普通跳转,通过页面的name去跳转,并可以携带param。 316 317 ```ts 318 this.pageStack.pushPath({ name: "PageOne", param: "PageOne Param" }) 319 this.pageStack.pushPathByName("PageOne", "PageOne Param") 320 ``` 321 3222. 带返回回调的跳转,跳转时添加onPop回调,能在页面出栈时获取返回信息,并进行处理。 323 324 ```ts 325 this.pageStack.pushPathByName('PageOne', "PageOne Param", (popInfo) => { 326 console.log('Pop page name is: ' + popInfo.info.name + ', result: ' + JSON.stringify(popInfo.result)) 327 }); 328 ``` 329 3303. 带错误码的跳转,跳转结束会触发异步回调,返回错误码信息。 331 332 ```ts 333 this.pageStack.pushDestination({name: "PageOne", param: "PageOne Param"}) 334 .catch((error: BusinessError) => { 335 console.error(`Push destination failed, error code = ${error.code}, error.message = ${error.message}.`); 336 }).then(() => { 337 console.info('Push destination succeed.'); 338 }); 339 this.pageStack.pushDestinationByName("PageOne", "PageOne Param") 340 .catch((error: BusinessError) => { 341 console.error(`Push destination failed, error code = ${error.code}, error.message = ${error.message}.`); 342 }).then(() => { 343 console.info('Push destination succeed.'); 344 }); 345 ``` 346 347### 页面返回 348 349NavPathStack通过Pop相关接口去实现页面返回功能。 350 351```ts 352// 返回到上一页 353this.pageStack.pop() 354// 返回到上一个PageOne页面 355this.pageStack.popToName("PageOne") 356// 返回到索引为1的页面 357this.pageStack.popToIndex(1) 358// 返回到根首页(清除栈中所有页面) 359this.pageStack.clear() 360``` 361 362### 页面替换 363 364NavPathStack通过Replace相关接口去实现页面替换功能。 365 366```ts 367// 将栈顶页面替换为PageOne 368this.pageStack.replacePath({ name: "PageOne", param: "PageOne Param" }) 369this.pageStack.replacePathByName("PageOne", "PageOne Param") 370// 带错误码的替换,跳转结束会触发异步回调,返回错误码信息 371this.pageStack.replaceDestination({name: "PageOne", param: "PageOne Param"}) 372 .catch((error: BusinessError) => { 373 console.error(`Replace destination failed, error code = ${error.code}, error.message = ${error.message}.`); 374 }).then(() => { 375 console.info('Replace destination succeed.'); 376 }) 377``` 378 379### 页面删除 380 381NavPathStack通过Remove相关接口去实现删除页面栈中特定页面的功能。 382 383```ts 384// 删除栈中name为PageOne的所有页面 385this.pageStack.removeByName("PageOne") 386// 删除指定索引的页面 387this.pageStack.removeByIndexes([1,3,5]) 388// 删除指定id的页面 389this.pageStack.removeByNavDestinationId("1"); 390``` 391 392### 移动页面 393 394NavPathStack通过Move相关接口去实现移动页面栈中特定页面到栈顶的功能。 395 396```ts 397// 移动栈中name为PageOne的页面到栈顶 398this.pageStack.moveToTop("PageOne"); 399// 移动栈中索引为1的页面到栈顶 400this.pageStack.moveIndexToTop(1); 401``` 402 403### 参数获取 404 405NavPathStack通过Get相关接口去获取页面的一些参数。 406 407```ts 408// 获取栈中所有页面name集合 409this.pageStack.getAllPathName() 410// 获取索引为1的页面参数 411this.pageStack.getParamByIndex(1) 412// 获取PageOne页面的参数 413this.pageStack.getParamByName("PageOne") 414// 获取PageOne页面的索引集合 415this.pageStack.getIndexByName("PageOne") 416``` 417 418### 路由拦截 419 420NavPathStack提供了[setInterception](../reference/apis-arkui/arkui-ts/ts-basic-components-navigation.md#setinterception12)方法,用于设置Navigation页面跳转拦截回调。该方法需要传入一个NavigationInterception对象,该对象包含三个回调函数: 421 422| 名称 | 描述 | 423| ------------ | ------------------------------------------------------ | 424| willShow | 页面跳转前回调,允许操作栈,在当前跳转生效。 | 425| didShow | 页面跳转后回调,在该回调中操作栈会在下一次跳转生效。 | 426| modeChange | Navigation单双栏显示状态发生变更时触发该回调。 | 427 428> **说明:** 429> 430> 无论是哪个回调,在进入回调时页面栈都已经发生了变化。 431 432开发者可以在willShow回调中通过修改路由栈来实现路由拦截重定向的能力。 433 434```ts 435this.pageStack.setInterception({ 436 willShow: (from: NavDestinationContext | "navBar", to: NavDestinationContext | "navBar", 437 operation: NavigationOperation, animated: boolean) => { 438 if (typeof to === "string") { 439 console.log("target page is navigation home page."); 440 return; 441 } 442 // 将跳转到PageTwo的路由重定向到PageOne 443 let target: NavDestinationContext = to as NavDestinationContext; 444 if (target.pathInfo.name === 'PageTwo') { 445 target.pathStack.pop(); 446 target.pathStack.pushPathByName('PageOne', null); 447 } 448 } 449}) 450``` 451 452## 子页面 453 454[NavDestination](../reference/apis-arkui/arkui-ts/ts-basic-components-navdestination.md)是Navigation子页面的根容器,用于承载子页面的一些特殊属性以及生命周期等。NavDestination可以设置独立的标题栏和菜单栏等属性,使用方法与Navigation相同。NavDestination也可以通过mode属性设置不同的显示类型,用于满足不同页面的诉求。 455 456### 页面显示类型 457 458- 标准类型 459 460 NavDestination组件默认为标准类型,此时mode属性为NavDestinationMode.STANDARD。标准类型的NavDestination的生命周期跟随其在NavPathStack页面栈中的位置变化而改变。 461 462- 弹窗类型 463 464 NavDestination设置mode为NavDestinationMode.DIALOG弹窗类型,此时整个NavDestination默认透明显示。弹窗类型的NavDestination显示和消失时不会影响下层标准类型的NavDestination的显示和生命周期,两者可以同时显示。 465 466 ```ts 467 // Dialog NavDestination 468 @Entry 469 @Component 470 struct Index { 471 @Provide('NavPathStack') pageStack: NavPathStack = new NavPathStack() 472 473 @Builder 474 PagesMap(name: string) { 475 if (name == 'DialogPage') { 476 DialogPage() 477 } 478 } 479 480 build() { 481 Navigation(this.pageStack) { 482 Button('Push DialogPage') 483 .margin(20) 484 .width('80%') 485 .onClick(() => { 486 this.pageStack.pushPathByName('DialogPage', ''); 487 }) 488 } 489 .mode(NavigationMode.Stack) 490 .title('Main') 491 .navDestination(this.PagesMap) 492 } 493 } 494 495 @Component 496 export struct DialogPage { 497 @Consume('NavPathStack') pageStack: NavPathStack; 498 499 build() { 500 NavDestination() { 501 Stack({ alignContent: Alignment.Center }) { 502 Column() { 503 Text("Dialog NavDestination") 504 .fontSize(20) 505 .margin({ bottom: 100 }) 506 Button("Close").onClick(() => { 507 this.pageStack.pop() 508 }).width('30%') 509 } 510 .justifyContent(FlexAlign.Center) 511 .backgroundColor(Color.White) 512 .borderRadius(10) 513 .height('30%') 514 .width('80%') 515 }.height("100%").width('100%') 516 } 517 .backgroundColor('rgba(0,0,0,0.5)') 518 .hideTitleBar(true) 519 .mode(NavDestinationMode.DIALOG) 520 } 521 } 522 ``` 523  524 525### 页面生命周期 526 527Navigation作为路由容器,其生命周期承载在NavDestination组件上,以组件事件的形式开放。 528 529其生命周期大致可分为三类,自定义组件生命周期、通用组件生命周期和自有生命周期。其中,[aboutToAppear](../reference/apis-arkui/arkui-ts/ts-custom-component-lifecycle.md#abouttoappear)和[aboutToDisappear](../reference/apis-arkui/arkui-ts/ts-custom-component-lifecycle.md#abouttodisappear)是自定义组件的生命周期(NavDestination外层包含的自定义组件),[OnAppear](../reference/apis-arkui/arkui-ts/ts-universal-events-show-hide.md#onappear)和[OnDisappear](../reference/apis-arkui/arkui-ts/ts-universal-events-show-hide.md#ondisappear)是组件的通用生命周期。剩下的六个生命周期为NavDestination独有。 530 531生命周期时序如下图所示: 532 533 534 535- **aboutToAppear**:在创建自定义组件后,执行其build()函数之前执行(NavDestination创建之前),允许在该方法中改变状态变量,更改将在后续执行build()函数中生效。 536- **onWillAppear**:NavDestination创建后,挂载到组件树之前执行,在该方法中更改状态变量会在当前帧显示生效。 537- **onAppear**:通用生命周期事件,NavDestination组件挂载到组件树时执行。 538- **onWillShow**:NavDestination组件布局显示之前执行,此时页面不可见(应用切换到前台不会触发)。 539- **onShown**:NavDestination组件布局显示之后执行,此时页面已完成布局。 540- **onWillHide**:NavDestination组件触发隐藏之前执行(应用切换到后台不会触发)。 541- **onHidden**:NavDestination组件触发隐藏后执行(非栈顶页面push进栈,栈顶页面pop出栈或应用切换到后台)。 542- **onWillDisappear**:NavDestination组件即将销毁之前执行,如果有转场动画,会在动画前触发(栈顶页面pop出栈)。 543- **onDisappear**:通用生命周期事件,NavDestination组件从组件树上卸载销毁时执行。 544- **aboutToDisappear**:自定义组件析构销毁之前执行,不允许在该方法中改变状态变量。 545 546### 页面监听和查询 547 548为了方便组件跟页面解耦,在NavDestination子页面内部的自定义组件可以通过全局方法监听或查询到页面的一些状态信息。 549 550- 页面信息查询 551 552 自定义组件提供[queryNavDestinationInfo](../reference/apis-arkui/arkui-ts/ts-custom-component-api.md#querynavdestinationinfo)方法,可以在NavDestination内部查询到当前所属页面的信息,返回值为[NavDestinationInfo](../reference/apis-arkui/js-apis-arkui-observer.md#navdestinationinfo),若查询不到则返回undefined。 553 554 ```ts 555 import { uiObserver } from '@kit.ArkUI'; 556 557 // NavDestination内的自定义组件 558 @Component 559 struct MyComponent { 560 navDesInfo: uiObserver.NavDestinationInfo | undefined 561 562 aboutToAppear(): void { 563 this.navDesInfo = this.queryNavDestinationInfo(); 564 } 565 566 build() { 567 Column() { 568 Text("所属页面Name: " + this.navDesInfo?.name) 569 }.width('100%').height('100%') 570 } 571 } 572 ``` 573- 页面状态监听 574 575 通过[observer.on('navDestinationUpdate')](../reference/apis-arkui/js-apis-arkui-observer.md#observeronnavdestinationupdate)提供的注册接口可以注册NavDestination生命周期变化的监听,使用方式如下: 576 577 ```ts 578 uiObserver.on('navDestinationUpdate', (info) => { 579 console.info('NavDestination state update', JSON.stringify(info)); 580 }); 581 ``` 582 583 也可以注册页面切换的状态回调,能在页面发生路由切换的时候拿到对应的页面信息[NavDestinationSwitchInfo](..//reference/apis-arkui/js-apis-arkui-observer.md#navdestinationswitchinfo12),并且提供了UIAbilityContext和UIContext不同范围的监听: 584 585 ```ts 586 // 在UIAbility中使用 587 import { UIContext, uiObserver } from '@kit.ArkUI'; 588 589 // callBackFunc 是开发者定义的监听回调函数 590 function callBackFunc(info: uiObserver.NavDestinationSwitchInfo) {} 591 uiObserver.on('navDestinationSwitch', this.context, callBackFunc); 592 593 // 可以通过窗口的getUIContext()方法获取对应的UIContent 594 uiContext: UIContext | null = null; 595 uiObserver.on('navDestinationSwitch', this.uiContext, callBackFunc); 596 ``` 597 598## 页面转场 599 600Navigation默认提供了页面切换的转场动画,通过页面栈操作时,会触发不同的转场效果(API version 13之前,Dialog类型的页面默认无转场动画。从API version13开始,Dialog类型的页面支持系统转场动画。),Navigation也提供了关闭系统转场、自定义转场以及共享元素转场的能力。 601 602### 关闭转场 603 604- 全局关闭 605 606 Navigation通过NavPathStack中提供的[disableAnimation](../reference/apis-arkui/arkui-ts/ts-basic-components-navigation.md#disableanimation11)方法可以在当前Navigation中关闭或打开所有转场动画。 607 ```ts 608 pageStack: NavPathStack = new NavPathStack() 609 610 aboutToAppear(): void { 611 this.pageStack.disableAnimation(true) 612 } 613 ``` 614- 单次关闭 615 616 NavPathStack中提供的Push、Pop、Replace等接口中可以设置animated参数,默认为true表示有转场动画,需要单次关闭转场动画可以置为false,不影响下次转场动画。 617 ```ts 618 pageStack: NavPathStack = new NavPathStack() 619 620 this.pageStack.pushPath({ name: "PageOne" }, false) 621 this.pageStack.pop(false) 622 ``` 623 624### 自定义转场 625 626Navigation通过[customNavContentTransition](../reference/apis-arkui/arkui-ts/ts-basic-components-navigation.md#customnavcontenttransition11)事件提供自定义转场动画的能力,通过如下三步可以定义一个自定义的转场动画。 627 6281. 构建一个自定义转场动画工具类CustomNavigationUtils,通过一个Map管理各个页面自定义动画对象CustomTransition,页面在创建的时候将自己的自定义转场动画对象注册进去,销毁的时候解注册; 6292. 实现一个转场协议对象[NavigationAnimatedTransition](..//reference/apis-arkui/arkui-ts/ts-basic-components-navigation.md#navigationanimatedtransition11),其中timeout属性表示转场结束的超时时间,默认为1000ms,transition属性为自定义的转场动画方法,开发者要在这里实现自己的转场动画逻辑,系统会在转场开始时调用该方法,onTransitionEnd为转场结束时的回调。 6303. 调用customNavContentTransition方法,返回实现的转场协议对象,如果返回undefined,则使用系统默认转场。 631 632具体示例代码可以参考[Navigation自定义转场示例](../reference/apis-arkui/arkui-ts/ts-basic-components-navigation.md#示例3设置可交互转场动画)。 633 634### 共享元素转场 635 636NavDestination之间切换时可以通过[geometryTransition](../reference/apis-arkui/arkui-ts/ts-transition-animation-geometrytransition.md#geometrytransition)实现共享元素转场。配置了共享元素转场的页面同时需要关闭系统默认的转场动画。 6371. 为需要实现共享元素转场的组件添加geometryTransition属性,id参数必须在两个NavDestination之间保持一致。 638 639 ```ts 640 // 起始页配置共享元素id 641 NavDestination() { 642 Column() { 643 // ... 644 Image($r('app.media.startIcon')) 645 .geometryTransition('sharedId') 646 .width(100) 647 .height(100) 648 } 649 } 650 .title('FromPage') 651 652 // 目的页配置共享元素id 653 NavDestination() { 654 Column() { 655 // ... 656 Image($r('app.media.startIcon')) 657 .geometryTransition('sharedId') 658 .width(200) 659 .height(200) 660 } 661 } 662 .title('ToPage') 663 ``` 664 6652. 将页面路由的操作,放到animateTo动画闭包中,配置对应的动画参数以及关闭系统默认的转场。 666 667 ```ts 668 NavDestination() { 669 Column() { 670 Button('跳转目的页') 671 .width('80%') 672 .height(40) 673 .margin(20) 674 .onClick(() => { 675 this.getUIContext()?.animateTo({ duration: 1000 }, () => { 676 this.pageStack.pushPath({ name: 'ToPage' }, false) 677 }) 678 }) 679 } 680 } 681 .title('FromPage') 682 ``` 683 684## 跨包动态路由 685 686通过静态import页面再进行路由跳转的方式会造成不同模块之间的依赖耦合,以及首页加载时间长等问题。 687 688动态路由设计的初衷旨在解决多个模块(HAR/HSP)能够复用相同的业务逻辑,实现各业务模块间的解耦,同时支持路由功能的扩展与整合。 689 690**动态路由的优势:** 691 692- 路由定义除了跳转的URL以外,可以丰富的配置扩展信息,如横竖屏默认模式,是否需要鉴权等等,做路由跳转时统一处理。 693- 给每个路由页面设置一个名字,按照名称进行跳转而不是文件路径。 694- 页面的加载可以使用动态import(按需加载),防止首个页面加载大量代码导致卡顿。 695 696动态路由提供[系统路由表](#系统路由表)和[自定义路由表](#自定义路由表)两种实现方式。 697 698- 系统路由表相对自定义路由表,使用更简单,只需要添加对应页面跳转配置项,即可实现页面跳转。 699 700- 自定义路由表使用起来更复杂,但是可以根据应用业务进行定制处理。 701 702支持自定义路由表和系统路由表混用。 703 704### 系统路由表 705 706从API version 12开始,Navigation支持使用系统路由表的方式进行动态路由。各业务模块([HSP](../quick-start/in-app-hsp.md)/[HAR](../quick-start/har-package.md))中需要独立配置route_map.json文件,在触发路由跳转时,应用只需要通过NavPathStack提供的路由方法,传入需要路由的页面配置名称,此时系统会自动完成路由模块的动态加载、页面组件构建,并完成路由跳转,从而实现了开发层面的模块解耦。系统路由表不支持预览器,跨平台及模拟器。其主要步骤如下: 707 7081. 在跳转目标模块的配置文件module.json5添加路由表配置: 709 710 ```json 711 { 712 "module" : { 713 "routerMap": "$profile:route_map" 714 } 715 } 716 ``` 7172. 添加完路由配置文件地址后,需要在工程resources/base/profile中创建route_map.json文件。添加如下配置信息: 718 719 ```json 720 { 721 "routerMap": [ 722 { 723 "name": "PageOne", 724 "pageSourceFile": "src/main/ets/pages/PageOne.ets", 725 "buildFunction": "PageOneBuilder", 726 "data": { 727 "description" : "this is PageOne" 728 } 729 } 730 ] 731 } 732 ``` 733 734 配置说明如下: 735 736 | 配置项 | 说明 | 737 |---|---| 738 | name | 跳转页面名称。| 739 | pageSourceFile | 跳转目标页在包内的路径,相对src目录的相对路径。| 740 | buildFunction | 跳转目标页的入口函数名称,必须以@Builder修饰。 | 741 | data | 应用自定义字段。可以通过配置项读取接口getConfigInRouteMap获取。| 742 7433. 在跳转目标页面中,需要配置入口Builder函数,函数名称需要和route_map.json配置文件中的buildFunction保持一致,否则在编译时会报错。 744 745 ```ts 746 // 跳转页面入口函数 747 @Builder 748 export function PageOneBuilder() { 749 PageOne() 750 } 751 752 @Component 753 struct PageOne { 754 pathStack: NavPathStack = new NavPathStack() 755 756 build() { 757 NavDestination() { 758 } 759 .title('PageOne') 760 .onReady((context: NavDestinationContext) => { 761 this.pathStack = context.pathStack 762 }) 763 } 764 } 765 ``` 7664. 通过pushPathByName等路由接口进行页面跳转。(注意:此时Navigation中可以不用配置navDestination属性)。 767 768 ```ts 769 @Entry 770 @Component 771 struct Index { 772 pageStack : NavPathStack = new NavPathStack(); 773 774 build() { 775 Navigation(this.pageStack){ 776 }.onAppear(() => { 777 this.pageStack.pushPathByName("PageOne", null, false); 778 }) 779 .hideNavBar(true) 780 } 781 } 782 ``` 783 784### 自定义路由表 785 786开发者可以通过自定义路由表的方式来实现跨包动态路由,具体实现方法请参考<!--RP1-->[Navigation自定义动态路由](https://gitee.com/openharmony/applications_app_samples/tree/master/code/BasicFeature/ApplicationModels/DynamicRouter)<!--RP1End--> 示例。 787 788**实现方案:** 789 7901. 定义页面跳转配置项。 791 - 使用资源文件进行定义,通过资源管理[@ohos.resourceManager](../reference/apis-localization-kit/js-apis-resource-manager.md)在运行时对资源文件解析。 792 - 在ets文件中配置路由加载配置项,一般包括路由页面名称(即pushPath等接口中页面的别名),文件所在模块名称(hsp/har的模块名),加载页面在模块内的路径(相对src目录的路径)。 7932. 加载目标跳转页面,通过[动态import](../arkts-utils/arkts-dynamic-import.md)将跳转目标页面所在的模块在运行时加载, 在模块加载完成后,调用模块中的方法,通过import在模块的方法中加载模块中显示的目标页面,并返回页面加载完成后定义的Builder函数。 7943. 触发页面跳转,在Navigation的[navDestination](../reference/apis-arkui/arkui-ts/ts-basic-components-navigation.md#navdestination10)属性执行步骤2中加载的Builder函数,即可跳转到目标页面。 795<!--RP2--><!--RP2End-->