01-万少带你精读鸿蒙codelabs 一多界面适配和三层架构 
前言 
本文将深入解析华为开发者联盟 CodeLabs 上的优质一多开发示例项目——MultiShopping。该项目展示了鸿蒙应用开发中的最佳实践,特别是在响应式布局和工程级一多架构方面的实现。项目地址:https://developer.huawei.com/consumer/cn/codelabsPortal/carddetails/tutorials_Next-MultiShopping
一次开发,多端部署-购物应用 功能演示 



一、响应式布局解决方案:断点系统 
在开发跨设备应用时,响应式布局是确保应用在不同尺寸设备上都能提供良好用户体验的关键。MultiShopping 项目中实现了一套优雅的断点系统(Breakpoint System),用于检测设备屏幕尺寸并自动调整 UI 布局。
1.1 断点系统的核心实现 
断点系统的核心代码位于common/src/main/ets/utils/BreakpointSystem.ets文件中,主要包含两个关键类:
BreakpointSystem 类 
该类负责监听设备屏幕尺寸变化,并将当前断点信息存储到 AppStorage 中:
export class BreakpointSystem {
  private currentBreakpoint: string = "";
  private smListener?: mediaquery.MediaQueryListener;
  private mdListener?: mediaquery.MediaQueryListener;
  private lgListener?: mediaquery.MediaQueryListener;
  private updateCurrentBreakpoint(breakpoint: string) {
    if (this.currentBreakpoint !== breakpoint) {
      this.currentBreakpoint = breakpoint;
      AppStorage.set<string>(
        BreakpointConstants.CURRENT_BREAKPOINT,
        this.currentBreakpoint
      );
    }
  }
  public register() {
    this.smListener = mediaquery.matchMediaSync(BreakpointConstants.RANGE_SM);
    this.smListener.on("change", this.isBreakpointSM);
    this.mdListener = mediaquery.matchMediaSync(BreakpointConstants.RANGE_MD);
    this.mdListener.on("change", this.isBreakpointMD);
    this.lgListener = mediaquery.matchMediaSync(BreakpointConstants.RANGE_LG);
    this.lgListener.on("change", this.isBreakpointLG);
  }
  public unregister() {
    this.smListener?.off("change", this.isBreakpointSM);
    this.mdListener?.off("change", this.isBreakpointMD);
    this.lgListener?.off("change", this.isBreakpointLG);
  }
}BreakPointType 类 
该类提供了一种便捷的方式,根据当前断点类型返回对应的 UI 配置值:
export class BreakPointType<T> {
  options: BreakPointTypeOption<T>;
  constructor(option: BreakPointTypeOption<T>) {
    this.options = option;
  }
  getValue(currentBreakPoint: string): T {
    if (this.options.sm !== undefined && currentBreakPoint === "sm") {
      return this.options.sm as T;
    }
    if (this.options.md && currentBreakPoint === "md") {
      return this.options.md as T;
    } else {
      return this.options.lg as T;
    }
  }
}1.2 断点常量定义 
断点系统使用的常量定义在common/src/main/ets/constants/BreakpointConstants.ets文件中:
export class BreakpointConstants {
  /**
   * 表示小型设备的断点
   */
  static readonly BREAKPOINT_SM: string = "sm";
  /**
   * 表示中型设备的断点
   */
  static readonly BREAKPOINT_MD: string = "md";
  /**
   * 表示大型设备的断点
   */
  static readonly BREAKPOINT_LG: string = "lg";
  /**
   * 当前断点,用于查询设备类型
   */
  static readonly CURRENT_BREAKPOINT: string = "currentBreakpoint";
  /**
   * 小型设备宽度范围
   */
  static readonly RANGE_SM: string = "(320vp<=width<520vp)";
  /**
   * 中型设备宽度范围
   */
  static readonly RANGE_MD: string = "(520vp<=width<840vp)";
  /**
   * 大型设备宽度范围
   */
  static readonly RANGE_LG: string = "(840vp<=width)";
}1.3 断点系统的工作原理 
关键方法解析 
- mediaquery.matchMediaSync: - 这是鸿蒙系统提供的媒体查询 API,用于创建一个媒体查询监听器
- 参数是一个媒体查询条件字符串,例如"(320vp<=width<520vp)"表示屏幕宽度在 320vp 到 520vp 之间
- 返回一个MediaQueryListener对象,可用于监听媒体查询条件的变化
- 与 Web 开发中的window.matchMedia()类似,但针对鸿蒙系统优化
 
- listener.on("change", callback): - 为媒体查询监听器注册一个变化事件的回调函数
- 当设备屏幕尺寸变化导致媒体查询条件结果发生变化时,会触发回调函数
- 回调函数接收一个MediaQueryResult对象,其中matches属性表示当前是否匹配查询条件
- 这种事件监听模式使断点系统能够实时响应设备尺寸变化
 
