HarmonyOS Next典型布局案例:多栏导航与内容展示 原创

SameX
发布于 2025-2-26 15:59
1.8w浏览
0收藏

本文旨在深入探讨华为鸿蒙HarmonyOS Next系统的技术细节,基于实际开发实践进行总结。主要作为技术分享与交流载体,难免错漏,欢迎各位同仁提出宝贵意见和问题,以便共同进步。本文为原创内容,任何形式的转载必须注明出处及原作者。

在HarmonyOS Next应用开发中,多栏导航与内容展示布局是常见的界面设计模式,它能有效提升用户操作效率和信息展示效果。接下来,咱们深入剖析单栏/双栏/三栏布局的实现逻辑,以及如何在大屏设备上优化三分栏导航体验。

单栏/双栏/三栏布局的核心逻辑(SideBarContainer + Navigation组合使用)

在HarmonyOS Next中,实现单栏/双栏/三栏布局的关键在于SideBarContainerNavigation组件的巧妙组合。SideBarContainer用于创建侧边栏,可放置导航菜单等功能模块;Navigation组件则负责管理页面内容的显示和切换逻辑。

以一个文件管理应用为例,单栏布局通常适用于小屏幕设备,此时侧边栏可能隐藏,用户通过点击操作唤起侧边栏进行功能切换。双栏布局在中等屏幕尺寸设备上较为常见,侧边栏和内容区同时显示,方便用户快速导航和查看内容。三栏布局则更适合大屏设备,将界面分为侧边导航区、列表导航区和内容区,进一步提升操作效率。

下面是一个简单的代码示例,展示基本的组合使用方式:

// 工程配置文件module.json5中配置{"routerMap": "$profile:route_map"}
// route_map.json
{
    "routerMap": [
        {
            "name": "FileListPage",
            "pageSourceFile": "src/main/ets/pages/FileListPage.ets",
            "buildFunction": "FileListPageBuilder",
            "data": {
                "description": "文件列表页面"
            }
        },
        {
            "name": "FileDetailPage",
            "pageSourceFile": "src/main/ets/pages/FileDetailPage.ets",
            "buildFunction": "FileDetailPageBuilder"
        }
    ]
}

// FileListPage.ets
@Builder
export function FileListPageBuilder() {
    return FileListPage();
}

@Component
export struct FileListPage {
    build() {
        Column() {
            NavDestination() {
                Text('文件列表页面内容').fontSize(20).padding(20)
            }
              .title('文件列表')
        }
    }
}

// FileDetailPage.ets
@Builder
export function FileDetailPageBuilder() {
    return FileDetailPage();
}

@Component
export struct FileDetailPage {
    build() {
        Column() {
            NavDestination() {
                Text('文件详情页面内容').fontSize(20).padding(20)
            }
              .title('文件详情')
        }
    }
}

