HarmonyOS 鸿蒙Next中HMRouter侧边分栏功能技术方案

HarmonyOS 鸿蒙Next中HMRouter侧边分栏功能技术方案 HMRouter 侧边分栏功能技术方案

本文基于 HarmonyOS ArkTS 开发的 HMRouter 侧边分栏功能,围绕 “跨设备响应式布局、多导航容器隔离、断点实时适配” 三大核心场景,从 “问题说明、原因分析、解决思路、解决方案、效果总结” 五个维度展开解析,为鸿蒙应用实现 “小屏单页 / 大屏分栏” 的导航体验提供可复用方案。

1. 功能概述

HMRouter 侧边分栏功能是一种基于 HarmonyOS 响应式布局的导航解决方案,通过结合 HMRouter 路由管理和 GridRow/GridCol 网格布局,实现了在不同屏幕尺寸下的智能布局适配:

  • 小屏幕(手机) :单列布局,通过全屏导航实现页面切换
  • 大屏幕(平板/折叠屏) :双列布局,左侧为导航菜单,右侧为内容区域,实现类似桌面端的侧边栏导航体验

2. 核心组件

核心组件

分难点详细解析

难点 1:响应式布局适配准确性问题

1. 问题说明

小屏幕(<600vp)下侧边栏未隐藏,导致内容区域被挤压;大屏幕(≥840vp)下左右分栏占比失衡,导航栏与内容区间距混乱;窗口尺寸动态变化时,布局切换出现卡顿或错乱。

2. 原因分析

  • 栅格配置逻辑不合理:小屏未设置span: 0导致侧边栏冗余,大屏列数分配未遵循 12 列布局规范;
  • 断点与布局绑定不紧密:未将栅格参数与BreakpointConstants常量关联,硬编码导致适配灵活性差;
  • 窗口变化无布局重绘触发:仅初始化时设置布局,未监听窗口尺寸变化后的断点更新。

3. 解决思路

  • 规范栅格配置:基于断点常量动态设置GridColspan属性,小屏隐藏侧边栏、大屏合理分配列宽;
  • 布局与断点强绑定:通过@StorageProp监听全局断点状态,实现布局参数实时响应;
  • 统一间距常量:使用BreakpointConstants.GUTTER_X控制分栏间距,避免硬编码。

4. 解决方案(基于代码实现)

断点常量定义(数据层支撑)

/**
 * 断点相关常量定义
 */
export class BreakpointConstants {
  /**
   * 组件宽度百分比:100%
   */
  static readonly FULL_WIDTH: string = '100%';

  /**
   * 组件高度百分比:100%
   */
  static readonly FULL_HEIGHT: string = '100%';

  /**
   * 代表小型设备的断点标识
   */
  static readonly BREAKPOINT_SM: string = 'sm';

  /**
   * 代表中型设备的断点标识
   */
  static readonly BREAKPOINT_MD: string = 'md';

  /**
   * 代表大型设备的断点标识
   */
  static readonly BREAKPOINT_LG: string = 'lg';

  /**
   * 断点对应的具体尺寸值(带vp单位)
   */
  static readonly BREAKPOINT_VALUE: Array<string> = ['320vp', '600vp', '840vp'];

  /**
   * 断点对应的纯数字尺寸值(无单位)
   */
  static readonly BREAKPOINT_VALUE_NUMBER: Array<number> = [320, 600, 840];

  /**
   * 小型设备对应的列数
   */
  static readonly COLUMN_SM: number = 4;

  /**
   * 中型设备对应的列数
   */
  static readonly COLUMN_MD: number = 6;

  /**
   * 大型设备对应的列数
   */
  static readonly COLUMN_LG: number = 12;

  /**
   * 大型设备下歌词区域对应的列数
   */
  static readonly COLUMN_LYRIC_LG: number = 7;

  /**
   * 设备通用水平方向间距值
   */
  static readonly GUTTER_X: number = 12;

  /**
   * 音乐相关区域水平方向间距值
   */
  static readonly GUTTER_MUSIC_X: number = 24;

  /**
   * 小型设备对应的占据列数
   */
  static readonly SPAN_SM: number = 4;

  /**
   * 中型设备对应的占据列数
   */
  static readonly SPAN_MD: number = 6;

  /**
   * 大型设备对应的占据列数
   */
  static readonly SPAN_LG: number = 8;

  /**
   * 大型设备下歌词区域对应的占据列数
   */
  static readonly SPAN_LYRIC_LG: number = 5;

  /**
   * 小型设备对应的偏移列数
   */
  static readonly OFFSET_SM: number = 0;

  /**
   * 中型设备对应的偏移列数
   */
  static readonly OFFSET_MD: number = 1;

