Appearance
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 布局适配方式
| 方式 | 说明 | 适用场景 |
|---|---|---|
| 自适应布局 | Flex、Grid 自动缩放 | 大部分场景 |
| 响应式布局 | 基于断点的条件渲染 | 手机/平板切换 |
| 相对布局 | 百分比、比例 | 容器内布局 |
| 断点系统 | 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
| 单位 | 全称 | 说明 |
|---|---|---|
vp | virtual pixel | 虚拟像素,自动缩放,推荐用于尺寸 |
fp | font pixel | 字体像素,自动缩放,推荐用于字号 |
px | physical pixel | 物理像素,固定值 |
ratio | ratio | 比例单位,基于设计稿 |
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.json4. 折叠屏适配
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 对比
| 概念 | Android | HarmonyOS |
|---|---|---|
| 适配 | drawable-/layout- | resources/phone/tablet/fold/ |
| 断点 | ConstraintLayout | 断点系统 + 条件渲染 |
| 多窗口 | MultiWindow | 多窗口管理 |
| 折叠屏 | WindowManager | foldChange 事件 |
| 单位 | dp/sp | vp/fp |
| 设备类型 | compatible-screens | deviceTypes |