Implementing Multi-Level Navigation in HarmonyOS with Navbar and TabBar
In HarmonyOS application development, routing and view switching are typically managed through state-decorated propertise and lifecycle-aware components. The following example demonstrates how to implement a tab-based interface using TabBar while isolating route state.
@Entry
@Component
struct MainRouter {
@State activeTabIndex: number = 0;
build() {
Column() {
TabBar({
selectedIndex: this.activeTabIndex,
onTabChanged: (index: number) => {
this.activeTabIndex = index;
}
}) {
TabItem({ label: 'Dashboard', icon: ResourceStr('icons/home') })
TabItem({ label: 'Configuration', icon: ResourceStr('icons/cog') })
}
// Dynamic View Rendering
if (this.activeTabIndex === 0) {
DashboardPanel();
} else if (this.activeTabIndex === 1) {
SettingsPanel();
}
}
.width('100%')
.height('100%')
}
}
Individual views are structured as autonomous components. Layout hierarchy utilizes nested containers to distribute content proportionally across the screen dimensions.
struct DashboardPanel {
build() {
Row() {
Column() {
Text('System Overview')
.fontSize(48)
.fontWeight(FontWeight.Medium)
.padding(16)
}
.alignItems(HorizontalAlign.Start)
}
.layoutWeight(1)
.backgroundColor('#F5F7FA')
}
}
struct SettingsPanel {
build() {
Row() {
Column() {
Text('User Preferences')
.fontSize(48)
.fontWeight(FontWeight.Medium)
.padding(16)
}
.alignItems(HorizontalAlign.End)
}
.layoutWeight(1)
.backgroundColor('#FFFFFF')
}
}
Advanced interfaces often require simultaneous top and bottom navigation controls. By decoupling their respective state variables, developers can manage independent interaction flows without triggering unnecessary UI rebuilds.
@Entry
@Component
struct DualNavLayout {
@State topNavSelection: number = 0;
@State bottomTabSelection: number = 0;
build() {
Column() {
// Upper Control Layer
Navbar({
title: 'Platform Console',
leading: Icon(Icons.menu),
trailing: Icon(Icons.search),
onLeadingTap: () => this.handleMenuAction(),
onTrailingTap: () => this.handleSearchAction()
})
// Content Area Conditionally Rendered
if (this.topNavSelection === 0) {
HomeView();
} else if (this.topNavSelection === 1) {
AnalyticsView();
}
// Lower Control Layer
TabBar({
selectedIndex: this.bottomTabSelection,
onTabChanged: (idx: number) => {
this.bottomTabSelection = idx;
}
}) {
TabItem({ label: 'Main' })
TabItem({ label: 'Tools' })
}
}
.width('100%')
.height('100%')
}
private handleMenuAction(): void { /* Drawer toggle logic */ }
private handleSearchAction(): void { /* Query modal logic */ }
}
The underlying panel definitions follow a standardized structural pattern. Each component encapsulates its own layout geometry and styling parameters, promoting modular reuse across different routing contexts.
struct HomeView {
build() {
Flex({ direction: FlexDirection.Row, wrap: FlexWrap.Nowrap }) {
Column() {
Text('Active Workspace')
.style({ fontSize: 52, fontWeight: FontWeight.Bold })
.margin(20)
}
.layoutFlex(1)
}
.backgroundImageStyle(ImageFit.Contain)
}
}
struct AnalyticsView {
build() {
Flex({ direction: FlexDirection.Column }) {
Row() {
Text('Metrics Summary')
.style({ fontSize: 52, fontWeight: FontWeight.Bold })
.margin(20)
}
}
.justifyContent(FlexAlign.Center)
}
}
When configuring multi-axis navigation systems, state synchronization becomes the primary concern. Binding selectedIndex to independent decorators ensures that modifying one controller does not invalidate the other's display context. Event handlers route user inputs back to the local scope, allowing precise control over render cycles. This architecture scales efficiently to enterprise-grade dashboards where header actions and footer tabs operate on disjoint data models.