  /**
   * 大型设备对应的偏移列数
   */
  static readonly OFFSET_LG: number = 2;

  /**
   * 大型设备(次级规格)对应的偏移列数
   */
  static readonly OFFSET_LGS: number = 3;

  /**
   * 用于查询设备类型的当前断点标识键名
   */
  static readonly CURRENT_BREAKPOINT: string = 'currentBreakpoint';



  /**
   * 小型设备的宽度范围(媒体查询表达式)
   */
  static readonly RANGE_SM: string = '(320vp<=width<600vp)';

  /**
   * 中型设备的宽度范围(媒体查询表达式)
   */
  static readonly RANGE_MD: string = '(600vp<=width<840vp)';

  /**
   * 大型设备的宽度范围(媒体查询表达式)
   */
  static readonly RANGE_LG: string = '(840vp<=width)';


}

响应式布局实现(视图层)

// 导入导航组件和默认动画器
import { HMDefaultGlobalAnimator, HMNavigation } from "[@hadss](/user/hadss)/hmrouter";
// 导入属性更新器,用于自定义导航栏属性
import { AttributeUpdater } from "@kit.ArkUI";
// 导入断点常量,用于响应式布局
import { BreakpointConstants } from "../tool/BreakpointConstants";
// 导入导航常量
import {  NAVIGATION_ID } from "../tool/HMRouterPath";

/**
 * 应用首页组件
 * @Entry 装饰器:标记为页面入口组件
 */
@Entry
@Component
export struct Index {
  // 导航栏修饰器实例
  modifier: MyNavModifier = new MyNavModifier();
  
  /**
   * 响应式断点状态
   * @StorageProp 装饰器:从全局存储中获取断点值
   * - 用于根据屏幕尺寸调整布局
   * - 默认值为小屏幕断点
   */
  @StorageProp('currentBreakpoint') currentBreakpoint: string = BreakpointConstants.BREAKPOINT_SM;

  /**
   * 构建页面 UI 结构
   */
  build() {
    // 创建垂直布局容器
    Column() {
      // 创建主导航容器
      HMNavigation({
        navigationId: NAVIGATION_ID, // 导航容器唯一标识
        homePageUrl: 'MainPage',      // 默认显示的首页
        options: {
          standardAnimator: HMDefaultGlobalAnimator.STANDARD_ANIMATOR, // 标准动画器
          dialogAnimator: HMDefaultGlobalAnimator.DIALOG_ANIMATOR,     // 对话框动画器
          modifier: this.modifier                                     // 导航栏修饰器
        }
      });
    }
    .height('100%')        // 高度100%
    .width('100%')         // 宽度100%
    .backgroundColor(Color.Red) // 背景色
  }
}

/**
 * 导航栏修饰器类
 * 继承自 AttributeUpdater,用于自定义导航栏属性
 */
class MyNavModifier extends AttributeUpdater<NavigationAttribute> {
  /**
   * 初始化修饰器
   * [@param](/user/param) instance 导航栏属性实例
   */
  initializeModifier(instance: NavigationAttribute): void {
    // 隐藏导航栏
    instance.hideNavBar(true);
  }
}
// 导入 HMNavigation 和 HMRouter 组件,用于页面导航和路由管理
import { HMNavigation, HMRouter } from '[@hadss](/user/hadss)/hmrouter';
// 导入断点常量,用于响应式布局
import { BreakpointConstants } from '../tool/BreakpointConstants';
// 导入导航常量和路由路径对象
import { CHILD_NAVIGATION, HMRouterPath, HMRouterPathLG } from '../tool/HMRouterPath';

/**
 * 主页面组件
 * @HMRouter 装饰器:注册页面到路由系统
 * - pageUrl: 页面唯一标识,用于路由跳转
 * - singleton: 是否单例模式,确保页面只创建一次
 * - lifecycle: 生命周期管理模式,设置为退出应用时销毁
 */
@HMRouter({ pageUrl: 'MainPage', singleton: true, lifecycle: 'ExitAppLifecycle' })
@Component
export  struct MainPage {
  /**
   * 响应式断点状态
   * @StorageProp 装饰器:从全局存储中获取断点值
   * - 用于根据屏幕尺寸调整布局和导航逻辑
   * - 默认值为小屏幕断点
   */
  @StorageProp('currentBreakpoint') currentBreakpoint: string = BreakpointConstants.BREAKPOINT_SM;

