DEV Community

SameX
SameX

Posted on

Typical Layout Case in HarmonyOS Next: Multi-column Navigation and Content Display

Typical Layout Case in HarmonyOS Next: Multi-column Navigation and Content Display

This article aims to deeply explore the technical details of the Huawei HarmonyOS Next system and summarize them based on practical development practices. It mainly serves as a medium for technical sharing and communication. There may inevitably be some errors or omissions. Colleagues are welcome to put forward valuable opinions and questions so that we can make progress together. This article is original content, and any form of reprint must indicate the source and the original author.

In the application development of HarmonyOS Next, the layout of multi-column navigation and content display is a common interface design pattern, which can effectively improve the user operation efficiency and the information display effect. Next, let's deeply analyze the implementation logic of the single-column/double-column/triple-column layout, and how to optimize the three-column navigation experience on large-screen devices.

The Core Logic of the Single-column/Double-column/Triple-column Layout (Combined Use of SideBarContainer + Navigation)

In HarmonyOS Next, the key to implementing the single-column/double-column/triple-column layout lies in the ingenious combination of the SideBarContainer and Navigation components. The SideBarContainer is used to create the sidebar, where functional modules such as navigation menus can be placed; the Navigation component is responsible for managing the display and switching logic of the page content.

Taking a file management application as an example, the single-column layout is usually suitable for small-screen devices. At this time, the sidebar may be hidden, and users can evoke the sidebar through click operations to switch functions. The double-column layout is more common on devices with medium screen sizes, where the sidebar and the content area are displayed simultaneously, facilitating users to navigate and view the content quickly. The triple-column layout is more suitable for large-screen devices, dividing the interface into a sidebar navigation area, a list navigation area, and a content area, further improving the operation efficiency.

The following is a simple code example showing the basic combination usage:

// Configure in the project configuration file module.json5 {"routerMap": "$profile:route_map"}
// route_map.json
{
    "routerMap": [
        {
            "name": "FileListPage",
            "pageSourceFile": "src/main/ets/pages/FileListPage.ets",
            "buildFunction": "FileListPageBuilder",
            "data": {
                "description": "File List Page"
            }
        },
        {
            "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('Content of the File List Page').fontSize(20).padding(20)
            }
              .title('File List')
        }
    }
}

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

@Component
export struct FileDetailPage {
    build() {
        Column() {
            NavDestination() {
                Text('Content of the File Detail Page').fontSize(20).padding(20)
            }
              .title('File Details')
        }
    }
}

@Entry
@Component
struct MultiColumnLayout {
    @State showSideBar: boolean = false;
    pageInfos: NavPathStack = new NavPathStack();
    @State navItems: { label: string, pagePath: string }[] = [
        {
            label: 'File List',
            pagePath: 'FileListPage'
        },
        {
            label: 'File Details',
            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;
            })
    }
}
Enter fullscreen mode Exit fullscreen mode

In the above code, the SideBarContainer contains the navigation menu of the sidebar, and the Navigation component is responsible for managing the display and switching of the page content. By clicking on the menu options in the sidebar, the path of pageInfos is updated, thus realizing the display of different page contents.

Dynamically Adjusting the Layout Mode (How Does NavigationMode.Auto Work?)

The NavigationMode.Auto mode of the Navigation component is the key to achieving dynamic layout adjustment. In this mode, the Navigation component will automatically select an appropriate mode according to the size of the application window: when the window width is less than 520vp, it will be displayed in the Stack mode, that is, the page content is displayed in a stacked manner; when the window width is greater than or equal to 520vp, it will be displayed in the Split mode, dividing the page into two columns or more columns for display.

When the window size changes, the Navigation component will automatically switch between the Stack mode and the Split mode. For example, in a news reading application, on a small-screen mobile phone, the article list and details may be displayed in the Stack mode. After the user clicks on a list item, the article details will cover the list; while on a tablet device, the window width is larger, and the Navigation component will automatically switch to the Split mode, where the article list and details can be displayed in different columns at the same time, facilitating users to compare and operate.

In actual use, developers do not need to write complex switching logic manually. They only need to set the mode property of the Navigation component to NavigationMode.Auto and configure the relevant sub-components reasonably to easily achieve dynamic layout adjustment and provide users with a smoother operation experience.

How to Optimize the Three-column Navigation Experience on Large-screen Devices (Example Code)

On large-screen devices, the three-column navigation can make full use of the screen space and improve the user operation efficiency. In order to optimize the three-column navigation experience, we can start from multiple aspects, such as adjusting the width of each column and optimizing the interaction effect.

The following is an optimized example code of the three-column layout:

// Configure in the project configuration file module.json5 {"routerMap": "$profile:route_map"}
// route_map.json
{
    "routerMap": [
        {
            "name": "MainPage",
            "pageSourceFile": "src/main/ets/pages/MainPage.ets",
            "buildFunction": "MainPageBuilder",
            "data": {
                "description": "Main Page"
            }
        },
        {
            "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('This is the content of the main page').fontSize(20).padding(20)
            }
              .title('Main Page')
        }
    }
}

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

@Component
export struct SubPage1 {
    build() {
        Column() {
            NavDestination() {
                Text('This is the content of Sub-page 1').fontSize(20).padding(20)
            }
              .title('Sub-page 1')
        }
    }
}

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

@Component
export struct SubPage2 {
    build() {
        Column() {
            NavDestination() {
                Text('This is the content of Sub-page 2').fontSize(20).padding(20)
            }
              .title('Sub-page 2')
        }
    }
}

@Entry
@Component
struct OptimizedTripleColumnLayout {
    @State curBp: string = 'lg';
    @State showSideBar: boolean = true;
    pageInfos: NavPathStack = new NavPathStack();
    @State navItems: { label: string, pagePath: string }[] = [
        {
            label: 'Main Page',
            pagePath: 'MainPage'
        },
        {
            label: 'Sub-page 1',
            pagePath: 'SubPage1'
        },
        {
            label: 'Sub-page 2',
            pagePath: 'SubPage2'
        }
    ];
    @Builder NavigationTitle() {
        Column() {
            Text('Application Name').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;
                }
            })
    }
}
Enter fullscreen mode Exit fullscreen mode

In this example, the three-column layout is constructed through the SideBarContainer and Navigation components. The sideBarWidth property of the SideBarContainer sets the width of the sidebar to 240, and the minContentWidth property sets the minimum width of the content area to 600, ensuring a reasonable layout of each column under different window sizes. The mode property of the Navigation component is set to NavigationMode.Auto to achieve automatic layout mode switching according to the window size. At the same time, by listening to the breakpoint change through the onBreakpointChange event, the sidebar is hidden on small screens (such as the sm breakpoint) to improve the display effect on small-screen devices; on large-screen devices, the sidebar is kept displayed for the convenience of user operation. Through these optimization measures, a more efficient and convenient navigation experience can be provided for users of large-screen devices.

Top comments (0)