導讀
- 設置標題欄模式
- 設置菜單欄
- 設置工具欄
- 路由操作
- 頁面跳轉
- 頁面返回
- 頁面替換
- 頁面刪除
- 移動頁面
- 參數獲取
- 路由攔截
- 子頁面
- 頁面顯示類型
- 頁面生命周期
- 頁面監聽和查詢
- 頁面轉場
- 關閉轉場
- 自定義轉場
- 共享元素轉場
- 跨包動態路由
- 系統路由表
- 自定義路由表
- 示例代碼
Navigation組件適用于模塊內和跨模塊的路由切換,通過組件級路由能力實現更加自然流暢的轉場體驗,并提供多種標題欄樣式來呈現更好的標題和內容聯動效果。一次開發,多端部署場景下,Navigation組件能夠自動適配窗口顯示大小,在窗口較大的場景下自動切換分欄展示效果。
本文從組件導航(Navigation)的顯示模式、路由操作、子頁面管理、跨包跳轉以及跳轉動效等幾個方面進行詳細介紹。
Navigation組件主要包含?導航頁和子頁。導航頁由標題欄(包含菜單欄)、內容區和工具欄組成,可以通過hideNavBar屬性進行隱藏,導航頁不存在頁面棧中,與子頁,以及子頁之間可以通過路由操作進行切換。
在API Version 9上,Navigation需要配合NavRouter組件實現頁面路由。從API Version 10開始,更推薦使用NavPathStack實現頁面路由。
設置標題欄模式
標題欄在界面頂部,用于呈現界面名稱和操作入口,Navigation組件通過titleMode屬性設置標題欄模式。
說明
Navigation或NavDestination未設置主副標題并且沒有返回鍵時,不顯示標題欄。
-
Mini模式
普通型標題欄,用于一級頁面不需要突出標題的場景。Navigation() {// ... } .titleMode(NavigationTitleMode.Mini)
-
Full模式
強調型標題欄,用于一級頁面需要突出標題的場景。Navigation() {// ... } .titleMode(NavigationTitleMode.Full)
設置菜單欄
菜單欄位于Navigation組件的右上角,可以通過menus屬性進行設置。
menus支持Array和CustomBuilder兩種參數類型。
使用Array類型時,豎屏最多支持顯示3個圖標,橫屏最多支持顯示5個圖標,多余的圖標會被放入自動生成的更多圖標。
-
設置了3個圖標的菜單欄
let TooTmp: NavigationMenuItem = {'value': "", 'icon': "./image/ic_public_highlights.svg", 'action': ()=> {}} let TooTmpFromResource: NavigationMenuItem = {'value': "", 'icon': "resources/base/media/ic_public_highlights.svg", 'action': ()=> {}} Navigation() {// ... } .menus([TooTmp,TooTmp,TooTmp])
-
設置了4個圖標的菜單欄
let TooTmp: NavigationMenuItem = {'value': "", 'icon': "./image/ic_public_highlights.svg", 'action': ()=> {}} Navigation() {// ... } // 豎屏最多支持顯示3個圖標,多余的圖標會被放入自動生成的更多圖標。 .menus([TooTmp,TooTmp,TooTmp,TooTmp])
設置工具欄
工具欄位于Navigation組件的底部,開發者可以通過toolbarConfiguration屬性進行設置。
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對象,用于管理頁面。主要涉及頁面跳轉、頁面返回、頁面替換、頁面刪除、參數獲取、路由攔截等功能。
不建議通過監聽生命周期的方式管理自己的頁面棧。
{// 創建一個頁面棧對象并傳入NavigationpageStack: NavPathStack = new NavPathStack()build() {Navigation(this.pageStack) {}.title('Main')}
}
struct Index
頁面跳轉
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);
參數獲取
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);}}
})
子頁面
NavDestination是Navigation子頁面的根容器,用于承載子頁面的一些特殊屬性以及生命周期等。NavDestination可以設置獨立的標題欄和菜單欄等屬性,使用方法與Navigation相同。NavDestination也可以通過mode屬性設置不同的顯示類型,用于滿足不同頁面的訴求。
頁面顯示類型
-
標準類型
NavDestination組件默認為標準類型,此時mode屬性為NavDestinationMode.STANDARD。標準類型的NavDestination的生命周期跟隨其在NavPathStack頁面棧中的位置變化而改變。 -
彈窗類型
NavDestination設置mode為NavDestinationMode.DIALOG彈窗類型,此時整個NavDestination默認透明顯示。彈窗類型的NavDestination顯示和消失時不會影響下層標準類型的NavDestination的顯示和生命周期,兩者可以同時顯示。// Dialog NavDestination struct Index {('NavPathStack') pageStack: NavPathStack = new NavPathStack()PagesMap(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)}}export struct DialogPage {('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)}}
彈窗類型的NavDestination,代碼效果圖
頁面生命周期
Navigation作為路由容器,其生命周期承載在NavDestination組件上,以組件事件的形式開放。
其生命周期大致可分為三類,自定義組件生命周期、通用組件生命周期和自有生命周期。其中,aboutToAppear和aboutToDisappear是自定義組件的生命周期(NavDestination外層包含的自定義組件),OnAppear和OnDisappear是組件的通用生命周期。剩下的六個生命周期為NavDestination獨有。
生命周期時序如下圖所示:
- aboutToAppear:在創建自定義組件后,執行其build()函數之前執行(NavDestination創建之前),允許在該方法中改變狀態變量,更改將在后續執行build()函數中生效。
- onWillAppear:NavDestination創建后,掛載到組件樹之前執行,在該方法中更改狀態變量會在當前幀顯示生效。
- onAppear:通用生命周期事件,NavDestination組件掛載到組件樹時執行。
- onWillShow:NavDestination組件布局顯示之前執行,此時頁面不可見(應用切換到前臺不會觸發)。
- onShown:NavDestination組件布局顯示之后執行,此時頁面已完成布局。
- onWillHide:NavDestination組件觸發隱藏之前執行(應用切換到后臺不會觸發)。
- onHidden:NavDestination組件觸發隱藏后執行(非棧頂頁面push進棧,棧頂頁面pop出棧或應用切換到后臺)。
- onWillDisappear:NavDestination組件即將銷毀之前執行,如果有轉場動畫,會在動畫前觸發(棧頂頁面pop出棧)。
- onDisappear:通用生命周期事件,NavDestination組件從組件樹上卸載銷毀時執行。
- aboutToDisappear:自定義組件析構銷毀之前執行,不允許在該方法中改變狀態變量。
頁面監聽和查詢
為了方便組件跟頁面解耦,在NavDestination子頁面內部的自定義組件可以通過全局方法監聽或查詢到頁面的一些狀態信息。
-
頁面信息查詢
自定義組件提供queryNavDestinationInfo方法,可以在NavDestination內部查詢到當前所屬頁面的信息,返回值為NavDestinationInfo,若查詢不到則返回undefined。
import { uiObserver } from '@kit.ArkUI';// NavDestination內的自定義組件 struct MyComponent {navDesInfo: uiObserver.NavDestinationInfo | undefinedaboutToAppear(): void {this.navDesInfo = this.queryNavDestinationInfo();Logger.info("所屬頁面Name: " + this.navDesInfo?.name)}}
-
頁面狀態監聽
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默認提供了頁面切換的轉場動畫,通過頁面棧操作時,會觸發不同的轉場效果(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)
自定義轉場
avigation通過customNavContentTransition事件提供自定義轉場動畫的能力,參考Navigation自定義轉場示例。
共享元素轉場
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(按需加載),防止首個頁面加載大量代碼導致卡頓。
動態路由提供系統路由表和自定義路由表兩種實現方式。
系統路由表相對自定義路由表,使用更簡單,只需要添加對應頁面跳轉配置項,即可實現頁面跳轉。
自定義路由表使用起來更復雜,但是可以根據應用業務進行定制處理。
支持自定義路由表和系統路由表混用。
系統路由表
-
在跳轉目標模塊的配置文件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保持一致,否則在編譯時會報錯。
// 跳轉頁面入口函數export function PageOneBuilder() {PageOne()} struct PageOne {pathStack: NavPathStack = new NavPathStack()build() {NavDestination() {}.title('PageOne').onReady((context: NavDestinationContext) => {this.pathStack = context.pathStack})}}
-
通過pushPathByName等路由接口進行頁面跳轉。(注意:此時Navigation中可以不用配置navDestination屬性)。
{pageStack : NavPathStack = new NavPathStack();build() {Navigation(this.pageStack){}.onAppear(() => {this.pageStack.pushPathByName("PageOne", null, false);}).hideNavBar(true)}}
struct Index
自定義路由表
開發者可以通過自定義路由表的方式來實現跨包動態路由,具體實現方法請參考Navigation自定義動態路由 示例。
示例代碼
Navigation系統路由