Skip to content

10_Engineering/07 - 多端构建与适配

1. HarmonyOS 多端覆盖

HarmonyOS 的核心理念是"一次开发,多端部署"。覆盖的设备类型包括:

设备类型设备形态屏幕范围分辨率
phone手机3.5" ~ 7"720x1280 ~ 1440x3200
tablet平板8" ~ 14"1200x1920 ~ 2000x3000
fold折叠屏内屏 ~ 外屏可变
car车机10" ~ 15"1080x1920 ~ 1440x2560
wearable手表1" ~ 2"360x360 ~ 454x454
tv电视40" ~ 80"1920x1080 ~ 3840x2160
desktop桌面13" ~ 32"1080x1920 ~ 3840x2160

2. 多端适配策略

2.1 布局适配方式

方式说明适用场景
自适应布局FlexGrid 自动缩放大部分场景
响应式布局基于断点的条件渲染手机/平板切换
相对布局百分比、比例容器内布局
断点系统breakpoint 断点匹配按设备类型切换
媒体查询media 查询按屏幕尺寸切换

2.2 断点系统(Breakpoints)

typescript
// 断点定义
const BREAKPOINTS = {
  phoneSmall: 320,   // 小屏手机
  phone: 360,        // 标准手机
  fold: 600,         // 折叠屏内屏
  tabletSmall: 840,  // 小平板
  tablet: 1200,      // 标准平板
  desktop: 1920,     // 桌面端
} as const;

// ArkUI 中获取当前断点
import { window } from '@kit.ArkUI';

@Entry
@Component
struct AdaptivePage {
  @State currentBreakpoint: number = BREAKPOINTS.phone;

  aboutToAppear() {
    // 监听窗口宽度变化
    window.getLastWindow(getContext(this)).then((win) => {
      win.on('windowSizeChange', (size) => {
        if (size.width < BREAKPOINTS.fold) {
          this.currentBreakpoint = BREAKPOINTS.phone;
        } else {
          this.currentBreakpoint = BREAKPOINTS.tablet;
        }
      });
    });
  }

  build() {
    // 根据断点渲染不同布局
    if (this.currentBreakpoint < BREAKPOINTS.fold) {
      // 手机布局
      PhoneLayout()
    } else {
      // 平板/桌面布局
      TabletLayout()
    }
  }
}

2.3 ArkUI 布局适配组件

typescript
@Entry
@Component
struct MultiDevicePage {
  @State windowWidth: number = 0;

  build() {
    Stack() {
      // 方式1:百分比布局
      Column() {
        Column() {
          Text('Header')
            .width('100%')
        }
        .height('100%')

        // 方式2:Flex 弹性布局
        Flex({ direction: FlexDirection.Row, wrap: FlexWrap.Wrap }) {
          ForEach(this.items, (item: string) => {
            Card() {
              Text(item)
            }
            .width('30%')     // 30% 宽度,自适应
            .height('120vp')
            .margin(5)
          })
        }
        .width('100%')
        .padding(10)
      }
      .width('100%')
      .height('100%')
    }
    .onAppear(() => {
      const win = window.getLastWindow(getContext(this));
      this.windowWidth = win?.getWindowAvoidArea(AvoidAreaType.AA_TYPE_NOTCH)?.rect?.width ?? 0;
    })
  }
}

2.4 适配 vp 与 fp

单位全称说明
vpvirtual pixel虚拟像素,自动缩放,推荐用于尺寸
fpfont pixel字体像素,自动缩放,推荐用于字号
pxphysical pixel物理像素,固定值
ratioratio比例单位,基于设计稿

3. deviceTypes 声明

3.1 module.json5 声明支持的设备

json5
{
  "module": {
    "name": "entry",
    "type": "entry",
    "deviceTypes": ["phone", "tablet", "fold"],
    // deviceTypes 可选值:
    // ["phone"],
    // ["phone", "tablet"],
    // ["phone", "tablet", "fold"],
    // ["phone", "tablet", "car", "wearable", "tv", "desktop"]
  }
}

3.2 多设备资源目录