- AppStorage.set: - 将当前断点信息存储到应用的全局状态存储中
- 使用"currentBreakpoint"作为键,确保全应用范围内可访问
- 当断点变化时,所有使用@StorageProp('currentBreakpoint')的组件都会自动更新
 
断点系统工作流程图 

1.4 断点系统的使用流程 
- 初始化与注册: 在应用启动时(通常在入口页面的 - aboutToAppear生命周期函数中)初始化并注册断点系统:typescript- // SplashPage.ets @Entry @Component struct SplashPage { @StorageProp('currentBreakpoint') currentBreakpoint: string = 'sm'; private breakpointSystem = new BreakpointSystem(); aboutToAppear() { this.breakpointSystem.register(); // ...其他代码 } aboutToDisappear() { this.breakpointSystem.unregister(); // ...其他代码 } }
- 在组件中使用断点信息: 组件可以通过 - @StorageProp装饰器访问当前断点信息:typescript- @Component struct MyComponent { @StorageProp('currentBreakpoint') currentBreakpoint: string = 'md'; // ...组件代码 }
- 根据断点调整 UI: 使用 - BreakPointType类根据当前断点返回对应的 UI 配置:typescript- Image($r("app.media.ic_eshop")).width( new BreakPointType({ sm: $r("app.float.splash_image_size"), md: $r("app.float.splash_image_size"), lg: $r("app.float.splash_image_size_lg"), }).getValue(this.currentBreakpoint) );
1.5 断点系统的优势 
- 解耦与复用:断点逻辑与 UI 组件解耦,可在整个应用中复用
- 易于维护:集中管理断点定义,便于统一修改
- 类型安全:通过泛型支持,确保类型安全
- 使用简便:提供简洁的 API,降低开发者使用成本
- 实时响应:基于事件监听机制,能够实时响应设备尺寸变化
- 全局状态共享:利用 AppStorage 机制,确保断点信息在全应用范围内一致
二、工程级一多架构 
MultiShopping 项目采用了鸿蒙应用开发中常见的三层架构模式,实现了工程级的一多开发能力。
2.1 架构层次 
项目架构分为三层:
- 公共能力层(common): - 提供整个应用共享的基础组件、工具类和常量
- 包含断点系统等跨设备适配的核心能力
- 位于common/目录
 
- 基础特性层(features): - 实现应用的各个功能模块
- 每个功能模块相对独立,可单独开发和测试
- 位于features/目录,包含多个子目录如home/、personal/等
 
- 产品定制层(product): - 针对不同设备类型(如手机、平板等)的具体产品实现
- 整合公共能力层和基础特性层,构建完整产品
- 位于product/目录,目前包含phone/子目录
 
2.2 目录结构分析 
MultiShopping/
├── common/                 # 公共能力层
│   ├── src/main/ets/
│   │   ├── components/     # 公共UI组件
│   │   ├── constants/      # 公共常量定义
│   │   ├── utils/          # 公共工具类
│   │   └── viewmodel/      # 公共数据模型
│   └── ...
├── features/               # 基础特性层
│   ├── home/               # 首页功能模块
│   ├── personal/           # 个人中心功能模块
│   ├── shopcart/           # 购物车功能模块
│   ├── commoditydetail/    # 商品详情功能模块
│   └── ...
└── product/                # 产品定制层
    └── phone/              # 手机产品
        ├── src/main/ets/
        │   ├── pages/      # 页面定义
        │   ├── entryability/ # 应用入口
        │   └── ...
        └── ...2.3 层间依赖关系 
- 依赖方向:产品层 → 特性层 → 公共层
- 依赖管理:通过oh-package.json5文件管理模块间依赖
2.4 工程级一多架构的优势 
- 代码复用:公共能力层提供跨设备共享的基础能力,减少重复开发
- 职责分离:各层职责明确,便于团队协作开发
- 灵活适配:产品层可针对不同设备特性进行定制,提供最佳用户体验
- 可维护性:模块化设计使代码更易于维护和扩展
- 并行开发:不同团队可以并行开发不同层次或模块
总结 
MultiShopping 项目通过断点系统实现了优雅的响应式布局,通过三层架构实现了工程级的一多开发能力。这些设计不仅提高了开发效率,也确保了应用在不同设备上的一致性和良好体验。
对于鸿蒙应用开发者而言,这些设计模式和实践经验具有很高的参考价值,可以帮助开发者构建高质量的跨设备应用。通过学习和借鉴 MultiShopping 项目的实现,开发者可以更好地掌握鸿蒙应用开发的最佳实践。