前面几篇都是在折叠屏上开发的,布局看着挺好。但扔到手机上一跑,问题就来了------三栏布局在手机竖屏下挤成一团,文字小得看不清,左右边距也不对。这篇专门讲多设备适配,也是鸿蒙开发里和 Android 很不一样的地方。
HarmonyOS 7 的设备形态比 Android 更丰富:手机、折叠屏、平板、智慧屏,同一个应用要跑在这么多设备上,不能每台设备都写一套代码。官方提供了响应式布局的能力,用对了能省很多事。
设计思路:不要写死宽度
最开始我们直接把折叠屏上的布局搬到手机上,结果就是:左侧导航 200vp,右侧详情 360vp,中间内容区在手机上只剩下 100vp 左右,根本没法看。
正确的思路是:不要写死具体宽度,而是根据窗口大小动态调整布局。鸿蒙提供了几种方式:媒体查询、断点、自适应组件。
我们项目里主要用了断点的方式。把屏幕尺寸分成三档:小屏(<600vp) 、中屏(600-840vp) 、大屏(>840vp)。不同断点下布局不一样。

断点适配的实际实现
ArkUI 没有内置的断点 API,需要自己监听窗口尺寸变化。下面是我们封装的断点工具类,文件位置在 utils/BreakpointSystem.ets:
typescript
// 断点类型
export enum BreakpointType {
SM = 'sm', // 小屏:手机竖屏
MD = 'md', // 中屏:折叠屏展开、平板竖屏
LG = 'lg' // 大屏:折叠屏平板横屏、智慧屏
}
@Entry
@Component
struct WorkbenchPage {
@State currentBreakpoint: BreakpointType = BreakpointType.SM;
private windowStage: window.WindowStage | null = null;
async aboutToAppear() {
// 获取窗口对象,监听尺寸变化
this.windowStage = await window.getLastWindow(this.context);
this.updateBreakpoint(this.windowStage.getWindowProperties().windowRect.width);
// 注册窗口尺寸变化监听
this.windowStage.on('windowSizeChange', (size: window.Size) => {
this.updateBreakpoint(size.width);
});
}
private updateBreakpoint(width: number) {
// 按宽度判断断点
if (width < 600) {
this.currentBreakpoint = BreakpointType.SM;
} else if (width < 840) {
this.currentBreakpoint = BreakpointType.MD;
} else {
this.currentBreakpoint = BreakpointType.LG;
}
}
build() {
// 根据断点决定布局方式
if (this.currentBreakpoint === BreakpointType.LG) {
// 大屏:三栏布局
this.threeColumnLayout()
} else if (this.currentBreakpoint === BreakpointType.MD) {
// 中屏:两栏布局(隐藏右侧详情,用弹窗或跳转)
this.twoColumnLayout()
} else {
// 小屏:单栏布局(Tab 切换或导航抽屉)
this.singleColumnLayout()
}
}
// 大屏三栏布局
@Builder
threeColumnLayout() {
Row() {
NavSideBar().width(200)
ContentList().layoutWeight(1)
DetailPanel().width(360)
}
.width('100%')
.height('100%')
}
// 中屏两栏布局
@Builder
twoColumnLayout() {
Row() {
NavSideBar().width(160)
ContentList().layoutWeight(1)
// 详情面板:点击 item 时从右侧弹出
}
.width('100%')
.height('100%')
}
// 小屏单栏布局
@Builder
singleColumnLayout() {
Column() {
// 顶部导航栏
TopNavBar()
// 内容列表
ContentList().layoutWeight(1)
}
.width('100%')
.height('100%')
}
}
这个方案的好处:同一个页面,根据屏幕宽度自动切换布局。折叠屏展开的时候是三栏,合上变成手机就是单栏,不需要写多个页面。
组件内部的自适应
除了页面级别的布局切换,组件内部也要做适配。比如内容卡片,在大屏上可以显示更多信息,在小屏上就精简一下。
这里用了 @Builder 和条件渲染。还是那个 ContentCard 组件,根据断点调整显示的内容:
typescript
@Component
struct ContentCard {
@ObjectLink item: ContentItem;
@Prop breakpoint: BreakpointType;
build() {
Row({ space: 12 }) {
// 小屏:不显示封面图
if (this.breakpoint !== BreakpointType.SM) {
Image(this.item.coverUrl)
.width(80)
.height(80)
.borderRadius(8)
}
Column({ space: 6 }) {
Text(this.item.title)
.fontSize(this.breakpoint === BreakpointType.LG ? 16 : 15)
// 小屏:只显示一行摘要
Text(this.item.summary)
.fontSize(13)
.maxLines(this.breakpoint === BreakpointType.SM ? 1 : 2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
// 大屏:显示标签;小屏:隐藏标签
if (this.breakpoint !== BreakpointType.SM) {
Row({ space: 8 }) {
ForEach(this.item.tags, (tag: string) => {
Text(tag).fontSize(11)
}, (tag: string) => tag)
}
}
}
.layoutWeight(1)
}
.width('100%')
.padding(12)
}
}
组件内部根据断点调整字号、显示行数、隐藏不必要的元素。这样一套组件,在不同尺寸下都能有合理的显示效果。

折叠屏的特殊处理
折叠屏比普通多了一个状态:折叠和展开。开合的时候窗口尺寸变化很大,上面的断点方案已经能处理了------展开的时候是 LG,合上变成 SM,自动切换布局。
但有个细节:开合的过程中要保持当前的选中状态。比如展开时选了第 5 篇文章,合上之后不能丢失这个选中态。我们的状态都在顶层 WorkbenchPage 里管理,窗口尺寸变化只是切换了布局,状态本身不变,所以这个问题自然就解决了。
还有一个容易忽略的点:折叠屏的分屏模式。如果应用在分屏下运行,窗口宽度可能正好在断点边界上。我们的断点是动态计算的,每次窗口尺寸变化都重新判断,所以分屏下也能正常工作。
横竖屏切换
工作台类应用一般锁定横屏,但如果要支持竖屏,也要注意适配。竖屏下宽度变窄,自动落到 SM 断点,切换成单栏布局。
横竖屏切换的时候,系统会重新创建页面,状态会丢失。如果要保留状态,可以用 AppStorage 或者 Preferences 把关键状态存下来,页面重建的时候再读回来。
适配调试技巧
DevEco Studio 的模拟器可以模拟不同设备:手机、折叠屏、平板、智慧屏。调试的时候多切换几种设备看效果。
还有个小技巧:打开布局边界(Show Layout Bounds),能看到每个组件的实际大小和边距,方便调整布局间距。
我们当时适配的时候,把所有页面都在折叠屏模拟器上打开开合好几次,确认布局切换没有闪烁、状态没有丢失,才算完成。
小结
多设备适配的核心就是:不要写死尺寸,用断点动态切换布局。HarmonyOS 7 的响应式能力已经很完善了,只要设计思路对,一套代码跑多设备不是难事。最后一篇讲工程收尾,怎么把页面做到可上线状态。