  /**
   * 构建页面 UI 结构
   */
  build() {
    // 创建网格布局容器,用于实现响应式布局
    GridRow() {
      // 左侧网格列:包含导航按钮
      GridCol({
        span: {
          // 设置不同屏幕尺寸下的列宽
          sm: BreakpointConstants.COLUMN_LG, // 小屏幕(手机):占满12个栅格
          md: BreakpointConstants.COLUMN_SM, // 中等屏幕:占4个栅格
          lg: BreakpointConstants.COLUMN_SM  // 大屏幕:占4个栅格
        }
      }) {
        // 垂直布局容器,用于放置导航按钮
        Column() {
          // 个人中心导航按钮
          Button('个人中心')
            .width(200)      // 按钮宽度
            .height(40)       // 按钮高度
            .margin({top:50}) // 顶部外边距
            .onClick(()=>{     // 点击事件处理
              // 根据当前断点选择不同的导航路径
              if (this.currentBreakpoint === BreakpointConstants.BREAKPOINT_SM) {
                // 小屏幕(手机):使用主导航容器
                HMRouterPath.push('PersonagePage')
              } else {
                // 大屏幕(折叠屏):使用子导航容器
                HMRouterPathLG.push('PersonagePage')
              }
            })
          
          // 设置页面导航按钮
          Button('设置')
            .width(200)      // 按钮宽度
            .height(40)       // 按钮高度
            .margin({top:50}) // 顶部外边距
            .onClick(()=>{     // 点击事件处理
              // 根据当前断点选择不同的导航路径
              if (this.currentBreakpoint === BreakpointConstants.BREAKPOINT_SM) {
                // 小屏幕(手机):使用主导航容器
                HMRouterPath.push('SetPage')
              } else {
                // 大屏幕(折叠屏):使用子导航容器
                HMRouterPathLG.push('SetPage')
              }
            })
        }
        .width('100%')        // 列宽100%
        .height('100%')        // 列高100%
        .backgroundColor(Color.Blue) // 列背景色
      }
      
      // 右侧网格列:用于显示子页面内容
      GridCol({
        span: {
          // 设置不同屏幕尺寸下的列宽
          sm: BreakpointConstants.COLUMN_SM, // 小屏幕(手机):占4个栅格
          md: BreakpointConstants.COLUMN_SM, // 中等屏幕:占4个栅格
          lg: BreakpointConstants.SPAN_LG    // 大屏幕:占8个栅格
        }
      }) {
        // 根据断点判断是否显示内容
        if (this.currentBreakpoint === BreakpointConstants.BREAKPOINT_SM) {
          // 小屏幕(手机):不显示内容(留空)
        } else {
          // 大屏幕(折叠屏):显示导航容器
          // 创建子导航容器,用于管理设置页面等子页面
          HMNavigation({
            navigationId: CHILD_NAVIGATION, // 导航容器唯一标识
            homePageUrl: 'SettingPage',     // 默认显示的首页
          });
        }
      }
    }
    .width('100%')        // 网格行宽100%
    .height('100%')        // 网格行高100%
    .backgroundColor(Color.Orange) // 网格行背景色
  }
}

效果总结

  • 小屏(<600vp):侧边栏完全隐藏,内容区占满屏幕,跳转后全屏覆盖;
  • 中大屏(≥600vp):左侧 4 列导航栏固定,右侧 8 列内容区独立显示,间距统一;
  • 窗口尺寸动态变化时,布局实时切换无卡顿,栅格占比始终合理。

难点 2:多导航容器隔离与路由逻辑冲突

1. 问题说明

主导航(全屏跳转)与子导航(内容区跳转)共用路由栈,导致页面返回逻辑混乱;不同屏幕下跳转目标容器错误,出现 “小屏跳转子导航”“大屏跳转主导航” 的异常。

2. 原因分析

  • 导航容器标识未隔离:未给主导航和子导航分配独立navigationId,路由操作无法区分目标容器;
  • 路由逻辑分散:跳转 / 返回逻辑直接写在组件中,未统一封装,导致断点判断重复且易出错;
  • 页面栈管理混乱:未给不同导航容器配置独立生命周期,页面创建 / 销毁逻辑冲突。

3. 解决思路

  • 双导航容器隔离:定义NAVIGATION_ID(主导航)和CHILD_NAVIGATION(子导航),明确路由操作目标;
  • 统一路由工具类:封装HMRouterPathHMRouterPathLG,集中处理断点判断与容器选择;
  • 生命周期适配:主导航页面支持 “双击退出”,子导航页面保持单例,无重复创建。

4. 解决方案(基于代码实现)

路由常量与工具类(交互层)

// 导入路由相关的类型和管理器
import { HMParamType, HMRouterMgr, HMRouterPathInfo } from "[@hadss](/user/hadss)/hmrouter";

/**
 * 主导航容器唯一标识
 * 用于管理应用的主要页面导航
 */
export const NAVIGATION_ID: string = 'mainNavigation';

/**
 * 子导航容器唯一标识
 * 用于管理设置页面等子页面导航
 */