resources/
├── base/                    # 默认资源(所有设备共用)
│   └── element/
│       └── string.json
├── rawfile/                 # 原始文件
├── phone/                   # 手机专用资源
│   ├── media/
│   │   └── phone_bg.png     # 手机背景图
│   └── layout/
│       └── phone_layout.json
├── tablet/                  # 平板专用资源
│   ├── media/
│   │   └── tablet_bg.png
│   └── layout/
│       └── tablet_layout.json
├── fold/                    # 折叠屏专用资源
│   ├── media/
│   │   └── fold_bg.png
│   └── profile/
│       └── fold_config.json

4. 折叠屏适配

4.1 折叠屏特殊处理

typescript
import { window } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';

@Entry
@Component
struct FoldablePage {
  @State isFolded: boolean = false;
  @State windowWidth: number = 0;

  aboutToAppear() {
    window.getLastWindow(getContext(this)).then((win) => {
      // 监听折叠状态变化
      win.on('windowSizeChange', (size, reason) => {
        if (reason === window.WindowSizeChangeReason.REASON_WINDOW_ADJUST_RESIZE) {
          this.windowWidth = size.width;
          this.isFolded = size.width < 600;
        }
      });

      // 监听折叠事件
      win.on('foldChange', (foldStatus: number) => {
        // 0: 展开  1: 折叠中  2: 折叠完成
        this.isFolded = foldStatus === 2;
      });
    });
  }

  build() {
    if (this.isFolded) {
      // 折叠状态:单屏布局
      Column() {
        Text('Single Screen Layout')
          .width('100%')
      }
    } else {
      // 展开状态:双屏/多列布局
      Row() {
        Column() {
          List() {
            ForEach(this.items, (item) => {
              ListItem() {
                Text(item.name)
              }
            })
          }
          .width('40%')
        }
        .height('100%')

        Divider()
          .vertical(true)

        Column() {
          DetailView({ data: this.selectedItem })
        }
        .width('60%')
        .height('100%')
      }
      .width('100%')
      .height('100%')
    }
  }
}

4.2 折叠屏适配要点

适配点说明
居中布局折叠时避免内容被折叠线分割
双窗格展开时支持左右/上下双窗格
手势处理折叠手势需要特殊处理
状态保持折叠/展开时保持页面状态
多窗口支持多窗口并排

5. 多端构建配置

5.1 多设备构建 target

json5
// build-profile.json5
{
  "modules": [
    {
      "name": "entry",
      "srcPath": "./entry",
      "targets": [
        {
          "name": "default",
          "applyToProducts": ["default"],
          "applyToSDKVersion": ["release"]
        }
      ]
    }
  ]
}

5.2 多设备适配开发策略

策略说明成本
一套代码 + 条件渲染通过断点判断设备类型
多套资源不同设备使用不同资源文件
多套组件不同设备使用不同 UI 组件
多套页面不同设备使用不同页面布局
多模块不同设备类型独立模块最高

6. 面试高频考点

Q1: 鸿蒙如何实现一套代码多端部署?

回答要点

  • ArkUI 的 vp/fp 单位自动适配不同 DPI
  • Flex/Grid 弹性布局自动缩放
  • deviceTypes 声明支持的设备
  • 资源目录按设备类型区分(phone/tablet/fold)
  • 断点系统(breakpoint)条件渲染

Q2: 折叠屏适配的关键点?

回答要点

  • 监听 foldChange 事件
  • 折叠时单屏,展开时双屏
  • 避免内容被折叠线分割
  • 保持页面状态
  • 居中布局和双窗格设计

Q3: vp 和 fp 的区别?

回答要点

  • vp 用于尺寸(宽/高/间距),自动适配屏幕密度
  • fp 用于字体大小,随系统字体设置缩放
  • px 是物理像素,不随 DPI 变化
  • 推荐优先使用 vp/fp,仅在需要固定像素时使用 px

7. Android 对比

概念AndroidHarmonyOS
适配drawable-/layout-resources/phone/tablet/fold/
断点ConstraintLayout断点系统 + 条件渲染
多窗口MultiWindow多窗口管理
折叠屏WindowManagerfoldChange 事件
单位dp/spvp/fp
设备类型compatible-screensdeviceTypes