21、鴻蒙Harmony Next開發:組件導航(Navigation)

目錄

設置頁面顯示模式

設置標題欄模式

設置菜單欄

設置工具欄

路由操作

頁面跳轉

頁面返回

頁面替換

頁面刪除

移動頁面

參數獲取

路由攔截

單例跳轉

子頁面

頁面顯示類型

頁面生命周期

頁面監聽和查詢

頁面轉場

關閉轉場

自定義轉場

共享元素轉場

跨包動態路由

系統路由表

自定義路由表

導航示例

創建導航首頁

創建導航子頁

創建路由跳轉

附示例代碼


組件導航(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相關的接口去實現頁面跳轉的功能,主要分為以下三類:

  1. 普通跳轉,通過頁面的name去跳轉,并可以攜帶param。
    this.pageStack.pushPath({ name: "PageOne", param: "PageOne Param" });
    this.pageStack.pushPathByName("PageOne", "PageOne Param");
    
  2. 帶返回回調的跳轉,跳轉時添加onPop回調,能在頁面出棧時獲取返回信息,并進行處理。
    this.pageStack.pushPathByName('PageOne', "PageOne Param", (popInfo) => {console.log('Pop page name is: ' + popInfo.info.name + ', result: ' + JSON.stringify(popInfo.result));
    });
    
  3. 帶錯誤碼的跳轉,跳轉結束會觸發異步回調,返回錯誤碼信息。
    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頁面跳轉后回調,在該回調中操作棧會在下一次跳轉生效。
modeChangeNavigation單雙欄顯示狀態發生變更時觸發該回調。

無論是哪個回調,在進入回調時路由棧都已經發生了變化。

開發者可以在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路由棧的單實例跳轉。單實例跳轉的規則如下:

  1. 當指定為LaunchMode.MOVE_TO_TOP_SINGLETON時,系統會從棧底到棧頂查找具有指定名稱的NavDestination。找到后,該頁面將被移動到棧頂(replace操作會用指定的NavDestination替換當前棧頂)。
  2. 若指定為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事件提供,可以通過以下三步定義自定義轉場動畫:

  1. 構建一個自定義轉場動畫工具類CustomNavigationUtils,通過一個Map管理各頁面的自定義動畫對象CustomTransition。頁面在創建時注冊其自定義轉場動畫對象,在銷毀時取消注冊。
  2. 實現一個轉場協議對象NavigationAnimatedTransition。其中,timeout屬性表示轉場結束的超時時間,默認為1000ms,transition屬性為自定義的轉場動畫方法。開發者需在此實現自己的轉場動畫邏輯,系統在轉場開始時會調用此方法,onTransitionEnd為轉場結束時的回調。
  3. 調用customNavContentTransition方法并返回實現的轉場協議對象,若返回undefined,則使用系統默認轉場。
  4. 具體示例代碼可參考Navigation自定義轉場示例。

  • NavDestination自定義轉場
  • ?NavDestination支持自定義轉場動畫,通過設置customTransition屬性即可實現單個頁面的自定義轉場效果。要實現這一功能,需完成以下步驟:

  1. 實現NavDestination的轉場代理,針對不同的堆棧操作類型返回自定義的轉場協議對象NavDestinationTransition。其中,event是必填參數,需在此處編寫自定義轉場動畫的邏輯;而onTransitionEnd、duration、curve與delay為可選參數,分別對應動畫結束后的回調、動畫持續時間、動畫曲線類型與開始前的延時。若在轉場代理中返回多個轉場協議對象,這些動畫效果將逐層疊加。
  2. 通過調用NavDestination組件的customTransition屬性,并傳入上述實現的轉場代理,完成自定義轉場的設置。
  3. ?具體示例代碼可以參考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"}}]}
    
  1. 配置說明如下:

    配置項說明
    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自定義動態路由?示例。

實現方案:

  1. 定義頁面跳轉配置項。?
    • 使用資源文件進行定義,通過資源管理@ohos.resourceManager在運行時對資源文件解析。
    • 在ets文件中配置路由加載配置項,一般包括路由頁面名稱(即pushPath等接口中頁面的別名),文件所在模塊名稱(hsp/har的模塊名),加載頁面在模塊內的路徑(相對src目錄的路徑)。
  2. 加載目標跳轉頁面,通過動態import將跳轉目標頁面所在的模塊在運行時加載,在模塊加載完成后,調用模塊中的方法,通過import在模塊的方法中加載模塊中顯示的目標頁面,并返回頁面加載完成后定義的Builder函數。
  3. 觸發頁面跳轉,在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系統路由

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/89584.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/89584.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/89584.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

“外賣大戰”正在改變國內“大零售”

出品 | 何璽排版 | 葉媛7月18日&#xff0c;市場監管總局約談美團、餓了么、京東三家外賣平臺&#xff0c;要求“理性競爭、規范促銷”&#xff0c;劍指近期愈演愈烈的“0元購”“0.1秒殺”等外賣補貼亂象。但約談之后&#xff0c;平臺們是真整改&#xff0c;還是玩話術&#x…

當CAN握手EtherCAT:視覺檢測系統的“雙芯合璧”時代來了

在汽車制造的高速生產線上&#xff0c;設備間的“語言不通”曾是工程師們的頭疼事&#xff1a;CAN總線像踏實的老司機&#xff0c;穩扎穩打傳輸傳感器數據&#xff1b;而EtherCAT網關則是追求極致速度的“閃電俠”&#xff0c;主導著實時控制的重任。當視覺檢測系統需要同時對接…

【C語言】動態內存管理全解析:malloc、calloc、realloc與free的正確使用

C語言學習 動態內存分配 友情鏈接&#xff1a;C語言專欄 文章目錄C語言學習前言&#xff1a;一、為什么要有動態內存分配二、malloc和free2.1 malloc2.2 free三、calloc和realloc3.1 calloc3.2 realloc總結附錄上文鏈接下文鏈接專欄前言&#xff1a; 在C語言編程中&#xff0…

基于Arduino智能家居環境監測系統—以光照強度檢測修改

2 相關技術與理論 2.1 Arduino 技術 Arduino 是一款廣受歡迎的開源電子原型平臺&#xff0c;由硬件和軟件組成&#xff0c;為開發者提供了便捷且低成本的解決方案&#xff0c;尤其適用于快速搭建交互式電子項目&#xff0c;在本智能家居環境監測系統中擔當核心角色。? 硬件方…

前端上傳 pdf 文件 ,前端自己解析出來 生成界面 然后支持編輯

要在前端解析 PDF 文件并生成可編輯界面&#xff0c;我們可以使用 PDF.js 庫來解析 PDF 內容&#xff0c;然后將其轉換為可編輯的 HTML 元素。 主要特點和工作原理如下&#xff1a; PDF 解析&#xff1a; 使用 Mozilla 的 PDF.js 庫解析 PDF 文件內容&#xff0c;提取文本信息。…

Linux“一切皆文件“設計哲學 與 Linux文件抽象層:struct file與file_operations的架構解析

在Linux系統中&#xff0c;“一切皆文件”&#xff08;Everything is a file&#xff09;是一個核心設計哲學&#xff0c;它抽象了系統資源的訪問方式&#xff0c;使得幾乎所有硬件設備、進程、網絡連接等都可以通過統一的文件接口&#xff08;如open()、read()、write()、clos…

藍橋杯零基礎到獲獎-第3章 C++ 變量和常量

藍橋杯零基礎到獲獎-第3章 C 變量和常量 文章目錄一、變量和常量1.變量的創建2.變量初始化3.變量的分類4.常量4.1 字?常量4.2 #define定義常量4.3 const 定義常量4.4 練習練習1&#xff1a;買票https://www.nowcoder.com/practice/0ad8f1c0d7b84c6d8c560298f91d5e66練習2&…

物理AI是什么技術?

當英偉達CEO黃仁勛在鏈博會上明確提出“物理AI將是AI的下一浪潮”時&#xff0c;這個看似陌生的概念瞬間引發了科技圈的廣泛關注。究竟什么是物理AI&#xff1f;它與我們熟悉的人工智能有何不同&#xff1f;又將如何重塑我們與物理世界的交互方式&#xff1f; 物理AI&#xff1…

GRIB數據處理相關指令

GRIB 數據格式簡介 GRIB(General Regularly distributed Information in Binary form)&#xff0c;是由世界氣象組織&#xff08;WMO&#xff09;設計和維護的一種用于存儲和傳輸網格數據的標準數據格式&#xff0c;它是一種自描述的二進制壓縮格式&#xff0c;通常具有擴展名…

微服務學習(六)之分布式事務

微服務學習&#xff08;六&#xff09;之分布式事務一、認識Seata二、部署TC服務1、準備數據庫表2、準備配置文件3、docker部署三、微服務集成seata1、引入依賴2、改造配置3、添加數據庫表4、測試四、XA模式1、兩階段提交2、seata的XA模型3、優缺點4、實現步驟五、AT模式1、Sea…

Go實現用戶登錄小程序

寫一個用戶登錄注冊的小程序 運行程序&#xff0c;給出提示1. 注冊輸入用戶名、密碼、年齡、性別 {"用戶名": "root", "passwd": "123456", "age": 18, "sex": "男"}注冊前要判斷是否存在此用戶2. 登錄…

鴻蒙藍牙通信

https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-bluetooth-low-energy 藍牙權限 module.json5 {"module": {"requestPermissions": [{"name": "ohos.permission.ACCESS_BLUETOOTH","reason": "…

Java:Map

文章目錄Map常用方法Map遍歷的三種方法先獲取Map集合的全部鍵&#xff0c;再通過遍歷來找值Entry對象forEach結合lambda表達式Map 案例分析需求我的代碼&#xff08;不好&#xff09;老師的代碼&#xff08;好&#xff09;好在哪里另外集合分為Collection和MapMap常用方法 代碼…

fastjson2 下劃線字段轉駝峰對象

在對接第三方或查詢數據庫時&#xff0c;返回的字段是下劃線分隔的&#xff0c;而在業務中需要轉成java對象&#xff0c;java對象的字段是駝峰的&#xff0c;使用fastjson2時&#xff0c;有兩種方法可以實現&#xff1a; 比如數據格式是&#xff1a; {"item_id": &q…

【硬件】藍牙音頻協議

1. 無線音頻傳輸的工作原理 在無線傳輸的過程中&#xff0c;音源設備首先將MP3、FLAC等音頻文件還原為PCM格式。通過藍牙音頻編碼轉為藍牙無線傳輸的文件&#xff0c;發送到音頻設備段。將藍牙無線傳輸的文件再次還原為PCM格式&#xff0c;之后轉為模擬信號并放大&#xff0c;通…

【宇樹科技:未來1-3年,機器人可流水線打螺絲】

在第三屆中國國際供應鏈促進博覽會上&#xff0c;宇樹科技工作人員表示&#xff0c;未來1到3年內&#xff0c;機器人產品有望從單一工業化產品&#xff0c;發展至復合化工業場景&#xff0c;如機器人搬完箱子后&#xff0c;換個 “手” 就能在流水線上打螺絲。在3到10年內&…

Spring AI 1.0版本 + 千問大模型之 文本記憶對話

上篇文章&#xff0c;主要是簡單講解了一下文本對話的功能。由于模型不具備上下文記憶功能&#xff0c;只能一問一答。因此我們需要實現記憶對話功能&#xff0c;這樣大模型回答信息才能夠更加準確。 1、pom依賴 項目構建就不詳細說了&#xff0c;大家可以參考上篇 文本對話 文…

測試學習之——Pytest Day2

一、Pytest配置框架Pytest的配置旨在改變其默認行為&#xff0c;以適應不同的測試需求和項目結構。理解其配置層級和常用參數&#xff0c;是高效使用Pytest的基礎。1. 配置的意義與層級配置的本質在于提供一種機制&#xff0c;允許用戶根據項目特點、團隊規范或特定測試場景&am…

Go-Redis × RediSearch 全流程實踐

1. 連接 Redis ctx : context.Background()rdb : redis.NewClient(&redis.Options{Addr: "localhost:6379",Password: "",DB: 0,Protocol: 2, // 推薦 RESP2// UnstableResp3: true, // 若要體驗 RESP3 Raw* })2. 準備示例數據 u…

深入理解指針(指針篇2)

在指針篇1我們已經了解了整型指針&#xff0c;當然還有很多其他類型的指針&#xff0c;像字符指針、數組指針、函數指針等&#xff0c;他們都有他們的特別之處&#xff0c;讓我們接著學習。1. 指針類型介紹和應用1.1 字符指針變量字符指針變量類型為char*&#xff0c;一般這樣使…