目錄
設置頁面顯示模式
設置標題欄模式
設置菜單欄
設置工具欄
路由操作
頁面跳轉
頁面返回
頁面替換
頁面刪除
移動頁面
參數獲取
路由攔截
單例跳轉
子頁面
頁面顯示類型
頁面生命周期
頁面監聽和查詢
頁面轉場
關閉轉場
自定義轉場
共享元素轉場
跨包動態路由
系統路由表
自定義路由表
導航示例
創建導航首頁
創建導航子頁
創建路由跳轉
附示例代碼
組件導航(Navigation)主要用于實現Navigation頁面(NavDestination)間的跳轉,支持在不同Navigation頁面間傳遞參數,提供靈活的跳轉棧操作,從而更便捷地實現對不同頁面的訪問和復用。本文將從組件導航(Navigation)的顯示模式、路由操作、子頁面管理、跨包跳轉以及跳轉動效等幾個方面進行詳細介紹。
Navigation是路由導航的根視圖容器,一般作為頁面(@Entry)的根容器,包括單欄(Stack)、分欄(Split)和自適應(Auto)三種顯示模式。Navigation組件適用于模塊內和跨模塊的路由切換,通過組件級路由能力實現更加自然流暢的轉場體驗,并提供多種標題欄樣式來呈現更好的標題和內容聯動效果。一次開發,多端部署場景下,Navigation組件能夠自動適配窗口顯示大小,在窗口較大的場景下自動切換分欄展示效果。
Navigation組件主要包含?導航頁和子頁。導航頁由標題欄(包含菜單欄)、內容區和工具欄組成,可以通過hideNavBarhideNavBarhideNavBar屬性進行隱藏,導航頁不存在路由棧中,與子頁,以及子頁之間可以通過路由操作進行切換。
從API version 10開始,更推薦使用NavPathStack實現頁面路由。
設置頁面顯示模式
Navigation組件通過mode屬性設置頁面的顯示模式。
- 自適應模式
Navigation組件默認為自適應模式,此時mode屬性為NavigationMode.Auto。自適應模式下,當頁面寬度大于等于一定閾值( API version 9及以前:520vp,API version 10及以后:600vp )時,Navigation組件采用分欄模式,反之采用單欄模式。
Navigation() {// ...
}
.mode(NavigationMode.Auto)
- 單頁面模式
單頁面模式適用于窄屏設備,發生路由跳轉時,整個頁面都會被替換。
圖1?單頁面布局示意圖
將mode屬性設置為NavigationMode.Stack,Navigation組件即可設置為單頁面顯示模式。
Navigation() {// ...
}
.mode(NavigationMode.Stack)
- ?分欄模式
分欄模式適用于寬屏設備,分為左右兩部分,發生路由跳轉時,只有右邊子頁會被替換。
圖2?分欄布局示意圖
將mode屬性設置為NavigationMode.Split,Navigation組件即可設置為分欄顯示模式。
@Entry
@Component
struct NavigationExample {@State TooTmp: ToolbarItem = {'value': "func",'icon': "./image/ic_public_highlights.svg", // 當前目錄image文件夾下的圖標資源'action': () => {}}@Provide('pageInfos') pageInfos: NavPathStack = new NavPathStack()private arr: number[] = [1, 2, 3];@BuilderPageMap(name: string) {if (name === "NavDestinationTitle1") {pageOneTmp();} else if (name === "NavDestinationTitle2") {pageTwoTmp();} else if (name === "NavDestinationTitle3") {pageThreeTmp();}}build() {Column() {Navigation(this.pageInfos) {TextInput({ placeholder: 'search...' }).width("90%").height(40).backgroundColor('#FFFFFF')List({ space: 12 }) {ForEach(this.arr, (item: number) => {ListItem() {Text("Page" + item).width("100%").height(72).backgroundColor('#FFFFFF').borderRadius(24).fontSize(16).fontWeight(500).textAlign(TextAlign.Center).onClick(() => {this.pageInfos.pushPath({ name: "NavDestinationTitle" + item });})}}, (item: number) => item.toString())}.width("90%").margin({ top: 12 })}.title("主標題").mode(NavigationMode.Split).navDestination(this.PageMap).menus([{value: "", icon: "./image/ic_public_search.svg", action: () => {}},{value: "", icon: "./image/ic_public_add.svg", action: () => {}},{value: "", icon: "./image/ic_public_add.svg", action: () => {}},{value: "", icon: "./image/ic_public_add.svg", action: () => {}},{value: "", icon: "./image/ic_public_add.svg", action: () => {}}]).toolbarConfiguration([this.TooTmp, this.TooTmp, this.TooTmp])}.height('100%').width('100%').backgroundColor('#F1F3F5')}
}// PageOne.ets
@Component
export struct pageOneTmp {@Consume('pageInfos') pageInfos: NavPathStack;build() {NavDestination() {Column() {Text("NavDestinationContent1")}.width('100%').height('100%')}.title("NavDestinationTitle1").onBackPressed(() => {const popDestinationInfo = this.pageInfos.pop(); // 彈出路由棧棧頂元素console.log('pop' + '返回值' + JSON.stringify(popDestinationInfo));return true;})}
}// PageTwo.ets
@Component
export struct pageTwoTmp {@Consume('pageInfos') pageInfos: NavPathStack;build() {NavDestination() {Column() {Text("NavDestinationContent2")}.width('100%').height('100%')}.title("NavDestinationTitle2").onBackPressed(() => {const popDestinationInfo = this.pageInfos.pop(); // 彈出路由棧棧頂元素console.log('pop' + '返回值' + JSON.stringify(popDestinationInfo));return true;})}
}// PageThree.ets
@Component
export struct pageThreeTmp {@Consume('pageInfos') pageInfos: NavPathStack;build() {NavDestination() {Column() {Text("NavDestinationContent3")}.width('100%').height('100%')}.title("NavDestinationTitle3").onBackPressed(() => {const popDestinationInfo = this.pageInfos.pop(); // 彈出路由棧棧頂元素console.log('pop' + '返回值' + JSON.stringify(popDestinationInfo));return true;})}
}
設置標題欄模式
標題欄在界面頂部,用于呈現界面名稱和操作入口,Navigation組件通過titleMode屬性設置標題欄模式。
Navigation或NavDestination未設置主副標題并且沒有返回鍵時,不顯示標題欄。
- Mini模式
普通型標題欄,用于一級頁面不需要突出標題的場景。
圖3?Mini模式標題欄
Navigation() {// ...
}
.titleMode(NavigationTitleMode.Mini)
- Full模式
強調型標題欄,用于一級頁面需要突出標題的場景。
圖4?Full模式標題欄
Navigation() {// ...
}
.titleMode(NavigationTitleMode.Full)
設置菜單欄
菜單欄位于Navigation組件的右上角,開發者可以通過menus屬性進行設置。menus支持Array<NavigationMenuItem>和CustomBuilder兩種參數類型。使用Array<NavigationMenuItem>類型時,豎屏最多支持顯示3個圖標,橫屏最多支持顯示5個圖標,多余的圖標會被放入自動生成的更多圖標。
圖5?設置了3個圖標的菜單欄
let TooTmp: NavigationMenuItem = {'value': "", 'icon': "./image/ic_public_highlights.svg", 'action': ()=> {}}
Navigation() {// ...
}
.menus([TooTmp,TooTmp,TooTmp])
圖片也可以引用resources中的資源。
let TooTmp: NavigationMenuItem = {'value': "", 'icon': "resources/base/media/ic_public_highlights.svg", 'action': ()=> {}}
Navigation() {// ...
}
.menus([TooTmp,TooTmp,TooTmp])
圖6?設置了4個圖標的菜單欄
let TooTmp: NavigationMenuItem = {'value': "", 'icon': "./image/ic_public_highlights.svg", 'action': ()=> {}}
Navigation() {// ...
}
// 豎屏最多支持顯示3個圖標,多余的圖標會被放入自動生成的更多圖標。
.menus([TooTmp,TooTmp,TooTmp,TooTmp])
設置工具欄
工具欄位于Navigation組件的底部,開發者可以通過toolbarConfiguration屬性進行設置。
圖7?工具欄
let TooTmp: ToolbarItem = {'value': "func", 'icon': "./image/ic_public_highlights.svg", 'action': ()=> {}};
let TooBar: ToolbarItem[] = [TooTmp,TooTmp,TooTmp];
Navigation() {// ...
}
.toolbarConfiguration(TooBar)
路由操作
Navigation路由相關的操作都是基于導航控制器NavPathStack提供的方法進行,每個Navigation都需要創建并傳入一個NavPathStack對象,用于管理頁面。主要涉及頁面跳轉、頁面返回、頁面替換、頁面刪除、參數獲取、路由攔截等功能。
從API version 12開始,導航控制器允許被繼承。開發者可以在派生類中自定義屬性和方法,也可以重寫父類的方法。派生類對象可以替代基類NavPathStack對象使用。Navigation中的NavDeatination頁面存在于NavPathStack中,以棧的結構管理,我們稱為路由棧。具體示例代碼參見:導航控制器繼承示例代碼。
說明
1.不建議開發者通過監聽生命周期的方式管理自己的路由棧。
2.在應用處于后臺狀態下,調用NavPathStack的棧操作方法,會在應用再次回到前臺狀態時觸發刷新。
@Entry
@Component
struct Index {// 創建一個導航控制器對象并傳入NavigationpageStack: NavPathStack = new NavPathStack();build() {Navigation(this.pageStack) {}.title('Main')}
}
頁面跳轉
NavPathStack通過Push相關的接口去實現頁面跳轉的功能,主要分為以下三類:
- 普通跳轉,通過頁面的name去跳轉,并可以攜帶param。
this.pageStack.pushPath({ name: "PageOne", param: "PageOne Param" }); this.pageStack.pushPathByName("PageOne", "PageOne Param");
- 帶返回回調的跳轉,跳轉時添加onPop回調,能在頁面出棧時獲取返回信息,并進行處理。
this.pageStack.pushPathByName('PageOne', "PageOne Param", (popInfo) => {console.log('Pop page name is: ' + popInfo.info.name + ', result: ' + JSON.stringify(popInfo.result)); });
- 帶錯誤碼的跳轉,跳轉結束會觸發異步回調,返回錯誤碼信息。
this.pageStack.pushDestination({name: "PageOne", param: "PageOne Param"}).catch((error: BusinessError) => {console.error(`Push destination failed, error code = ${error.code}, error.message = ${error.message}.`);}).then(() => {console.info('Push destination succeed.');}); this.pageStack.pushDestinationByName("PageOne", "PageOne Param").catch((error: BusinessError) => {console.error(`Push destination failed, error code = ${error.code}, error.message = ${error.message}.`);}).then(() => {console.info('Push destination succeed.');});
頁面返回
NavPathStack通過Pop相關接口去實現頁面返回功能。
// 返回到上一頁
this.pageStack.pop();
// 返回到上一個PageOne頁面
this.pageStack.popToName("PageOne");
// 返回到索引為1的頁面
this.pageStack.popToIndex(1);
// 返回到根首頁(清除棧中所有頁面)
this.pageStack.clear();
頁面替換
NavPathStack通過Replace相關接口去實現頁面替換功能。
// 將棧頂頁面替換為PageOne
this.pageStack.replacePath({ name: "PageOne", param: "PageOne Param" });
this.pageStack.replacePathByName("PageOne", "PageOne Param");
// 帶錯誤碼的替換,跳轉結束會觸發異步回調,返回錯誤碼信息
this.pageStack.replaceDestination({name: "PageOne", param: "PageOne Param"}).catch((error: BusinessError) => {console.error(`Replace destination failed, error code = ${error.code}, error.message = ${error.message}.`);}).then(() => {console.info('Replace destination succeed.');})
頁面刪除
NavPathStack通過Remove相關接口去實現刪除路由棧中特定頁面的功能。
// 刪除棧中name為PageOne的所有頁面
this.pageStack.removeByName("PageOne");
// 刪除指定索引的頁面
this.pageStack.removeByIndexes([1, 3, 5]);
// 刪除指定id的頁面
this.pageStack.removeByNavDestinationId("1");
移動頁面
NavPathStack通過Move相關接口去實現移動路由棧中特定頁面到棧頂的功能。
// 移動棧中name為PageOne的頁面到棧頂
this.pageStack.moveToTop("PageOne");
// 移動棧中索引為1的頁面到棧頂
this.pageStack.moveIndexToTop(1);
參數獲取
NavDestination子頁第一次創建時會觸發onReady回調,可以獲取此頁面對應的參數。
@Component
struct Page01 {pathStack: NavPathStack | undefined = undefined;pageParam: string = '';build() {NavDestination() {
...}.title('Page01').onReady((context: NavDestinationContext) => {this.pathStack = context.pathStack;this.pageParam = context.pathInfo.param as string;})}
}
其他業務場景,可以通過主動調用NavPathStack的Get相關接口去獲取指定頁面的參數。
// 獲取棧中所有頁面name集合
this.pageStack.getAllPathName();
// 獲取索引為1的頁面參數
this.pageStack.getParamByIndex(1);
// 獲取PageOne頁面的參數
this.pageStack.getParamByName("PageOne");
// 獲取PageOne頁面的索引集合
this.pageStack.getIndexByName("PageOne");
路由攔截
NavPathStack提供了setInterception方法,用于設置Navigation頁面跳轉攔截回調。該方法需要傳入一個NavigationInterception對象,該對象包含三個回調函數:
名稱 | 描述 |
---|---|
willShow | 頁面跳轉前回調,允許操作棧,在當前跳轉生效。 |
didShow | 頁面跳轉后回調,在該回調中操作棧會在下一次跳轉生效。 |
modeChange | Navigation單雙欄顯示狀態發生變更時觸發該回調。 |
無論是哪個回調,在進入回調時路由棧都已經發生了變化。
開發者可以在willShow回調中通過修改路由棧來實現路由攔截重定向的能力。
this.pageStack.setInterception({willShow: (from: NavDestinationContext | "navBar", to: NavDestinationContext | "navBar",operation: NavigationOperation, animated: boolean) => {if (typeof to === "string") {console.log("target page is navigation home page.");return;}// 將跳轉到PageTwo的路由重定向到PageOnelet target: NavDestinationContext = to as NavDestinationContext;if (target.pathInfo.name === 'PageTwo') {target.pathStack.pop();target.pathStack.pushPathByName('PageOne', null);}}
})
單例跳轉
通過設置LaunchMode為LaunchMode.MOVE_TO_TOP_SINGLETON或LaunchMode.POP_TO_SINGLETON,可以實現Navigation路由棧的單實例跳轉。單實例跳轉的規則如下:
- 當指定為LaunchMode.MOVE_TO_TOP_SINGLETON時,系統會從棧底到棧頂查找具有指定名稱的NavDestination。找到后,該頁面將被移動到棧頂(replace操作會用指定的NavDestination替換當前棧頂)。
- 若指定為LaunchMode.POP_TO_SINGLETON,系統同樣會從棧底到棧頂查找具有指定名稱的NavDestination。找到后,便會移除該NavDestination上方的所有頁面(replace操作會用指定的NavDestination替換當前棧頂)。
當棧中存在的NavDestination頁面通過單實例方式移動到棧頂時,將觸發onNewParam回調。
有關單實例跳轉的示例代碼,可以參考Navigation單例跳轉示例。
子頁面
NavDestination是Navigation子頁面的根容器,用于承載子頁面的一些特殊屬性以及生命周期等。NavDestination可以設置獨立的標題欄和菜單欄等屬性,使用方法與Navigation相同。NavDestination也可以通過mode屬性設置不同的顯示類型,用于滿足不同頁面的訴求。
頁面顯示類型
- 標準類型
NavDestination組件默認為標準類型,此時mode屬性為NavDestinationMode.STANDARD。標準類型的NavDestination的生命周期跟隨其在NavPathStack路由棧中的位置變化而改變。
- 彈窗類型
NavDestination設置mode為NavDestinationMode.DIALOG彈窗類型,此時整個NavDestination默認透明顯示。彈窗類型的NavDestination顯示和消失時不會影響下層標準類型的NavDestination的顯示和生命周期,兩者可以同時顯示。
// Dialog NavDestination
@Entry
@Componentstruct Index {@Provide('NavPathStack') pageStack: NavPathStack = new NavPathStack();@BuilderPagesMap(name: string) {if (name == 'DialogPage') {DialogPage();}}build() {Navigation(this.pageStack) {Button('Push DialogPage').margin(20).width('80%').onClick(() => {this.pageStack.pushPathByName('DialogPage', '');})}.mode(NavigationMode.Stack).title('Main').navDestination(this.PagesMap)}}@Componentexport struct DialogPage {@Consume('NavPathStack') pageStack: NavPathStack;build() {NavDestination() {Stack({ alignContent: Alignment.Center }) {Column() {Text("Dialog NavDestination").fontSize(20).margin({ bottom: 100 })Button("Close").onClick(() => {this.pageStack.pop();}).width('30%')}.justifyContent(FlexAlign.Center).backgroundColor(Color.White).borderRadius(10).height('30%').width('80%')}.height("100%").width('100%')}.backgroundColor('rgba(0,0,0,0.5)').hideTitleBar(true).mode(NavDestinationMode.DIALOG)}}
頁面生命周期
Navigation作為路由容器,其生命周期承載在NavDestination組件上,以組件事件的形式開放。
其生命周期大致可分為三類,自定義組件生命周期、通用組件生命周期和自有生命周期。其中,aboutToAppear和aboutToDisappear是自定義組件的生命周期(NavDestination外層包含的自定義組件),OnAppear和OnDisappear是組件的通用生命周期。剩下的生命周期為NavDestination獨有。
生命周期時序如下圖所示:
- aboutToAppear:在創建自定義組件后,執行其build()函數之前執行(NavDestination創建之前),允許在該方法中改變狀態變量,更改將在后續執行build()函數中生效。
- onWillAppear:NavDestination創建后,掛載到組件樹之前執行,在該方法中更改狀態變量會在當前幀顯示生效。
- onAppear:通用生命周期事件,NavDestination組件掛載到組件樹時執行。
- onWillShow:NavDestination組件布局顯示之前執行,此時頁面不可見(應用切換到前臺不會觸發)。
- onShown:NavDestination組件布局顯示之后執行,此時頁面已完成布局。
- onActive:NavDestination處于激活態(處于棧頂可操作,且上層無特殊組件遮擋)觸發。
- onWillHide:NavDestination組件觸發隱藏之前執行(應用切換到后臺不會觸發)。
- onInactive:NavDestination組件處于非激活態(處于非棧頂不可操作,或處于棧頂時上層有特殊組件遮擋)觸發。
- onHidden:NavDestination組件觸發隱藏后執行(非棧頂頁面push進棧,棧頂頁面pop出棧或應用切換到后臺)。
- onWillDisappear:NavDestination組件即將銷毀之前執行,如果有轉場動畫,會在動畫前觸發(棧頂頁面pop出棧)。
- onDisappear:通用生命周期事件,NavDestination組件從組件樹上卸載銷毀時執行。
- aboutToDisappear:自定義組件析構銷毀之前執行,不允許在該方法中改變狀態變量。
頁面監聽和查詢
為了方便組件跟頁面解耦,在NavDestination子頁面內部的自定義組件可以通過全局方法監聽或查詢到頁面的一些狀態信息。
- 頁面信息查詢
自定義組件提供queryNavDestinationInfo方法,可以在NavDestination內部查詢到當前所屬頁面的信息,返回值為NavDestinationInfo,若查詢不到則返回undefined。
import { uiObserver } from '@kit.ArkUI';// NavDestination內的自定義組件@Componentstruct MyComponent {navDesInfo: uiObserver.NavDestinationInfo | undefined;aboutToAppear(): void {this.navDesInfo = this.queryNavDestinationInfo();}build() {Column() {Text("所屬頁面Name: " + this.navDesInfo?.name)}.width('100%').height('100%')}}
- 頁面狀態監聽
通過observer.on('navDestinationUpdate')提供的注冊接口可以注冊NavDestination生命周期變化的監聽,使用方式如下:
uiObserver.on('navDestinationUpdate', (info) => {console.info('NavDestination state update', JSON.stringify(info));});
也可以注冊頁面切換的狀態回調,能在頁面發生路由切換的時候拿到對應的頁面信息NavDestinationSwitchInfo,并且提供了UIAbilityContext和UIContext不同范圍的監聽:
// 在UIAbility中使用import { UIContext, uiObserver } from '@kit.ArkUI';// callBackFunc 是開發者定義的監聽回調函數function callBackFunc(info: uiObserver.NavDestinationSwitchInfo) {}uiObserver.on('navDestinationSwitch', this.context, callBackFunc);// 可以通過窗口的getUIContext()方法獲取對應的UIContentuiContext: UIContext | null = null;uiObserver.on('navDestinationSwitch', this.uiContext, callBackFunc);
頁面轉場
Navigation默認提供了頁面切換的轉場動畫,通過導航控制器操作時,會觸發不同的轉場效果(API version 13之前,Dialog類型的頁面默認無轉場動畫。從API version13開始,Dialog類型的頁面支持系統轉場動畫。),Navigation也提供了關閉系統轉場、自定義轉場以及共享元素轉場的能力。
關閉轉場
- 全局關閉
Navigation通過NavPathStack中提供的disableAnimation方法可以在當前Navigation中關閉或打開所有轉場動畫。
pageStack: NavPathStack = new NavPathStack();aboutToAppear(): void {this.pageStack.disableAnimation(true); }
- 單次關閉
NavPathStack中提供的Push、Pop、Replace等接口中可以設置animated參數,默認為true表示有轉場動畫,需要單次關閉轉場動畫可以置為false,不影響下次轉場動畫。
pageStack: NavPathStack = new NavPathStack();this.pageStack.pushPath({ name: "PageOne" }, false); this.pageStack.pop(false);
自定義轉場
- Navigation自定義轉場??具體示例代碼可參考Navigation自定義轉場示例。
-
?Navigation自定義轉場動畫能力通過customNavContentTransition事件提供,可以通過以下三步定義自定義轉場動畫:
- 構建一個自定義轉場動畫工具類CustomNavigationUtils,通過一個Map管理各頁面的自定義動畫對象CustomTransition。頁面在創建時注冊其自定義轉場動畫對象,在銷毀時取消注冊。
- 實現一個轉場協議對象NavigationAnimatedTransition。其中,timeout屬性表示轉場結束的超時時間,默認為1000ms,transition屬性為自定義的轉場動畫方法。開發者需在此實現自己的轉場動畫邏輯,系統在轉場開始時會調用此方法,onTransitionEnd為轉場結束時的回調。
- 調用customNavContentTransition方法并返回實現的轉場協議對象,若返回undefined,則使用系統默認轉場。
-
具體示例代碼可參考Navigation自定義轉場示例。
- NavDestination自定義轉場
-
?NavDestination支持自定義轉場動畫,通過設置customTransition屬性即可實現單個頁面的自定義轉場效果。要實現這一功能,需完成以下步驟:
- 實現NavDestination的轉場代理,針對不同的堆棧操作類型返回自定義的轉場協議對象NavDestinationTransition。其中,event是必填參數,需在此處編寫自定義轉場動畫的邏輯;而onTransitionEnd、duration、curve與delay為可選參數,分別對應動畫結束后的回調、動畫持續時間、動畫曲線類型與開始前的延時。若在轉場代理中返回多個轉場協議對象,這些動畫效果將逐層疊加。
- 通過調用NavDestination組件的customTransition屬性,并傳入上述實現的轉場代理,完成自定義轉場的設置。
-
?具體示例代碼可以參考NavDestination自定義轉場示例
共享元素轉場
NavDestination之間切換時可以通過geometryTransition實現共享元素轉場。配置了共享元素轉場的頁面同時需要關閉系統默認的轉場動畫。
- 為需要實現共享元素轉場的組件添加geometryTransition屬性,id參數必須在兩個NavDestination之間保持一致。
// 起始頁配置共享元素id NavDestination() {Column() {// ...Image($r('app.media.startIcon')).geometryTransition('sharedId').width(100).height(100)} } .title('FromPage')// 目的頁配置共享元素id NavDestination() {Column() {// ...Image($r('app.media.startIcon')).geometryTransition('sharedId').width(200).height(200)} } .title('ToPage')
- 將頁面路由的操作,放到animateTo動畫閉包中,配置對應的動畫參數以及關閉系統默認的轉場。
NavDestination() {Column() {Button('跳轉目的頁').width('80%').height(40).margin(20).onClick(() => {this.getUIContext()?.animateTo({ duration: 1000 }, () => {this.pageStack.pushPath({ name: 'ToPage' }, false)});})} } .title('FromPage')
跨包動態路由
通過靜態import頁面再進行路由跳轉的方式會造成不同模塊之間的依賴耦合,以及首頁加載時間長等問題。
動態路由設計的初衷旨在解決多個模塊(HAR/HSP)能夠復用相同的業務邏輯,實現各業務模塊間的解耦,同時支持路由功能的擴展與整合。
動態路由的優勢:
- 路由定義除了跳轉的URL以外,可以豐富的配置擴展信息,如橫豎屏默認模式,是否需要鑒權等等,做路由跳轉時統一處理。
- 給每個路由頁面設置一個名字,按照名稱進行跳轉而不是文件路徑。
- 頁面的加載可以使用動態import(按需加載),防止首個頁面加載大量代碼導致卡頓。
動態路由提供系統路由表和自定義路由表兩種實現方式。
- 系統路由表相對自定義路由表,使用更簡單,只需要添加對應頁面跳轉配置項,即可實現頁面跳轉。
- 自定義路由表使用起來更復雜,但是可以根據應用業務進行定制處理。
支持自定義路由表和系統路由表混用。
系統路由表
系統路由表是動態路由的一種實現方式。從API version 12開始,Navigation支持使用系統路由表的方式進行動態路由。各業務模塊(HSP/HAR)中需要獨立配置route_map.json文件,在觸發路由跳轉時,應用只需要通過NavPathStack提供的路由方法,傳入需要路由的頁面配置名稱,此時系統會自動完成路由模塊的動態加載、頁面組件構建,并完成路由跳轉,從而實現了開發層面的模塊解耦。系統路由表支持模擬器但不支持預覽器。其主要步驟如下:
- 在跳轉目標模塊的配置文件module.json5添加路由表配置:
{"module" : {"routerMap": "$profile:route_map"}}
- 添加完路由配置文件地址后,需要在工程resources/base/profile中創建route_map.json文件。添加如下配置信息:
{"routerMap": [{"name": "PageOne","pageSourceFile": "src/main/ets/pages/PageOne.ets","buildFunction": "PageOneBuilder","data": {"description" : "this is PageOne"}}]}
-
配置說明如下:
配置項 說明 name 可自定義的跳轉頁面名稱。 pageSourceFile 跳轉目標頁在包內的路徑,相對src目錄的相對路徑。 buildFunction 跳轉目標頁的入口函數名稱,必須以@Builder修飾。 data 應用自定義字段。可以通過配置項讀取接口getConfigInRouteMap獲取。
- 在跳轉目標頁面中,需要配置入口Builder函數,函數名稱需要和route_map.json配置文件中的buildFunction保持一致,否則在編譯時會報錯。
// 跳轉頁面入口函數@Builderexport function PageOneBuilder() {PageOne();}@Componentstruct PageOne {pathStack: NavPathStack = new NavPathStack();build() {NavDestination() {}.title('PageOne').onReady((context: NavDestinationContext) => {this.pathStack = context.pathStack;})}}
- 通過pushPathByName等路由接口進行頁面跳轉。(注意:此時Navigation中可以不用配置navDestination屬性。)
@Entry@Componentstruct Index {pageStack : NavPathStack = new NavPathStack();build() {Navigation(this.pageStack){}.onAppear(() => {this.pageStack.pushPathByName("PageOne", null, false);}).hideNavBar(true)}}
自定義路由表
自定義路由表是動態路由的一種實現方式。開發者可以通過自定義路由表的方式來實現跨包動態路由,具體實現方法請參考Navigation自定義動態路由?示例。
實現方案:
- 定義頁面跳轉配置項。?
- 使用資源文件進行定義,通過資源管理@ohos.resourceManager在運行時對資源文件解析。
- 在ets文件中配置路由加載配置項,一般包括路由頁面名稱(即pushPath等接口中頁面的別名),文件所在模塊名稱(hsp/har的模塊名),加載頁面在模塊內的路徑(相對src目錄的路徑)。
- 加載目標跳轉頁面,通過動態import將跳轉目標頁面所在的模塊在運行時加載,在模塊加載完成后,調用模塊中的方法,通過import在模塊的方法中加載模塊中顯示的目標頁面,并返回頁面加載完成后定義的Builder函數。
- 觸發頁面跳轉,在Navigation的navDestination屬性執行步驟2中加載的Builder函數,即可跳轉到目標頁面。
導航示例
創建導航首頁
實現步驟為:
1.使用Navigation創建導航主頁,并創建導航控制器NavPathStack以此來實現不同頁面之間的跳轉。
2.在Navigation中增加List組件,來定義導航主頁中不同的一級界面。
3.在List內的組件添加onClick方法,并在其中使用導航控制器NavPathStack的pushPathByName方法,使組件可以在點擊之后從當前頁面跳轉到輸入參數name在路由表內對應的頁面。
@Entry
@Component
struct NavigationDemo {@Provide('pathInfos') pathInfos: NavPathStack = new NavPathStack();private listArray: Array<string> = ['WLAN', 'Bluetooth', 'Personal Hotspot', 'Connect & Share'];build() {Column() {Navigation(this.pathInfos) {TextInput({ placeholder: '輸入關鍵字搜索' }).width('90%').height(40).margin({ bottom: 10 })// 通過List定義導航的一級界面List({ space: 12, initialIndex: 0 }) {ForEach(this.listArray, (item: string) => {ListItem() {Row() {Row() {Text(`${item.slice(0, 1)}`).fontColor(Color.White).fontSize(14).fontWeight(FontWeight.Bold)}.width(30).height(30).backgroundColor('#a8a8a8').margin({ right: 20 }).borderRadius(20).justifyContent(FlexAlign.Center)Column() {Text(item).fontSize(16).margin({ bottom: 5 })}.alignItems(HorizontalAlign.Start)Blank()Row().width(12).height(12).margin({ right: 15 }).border({width: { top: 2, right: 2 },color: 0xcccccc}).rotate({ angle: 45 })}.borderRadius(15).shadow({ radius: 100, color: '#ededed' }).width('90%').alignItems(VerticalAlign.Center).padding({ left: 15, top: 15, bottom: 15 }).backgroundColor(Color.White)}.width('100%').onClick(() => {this.pathInfos.pushPathByName(`${item}`, '詳情頁面參數'); // 將name指定的NaviDestination頁面信息入棧,傳遞的參數為param})}, (item: string): string => item)}.listDirection(Axis.Vertical).edgeEffect(EdgeEffect.Spring).sticky(StickyStyle.Header).chainAnimation(false).width('100%')}.width('100%').mode(NavigationMode.Auto).title('設置') // 設置標題文字}.size({ width: '100%', height: '100%' }).backgroundColor(0xf4f4f5)}
}
創建導航子頁
導航子頁1實現步驟為:
1.使用NavDestination,來創建導航子頁PageOne。
2.創建導航控制器NavPathStack并在onReady時進行初始化,獲取當前所在的導航控制器,以此來實現不同頁面之間的跳轉。
3.在子頁面內的組件添加onClick,并在其中使用導航控制器NavPathStack的pop方法,使組件可以在點擊之后彈出路由棧棧頂元素實現頁面的返回。
//PageOne.ets
@Builder
export function PageOneBuilder(name: string, param: string) {PageOne({ name: name, value: param });
}@Component
export struct PageOne {pathInfos: NavPathStack = new NavPathStack();name: String = '';@State value: String = '';build() {NavDestination() {Column() {Text(`${this.name}設置頁面`).width('100%').fontSize(20).fontColor(0x333333).textAlign(TextAlign.Center).textShadow({radius: 2,offsetX: 4,offsetY: 4,color: 0x909399}).padding({ top: 30 })Text(`${JSON.stringify(this.value)}`).width('100%').fontSize(18).fontColor(0x666666).textAlign(TextAlign.Center).padding({ top: 45 })Button('返回').width('50%').height(40).margin({ top: 50 }).onClick(() => {//彈出路由棧棧頂元素,返回上個頁面this.pathInfos.pop();})}.size({ width: '100%', height: '100%' })}.title(`${this.name}`).onReady((ctx: NavDestinationContext) => {// NavDestinationContext獲取當前所在的導航控制器this.pathInfos = ctx.pathStack;})}
}
導航子頁2實現步驟為:
1.使用NavDestination,來創建導航子頁PageTwo。
2.創建導航控制器NavPathStack并在onReady時進行初始化,獲取當前所在的導航控制器,以此來實現不同頁面之間的跳轉。
3.在子頁面內的組件添加onClick,并在其中使用導航控制器NavPathStack的pushPathByName方法,使組件可以在點擊之后從當前頁面跳轉到輸入參數name在路由表內對應的頁面
//PageTwo.ets
@Builder
export function PageTwoBuilder(name: string) {PageTwo({ name: name });
}@Component
export struct PageTwo {pathInfos: NavPathStack = new NavPathStack();name: String = '';private listArray: Array<string> = ['Projection', 'Print', 'VPN', 'Private DNS', 'NFC'];build() {NavDestination() {Column() {List({ space: 12, initialIndex: 0 }) {ForEach(this.listArray, (item: string) => {ListItem() {Row() {Row() {Text(`${item.slice(0, 1)}`).fontColor(Color.White).fontSize(14).fontWeight(FontWeight.Bold)}.width(30).height(30).backgroundColor('#a8a8a8').margin({ right: 20 }).borderRadius(20).justifyContent(FlexAlign.Center)Column() {Text(item).fontSize(16).margin({ bottom: 5 })}.alignItems(HorizontalAlign.Start)Blank()Row().width(12).height(12).margin({ right: 15 }).border({width: { top: 2, right: 2 },color: 0xcccccc}).rotate({ angle: 45 })}.borderRadius(15).shadow({ radius: 100, color: '#ededed' }).width('90%').alignItems(VerticalAlign.Center).padding({ left: 15, top: 15, bottom: 15 }).backgroundColor(Color.White)}.width('100%').onClick(() => {this.pathInfos.pushPathByName(`${item}`, '頁面設置參數');})}, (item: string): string => item)}.listDirection(Axis.Vertical).edgeEffect(EdgeEffect.Spring).sticky(StickyStyle.Header).width('100%')}.size({ width: '100%', height: '100%' })}.title(`${this.name}`).onReady((ctx: NavDestinationContext) => {// NavDestinationContext獲取當前所在的導航控制器this.pathInfos = ctx.pathStack;})}
}
創建路由跳轉
實現步驟為:
1.工程配置文件module.json5中配置 {"routerMap": "$profile:router_map"}。
2.router_map.json中配置全局路由表,導航控制器NavPathStack可根據路由表中的name將對應頁面信息入棧。
{"routerMap" : [{"name" : "WLAN","pageSourceFile" : "src/main/ets/pages/PageOne.ets","buildFunction" : "PageOneBuilder"},{"name" : "Bluetooth","pageSourceFile" : "src/main/ets/pages/PageOne.ets","buildFunction" : "PageOneBuilder"},{"name" : "Personal Hotspot","pageSourceFile" : "src/main/ets/pages/PageOne.ets","buildFunction" : "PageOneBuilder"},{"name" : "Connect & Share","pageSourceFile" : "src/main/ets/pages/PageTwo.ets","buildFunction" : "PageTwoBuilder"},{"name" : "Projection","pageSourceFile" : "src/main/ets/pages/PageOne.ets","buildFunction" : "PageOneBuilder"},{"name" : "Print","pageSourceFile" : "src/main/ets/pages/PageOne.ets","buildFunction" : "PageOneBuilder"},{"name" : "VPN","pageSourceFile" : "src/main/ets/pages/PageOne.ets","buildFunction" : "PageOneBuilder"},{"name" : "Private DNS","pageSourceFile" : "src/main/ets/pages/PageOne.ets","buildFunction" : "PageOneBuilder"},{"name" : "NFC","pageSourceFile" : "src/main/ets/pages/PageOne.ets","buildFunction" : "PageOneBuilder"}]
}
附示例代碼
- Navigation系統路由