HarmonyOS7 响应式布局:一套代码适配手机平板折叠屏

前言
折叠屏展开是平板、合上是手机——这种场景你写两套界面吗?显然不行。HarmonyOS7 提供了完整的响应式布局方案:断点系统 + GridRow/GridCol + 媒体查询,三件套一配,一套代码跑遍所有设备。今天就来拆解这套方案。
我之前做的一个 App,手机上跑得挺好,上了平板就被吐槽"全是空白浪费屏幕"。后来用响应式布局改造,手机上一列、平板上两列、大屏上三列,一套代码搞定。整个改造过程其实没多复杂,关键是理解断点系统。
多设备适配的挑战
先看下不同设备的屏幕差异有多大:
| 设备类型 | 典型宽度 | 布局特点 |

|----------|----------|----------|
| 手机 | 360-420vp | 单列,纵向滚动 |
| 折叠屏(合) | 360-400vp | 同手机 |
| 折叠屏(开) | 700-800vp | 双列,侧边栏 |
| 平板 | 800-1280vp | 多列,复杂布局 |
| 智慧屏 | 1280vp+ | 多列+侧边栏 |
核心问题:同样的内容,不同宽度要呈现不同的布局。你不能写死列数,得让布局根据宽度自动调整。
断点系统
HarmonyOS7 定义了 4 个标准断点,这是响应式布局的基础:
| 断点 | 宽度范围 | 典型设备 | 推荐列数 |
|---|---|---|---|
| xs | [0, 320vp) | 穿戴设备 | 1 列 |
| sm | [320, 600vp) | 手机 | 1-2 列 |
| md | [600, 840vp) | 折叠屏展开/小平板 | 2-3 列 |
| lg | [840vp, +∞) | 平板/智慧屏 | 3-4 列 |
系统会根据当前窗口宽度自动判断落在哪个断点区间。你只需要针对不同断点定义不同的布局参数。
断点判断逻辑
function getCurrentBreakpoint(width: number): string {
if (width < 320) return 'xs'
if (width < 600) return 'sm'
if (width < 840) return 'md'
return 'lg'
}
实际开发中不用手写这个函数,GridRow 组件内置了断点系统。
GridRow/GridCol 响应式
这是 HarmonyOS7 响应式布局的核心组件。GridRow 是栅格容器,GridCol 是栅格子组件——两者配合,自动根据断点调整列数。
@Entry
@Component
struct ResponsiveGridDemo {
@State currentBp: string = 'sm'
private items: string[] = ['新闻', '视频', '音乐', '游戏', '购物', '旅行']
build() {
Column() {
Text(`当前断点: ${this.currentBp}`)
.fontSize(14)
.margin({ bottom: 12 })
GridRow({
columns: { xs: 2, sm: 2, md: 3, lg: 4 },
gutter: { x: 12, y: 16 },
breakpoints: {
value: ['320vp', '600vp', '840vp'],
reference: BreakpointsReference.WindowSize
}
}) {
ForEach(this.items, (item: string) => {
GridCol({ span: { xs: 2, sm: 1, md: 1, lg: 1 } }) {
Column() {
Text(item)
.fontSize(16)
.fontColor(Color.White)
}
.width('100%')
.height(80)
.backgroundColor('#007DFF')
.borderRadius(8)
.justifyContent(FlexAlign.Center)
}
})
}
.width('100%')
.onBreakpointChange((breakpoint: string) => {
this.currentBp = breakpoint
})
}
.width('100%')
.height('100%')
.padding(16)
}
}