export const CHILD_NAVIGATION:string = 'childNavigation';

/**
 * 路由路径管理类
 * 提供页面导航的静态方法
 */
export class HMRouterPath {
  /**
   * 替换当前页面
   * [@param](/user/param) url 目标页面的唯一标识
   * [@param](/user/param) data 导航参数和回调
   */
  static replace(url: string, data?: HMRouterMgrData) {
    HMRouterMgr.replace({
      navigationId: NAVIGATION_ID, // 导航容器标识
      pageUrl: url,                // 目标页面标识
      param: data?.params,         // 传递的参数
      animator: true,              // 是否启用动画
    }, {
      onResult: data?.onResult,     // 页面返回时的回调
    });
  }
  
  /**
   * 获取页面路由栈
   * [@returns](/user/returns) 路由栈信息
   */
  public static getPathStack() {
    const path = HMRouterMgr.getPathStack(NAVIGATION_ID);
    return path;
  }

  /**
   * 推入新页面
   * [@param](/user/param) url 目标页面的唯一标识
   * [@param](/user/param) data 导航参数和回调
   */
  static push(url: string, data?: HMRouterMgrData) {
    HMRouterMgr.push({
      navigationId: NAVIGATION_ID, // 导航容器标识
      pageUrl: url,                // 目标页面标识
      param: data?.params,         // 传递的参数
      animator: true,              // 是否启用动画
    }, {
      onResult: data?.onResult,     // 页面返回时的回调
    });
  }

  /**
   * 弹出页面
   * [@param](/user/param) pathInfo 路径信息,包含导航容器标识、页面标识等
   * [@param](/user/param) skipedLayerNumber 跳过的层级数
   */
  static pop(pathInfo?: HMRouterPathInfo, skipedLayerNumber?: number) {
    HMRouterMgr.pop({
      navigationId: pathInfo?.navigationId || NAVIGATION_ID, // 导航容器标识,默认使用主导航
      pageUrl: pathInfo?.pageUrl,                           // 页面标识
      param: pathInfo?.param,                               // 传递的参数
      animator: pathInfo?.animator || true,                 // 是否启用动画,默认启用
    }, skipedLayerNumber);
  }

  /**
   * 获取当前页面的参数
   * [@param](/user/param) type 参数类型
   * [@returns](/user/returns) 当前页面的参数
   */
  static getCurrentParam(type?: HMParamType) {
    return HMRouterMgr.get

更多关于HarmonyOS 鸿蒙Next中HMRouter侧边分栏功能技术方案的实战教程也可以访问 https://www.itying.com/category-93-b0.html

3 回复

666 这个非常有用;

更多关于HarmonyOS 鸿蒙Next中HMRouter侧边分栏功能技术方案的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


HarmonyOS Next中HMRouter侧边分栏功能基于ArkUI框架实现,通过Navigation组件构建主从页面结构。主页面为侧边栏导航菜单,从页面为内容显示区域。HMRouter负责管理页面路由栈,实现侧边栏菜单项与内容页面的动态绑定与切换。该方案利用Navigation的Split模式,在平板等大屏设备上自动呈现分栏布局,并通过路由API控制页面跳转与状态保持。

这是一个非常专业且完整的HMRouter侧边分栏技术方案分享。该方案清晰地展示了如何在HarmonyOS Next中实现跨设备的响应式导航架构,其核心价值在于将响应式布局、路由管理与多容器隔离进行了深度整合。

方案的核心亮点在于:

  1. 分层架构设计:清晰的数据层(常量配置)、交互层(路由与断点逻辑)、视图层(UI渲染)分离,确保了代码的可维护性和可扩展性。

  2. 响应式布局的精准控制:通过GridRow/GridCol结合@StorageProp监听的全局断点状态,实现了从布局到间距的精细化适配,解决了小屏隐藏、大屏分栏的核心问题。

  3. 多导航容器的彻底隔离:定义NAVIGATION_IDCHILD_NAVIGATION,并封装HMRouterPathHMRouterPathLG两个路由工具类,是解决大/小屏路由逻辑冲突的关键。这使得主导航(全屏栈)和子导航(侧边内容栈)完全独立,避免了页面栈管理的混乱。

  4. 全局断点状态的实时同步:在EntryAbilityonWindowStageCreate中初始化并监听windowSizeChange事件,将断点状态实时存入AppStorage,确保了所有组件能通过响应式UI声明立刻获取最新状态并重绘,实现了无缝的布局切换体验。

该方案不仅提供了可运行的代码,更重要的是提炼出了一套在鸿蒙生态下处理复杂响应式导航的通用设计模式,对于开发需要适配手机、平板、折叠屏等多形态设备的应用具有很高的参考价值。

回到顶部