@Entry
@Component
struct MultiColumnLayout {
    @State showSideBar: boolean = false;
    pageInfos: NavPathStack = new NavPathStack();
    @State navItems: { label: string, pagePath: string }[] = [
        {
            label: '文件列表',
            pagePath: 'FileListPage'
        },
        {
            label: '文件详情',
            pagePath: 'FileDetailPage'
        }
    ];
    build() {
        SideBarContainer(SideBarContainerType.Overlay) {
            Column() {
                List() {
                    ForEach(this.navItems, (item) => {
                        ListItem() {
                            Text(item.label).fontSize(20).onClick(() => {
                                this.pageInfos.clear();
                                this.pageInfos.pushPath({ name: item.pagePath });
                            })
                        }
                    })
                }
            }
              .width('100%')
              .height('100%')
              .justifyContent(FlexAlign.SpaceEvenly)
              .backgroundColor('#F1F3F5')
            Column() {
                Navigation(this.pageInfos) {
                    List() {
                        ForEach(this.navItems, (item) => {
                            ListItem() {
                                Text(item.label).fontSize(20).onClick(() => {
                                    this.pageInfos.pushPath({ name: item.pagePath });
                                })
                            }
                        })
                    }
                }
                  .width('100%')
                  .height('100%')
                  .hideToolBar(true)
            }
        }
          .sideBarWidth(240)
          .showSideBar(this.showSideBar)
          .onChange((isOpen: boolean) => {
                this.showSideBar = isOpen;
            })
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.

在上述代码中,SideBarContainer包含了侧边栏的导航菜单,Navigation组件负责管理页面内容的显示和切换。通过点击侧边栏的菜单选项,pageInfos的路径更新,从而实现不同页面内容的展示。

动态调整布局模式(NavigationMode.Auto如何工作?)

Navigation组件的NavigationMode.Auto模式是实现布局动态调整的关键。该模式下,Navigation组件会根据应用窗口尺寸自动选择合适的模式:当窗口宽度小于520vp时,采用Stack模式显示,即页面内容堆叠显示;当窗口宽度大于等于520vp时,采用Split模式显示,将页面分为两栏或更多栏展示。

当窗口尺寸发生改变时,Navigation组件会自动在Stack模式和Split模式之间切换。例如,在一个新闻阅读应用中,小屏幕手机上可能以Stack模式显示文章列表和详情,用户点击列表项后,文章详情覆盖在列表上方;而在平板设备上,窗口宽度较大,Navigation组件会自动切换到Split模式,文章列表和详情可以同时显示在不同栏中,方便用户对比和操作。

在实际使用中,无需开发者手动编写复杂的切换逻辑,只需要将Navigation组件的mode属性设置为NavigationMode.Auto,并合理配置相关子组件,就能轻松实现布局的动态调整,为用户提供更加流畅的操作体验。

如何在大屏设备上优化三分栏导航体验(示例代码)

在大屏设备上,三分栏导航可以充分利用屏幕空间,提升用户操作效率。为了优化三分栏导航体验,我们可以从多个方面入手,如调整各栏宽度、优化交互效果等。

以下是一个优化后的三分栏布局示例代码:

// 工程配置文件module.json5中配置{"routerMap": "$profile:route_map"}
// route_map.json
{
    "routerMap": [
        {
            "name": "MainPage",
            "pageSourceFile": "src/main/ets/pages/MainPage.ets",
            "buildFunction": "MainPageBuilder",
            "data": {
                "description": "主页面"
            }
        },
        {
            "name": "SubPage1",
            "pageSourceFile": "src/main/ets/pages/SubPage1.ets",
            "buildFunction": "SubPage1Builder"
        },
        {
            "name": "SubPage2",
            "pageSourceFile": "src/main/ets/pages/SubPage2.ets",
            "buildFunction": "SubPage2Builder"
        }
    ]
}

// MainPage.ets
@Builder
export function MainPageBuilder() {
    return MainPage();
}

@Component
export struct MainPage {
    build() {
        Column() {
            NavDestination() {
                Text('这是主页面内容').fontSize(20).padding(20)
            }
              .title('主页面')
        }
    }
}

// SubPage1.ets
@Builder
export function SubPage1Builder() {
    return SubPage1();
}

@Component
export struct SubPage1 {
    build() {
        Column() {
            NavDestination() {
                Text('这是子页面1的内容').fontSize(20).padding(20)
            }
              .title('子页面1')
        }
    }
}

// SubPage2.ets
@Builder
export function SubPage2Builder() {
    return SubPage2();
}

@Component
export struct SubPage2 {
    build() {
        Column() {
            NavDestination() {
                Text('这是子页面2的内容').fontSize(20).padding(20)
            }
              .title('子页面2')
        }
    }
}

@Entry
@Component
struct OptimizedTripleColumnLayout {
    @State curBp: string = 'lg';
    @State showSideBar: boolean = true;
    pageInfos: NavPathStack = new NavPathStack();
    @State navItems: { label: string, pagePath: string }[] = [
        {
            label: '主页面',
            pagePath: 'MainPage'
        },
        {
            label: '子页面1',
            pagePath: 'SubPage1'
        },
        {
            label: '子页面2',
            pagePath: 'SubPage2'
        }
    ];
    @Builder NavigationTitle() {
        Column() {
            Text('应用名称').fontColor('#000000').fontSize(24).width('100%').height('100%').align(Alignment.BottomStart).margin({ left: '5%' })
        }
          .alignItems(HorizontalAlign.Start)
    }
    build() {
        SideBarContainer() {
            Column() {
                List() {
                    ForEach(this.navItems, (item) => {
                        ListItem() {
                            Text(item.label).fontSize(20).onClick(() => {
                                this.pageInfos.clear();
                                this.pageInfos.pushPath({ name: item.pagePath });
                            })
                        }
                    })
                }
                  .divider({ strokeWidth: 5, color: '#F1F3F5' })
            }
              .width('100%')
              .height('100%')
              .justifyContent(FlexAlign.SpaceEvenly)
              .backgroundColor('#F1F3F5')
            Column() {
                Navigation(this.pageInfos) {
                    List() {
                        ForEach(this.navItems, (item) => {
                            ListItem() {
                                Text(item.label).fontSize(20).onClick(() => {
                                    this.pageInfos.pushPath({ name: item.pagePath });
                                })
                            }
                        })
                    }
                }
                  .mode(NavigationMode.Auto)
                  .minContentWidth(600)
                  .navBarWidth(240)
                  .backgroundColor('#FFFFFF')
                  .height('100%')
                  .width('100%')
                  .hideToolBar(true)
                  .title(this.NavigationTitle)
            }
        }
          .sideBarWidth(240)
          .minContentWidth(600)
          .showSideBar(this.showSideBar)
          .onChange((isOpen: boolean) => {
                this.showSideBar = isOpen;
            })
          .onBreakpointChange((breakpoint: string) => {
                this.curBp = breakpoint;
                if (breakpoint ==='sm') {
                    this.showSideBar = false;
                } else {
                    this.showSideBar = true;
                }
            })
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.

在这个示例中,通过SideBarContainerNavigation组件构建了三分栏布局。SideBarContainersideBarWidth属性设置了侧边栏宽度为240,minContentWidth属性设置了内容区的最小宽度为600,确保在不同窗口尺寸下各栏布局合理。Navigation组件的mode属性设置为NavigationMode.Auto,实现了根据窗口尺寸自动切换布局模式。同时,通过onBreakpointChange事件监听断点变化,在小屏幕(如sm断点)下隐藏侧边栏,提升小屏幕设备的显示效果;在大屏设备上,保持侧边栏显示,方便用户操作。通过这些优化措施,能够为大屏设备用户提供更加高效、便捷的导航体验。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
分类
标签
收藏
回复
举报


回复
    相关推荐