逐行讲解:
columns: { xs: 2, sm: 2, md: 3, lg: 4 }——定义不同断点下的栅格列数。手机 2 列,平板 3 列,大屏 4 列gutter控制行列间距breakpoints定义断点阈值,reference指定参考窗口大小GridCol的span定义子组件在不同断点下占据的列数onBreakpointChange在断点变化时回调,可以用来做额外的 UI 调整
效果:手机上 2 列展示,折叠屏展开自动变 3 列,平板上 4 列。代码就这么多,不用写任何条件判断。
媒体查询
GridRow/GridCol 能处理栅格布局的响应式,但有些场景需要更细粒度的控制——比如某个组件只在特定宽度下显示。这时候就要用媒体查询了。
import { mediaQuery } from '@kit.ArkUI'
@Entry
@Component
struct MediaQueryDemo {
@State isWide: boolean = false
private listener: mediaQuery.MediaQueryListener | null = null
aboutToAppear() {
this.listener = this.getUIContext().getMediaQuery()
.matchMediaSync('(width>=600vp)')
this.listener.on('change', (result: mediaQuery.MediaQueryResult) => {
this.isWide = result.matches as boolean
})
}
aboutToDisappear() {
if (this.listener) {
this.listener.off('change')
}
}
build() {
Row() {
if (this.isWide) {
Column() {
Text('侧边栏')
.fontSize(16)
}
.width(240)
.height('100%')
.backgroundColor('#F1F3F5')
.padding(16)
}
Column() {
Text('主内容区')
.fontSize(20)
}
.layoutWeight(1)
.height('100%')
.padding(16)
}
.width('100%')
.height('100%')
}
}
讲解:
matchMediaSync('(width>=600vp)')设置查询条件:宽度大于等于 600vplistener.on('change', ...)注册变化回调,条件匹配状态改变时触发aboutToDisappear里必须off掉监听,否则内存泄漏result.matches为true表示当前宽度满足条件
常见查询条件:
| 条件 | 含义 |
|---|---|
(width>=600vp) |
宽度 ≥ 600vp |
(orientation: landscape) |
横屏 |
(orientation: portrait) |
竖屏 |
(height>=800vp) |
高度 ≥ 800vp |
折叠屏适配
折叠屏是最典型的响应式场景——展开和合上,屏幕宽度可能差一倍。
@Entry
@Component
struct FoldableDemo {
@State isExpanded: boolean = false
private listener: mediaQuery.MediaQueryListener | null = null
aboutToAppear() {
this.listener = this.getUIContext().getMediaQuery()
.matchMediaSync('(width>=600vp)')
this.isExpanded = (this.listener.matches as boolean)
this.listener.on('change', (result: mediaQuery.MediaQueryResult) => {
this.isExpanded = result.matches as boolean
})
}
aboutToDisappear() {
this.listener?.off('change')
}
build() {
if (this.isExpanded) {
Row() {
this.SidePanel()
this.MainContent()
}
.width('100%')
.height('100%')
} else {
Column() {
this.MainContent()
}
.width('100%')
.height('100%')
}
}
@Builder
SidePanel() {
Column() {
Text('列表')
.fontSize(18)
.fontWeight(FontWeight.Bold)
List() {
ForEach(['项目1', '项目2', '项目3'], (item: string) => {
ListItem() {
Text(item).fontSize(14).padding(12)
}
})
}
.layoutWeight(1)
}
.width(280)
.height('100%')
.backgroundColor('#F5F5F5')
.padding(16)
}
@Builder
MainContent() {
Column() {
Text('详情内容')
.fontSize(20)
.fontWeight(FontWeight.Bold)
}
.layoutWeight(1)
.height('100%')
.padding(16)
.justifyContent(FlexAlign.Center)
}
}
核心逻辑:折叠屏合上时(< 600vp),只显示主内容区;展开时(≥ 600vp),左侧显示列表、右侧显示详情。这种"列表+详情"的双栏模式在邮件、笔记类 App 里非常常见。
敲黑板:折叠屏的展开/合上会触发
on('change')回调,但不会重建组件。你的布局变化逻辑必须放在回调里,不能只在aboutToAppear里初始化一次。
最佳实践
做响应式布局这些年,我总结了几条经验:
- 先定断点再写布局——别上来就写代码,先画不同断点下的线框图
- 用 GridRow/GridCol 做栅格——能解决的不要用媒体查询,栅格组件更省代码
- 媒体查询管条件逻辑——组件显示/隐藏、布局结构切换用媒体查询
- 监听器必须注销——
aboutToDisappear里off掉,不然后果你懂的 - 测试三种宽度——至少测 360vp(手机)、700vp(折叠屏展开)、1280vp(平板)
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 等宽网格排列 | GridRow/GridCol | 自动响应断点 |
| 侧边栏显示/隐藏 | 媒体查询 | 需要条件判断 |
| 列数自适应 | GridRow columns 参数 | 一行配置搞定 |
| 组件显示/隐藏 | if 条件 + 媒体查询 | 灵活控制 |
写在最后
响应式布局看着复杂,其实就三件事:断点定义 + 栅格布局 + 媒体查询。GridRow/GridCol 负责栅格自适应,媒体查询负责条件逻辑,断点系统是两者的桥梁。
一套代码适配所有设备,这事儿以前在 Web 上靠 CSS 媒体查询做,现在 HarmonyOS7 把这套理念搬到原生组件里了。用起来比 Web 还方便——至少不用写那些 CSS hack。
如果你正在做多设备适配,先把 GridRow 搞明白,大部分场景它就够用了。
更多推荐

所有评论(0)