HarmonyOS 多设备页面布局实战:响应式布局四种方式完整指南
本文介绍了鸿蒙开发中的多设备布局适配方法,重点讲解了响应式布局的实现原理。通过断点机制将屏幕宽度划分为四个区间(sm/md/lg/xl),结合栅格系统实现组件自适应。文章详细展示了两种重复布局的实现:列表布局通过lanes属性控制列数,瀑布流布局使用WaterFlow组件动态调整列数。针对不同设备尺寸,开发者可以灵活调整间距、列数等参数,确保界面在各种设备上都能良好展示。这种"一次开发,
一、多设备布局到底是个啥
做鸿蒙开发最头疼的是什么?不是代码难写,是设备太多。手机、折叠屏、平板、PC,每种设备屏幕尺寸都不一样。你写一套代码,想在这些设备上都能跑,界面还得好看,这就得多设备适配。
华为搞了个"一次开发,多端部署"的理念,说白了就是让你写一套代码,自动适配所有设备。咋实现的?靠的就是响应式布局。
响应式布局的核心是两个东西:断点 和 栅格。
断点是啥?就是把屏幕宽度分成几个区间,不同区间用不同的布局策略。比如手机屏幕小,就用 sm 断点;平板屏幕大,就用 lg 断点。
栅格是啥?就是把屏幕划分成若干等分的格子,组件占多少格子,你说了算。比如手机屏幕分成 4 格,平板分成 12 格,同一个组件在不同设备上占不同的格子数,布局就自适应了。

上图展示了四种响应式布局方式对应的不同场景和实现方案。重复布局适合列表、瀑布流这些;分栏布局适合侧边栏、导航这些;挪移布局适合图文组合、导航切换;缩进布局适合居中展示。
二、断点机制先搞懂
断点是把屏幕宽度划分成几个区间,每个区间对应一种设备形态。HarmonyOS 定义了四个横向断点:
| 断点 | 屏幕宽度 | 对应设备 |
|---|---|---|
| sm | < 600vp | 手机 |
| md | 600vp - 840vp | 折叠屏、小平板 |
| lg | 840vp - 1440vp | 平板 |
| xl | > 1440vp | PC、2in1 设备 |
vp 是虚拟像素,HarmonyOS 用来做布局的单位。为啥用 vp 不用 px?因为不同设备像素密度不一样,用 vp 能保证在不同设备上显示效果一致。
断点咋用?你得先拿到当前设备的断点值,然后根据断点值动态调整组件属性。官方给了一个 WidthBreakpointType 类,专门用来根据断点返回不同的值:
new WidthBreakpointType(1, 2, 3, 3).getValue(this.mainWindowInfo.widthBp)
上面这行代码啥意思?sm 断点返回 1,md 断点返回 2,lg 断点返回 3,xl 断点也返回 3。你可以用这个机制动态控制组件属性。
三、重复布局:空间够了就多展示
重复布局是最常用的方式。啥意思?屏幕小的时候展示少一点,屏幕大了就多展示一些,用相同或相似的结构重复排列。
重复布局包含四种:列表布局、瀑布流布局、轮播布局、网格布局。
1. 列表布局
List 组件有个 lanes 属性,可以控制列数。结合断点,就能实现不同设备展示不同列数:
List({
space: new WidthBreakpointType(8, 12, 16, 16).getValue(this.mainWindowInfo.widthBp),
scroller: this.listScroller
}) {
// ...
}
.scrollBar(BarState.Off)
.lanes(new WidthBreakpointType(1, 2, 3, 3).getValue(this.mainWindowInfo.widthBp), 12)
这段代码有几个关键点:
space控制行间距,sm 断点用 8vp,md 用 12vp,lg 和 xl 用 16vplanes控制列数,sm 单列,md 双列,lg 和 xl 三列- 第二个参数 12 是列间距
不同断点下的布局效果:

从上图能看到,sm 断点是单列列表,行间距 8vp。空间不够,就展示最少的列数。

md 断点是双列列表,列间距 12vp,行间距 12vp。折叠屏上,空间够展示两列。

lg 断点是三列列表,列间距 12vp,行间距 16vp。平板上,空间够展示三列。
2. 瀑布流布局
瀑布流跟列表类似,区别是瀑布流每个元素高度不一样。WaterFlow 组件专门干这事儿:
WaterFlow() {
LazyForEach(this.dataSource, (item: number, index: number) => {
FlowItem() {
Row() {}
.width('100%')
.height('100%')
.borderRadius(16)
.backgroundColor('#F1F3F5')
}
.width('100%')
.height(this.itemHeightArray[index])
}, (item: number, index: number) => JSON.stringify(item) + index)
}
.columnsTemplate(`repeat(${new WidthBreakpointType(2, 3, 4, 4).getValue(this.mainWindowInfo.widthBp)}, 1fr)`)
.columnsGap(12)
.rowsGap(12)
.width('100%')
关键属性是 columnsTemplate,用 repeat() 函数动态控制列数。sm 双列,md 三列,lg 和 xl 四列。

sm 断点是双列瀑布流,每个元素高度不一,错落有致。

md 断点是三列瀑布流,列间距 12vp,行间距 12vp。

lg 断点是四列瀑布流,充分利用平板的大屏空间。
瀑布流适合展示高度不一的内容,比如商品卡片、新闻卡片、社交媒体信息流。
3. 轮播布局
轮播也能做多设备适配。Swiper 组件有个 displayCount 属性,控制视窗内展示几个元素:
Swiper() {
// ...
}
.displayCount(new WidthBreakpointType(1, 2, 3, 3).getValue(this.mainWindowInfo.widthBp))
.indicator(this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_SM ? Indicator.dot()
.itemWidth(6)
.itemHeight(6)
.selectedItemWidth(12)
.selectedItemHeight(6)
.color('#4DFFFFFF')
.selectedColor(Color.White) : false
)
.prevMargin(new WidthBreakpointType(0, 12, 64, 64).getValue(this.mainWindowInfo.widthBp))
.nextMargin(new WidthBreakpointType(0, 12, 64, 64).getValue(this.mainWindowInfo.widthBp))
这段代码有几个细节:
displayCount控制视窗内元素个数,手机一个,折叠屏两个,平板三个indicator只在手机上显示圆点指示器,其他设备不显示prevMargin和nextMargin控制前后边距,手机没边距,折叠屏 12vp,平板 64vp

sm 断点展示一个元素,无前后边距,显示圆点指示器。

md 断点展示两个元素,前后边距 12vp,不显示指示器。

lg 断点展示三个元素,前后边距 64vp,能看到前后各露出一点相邻卡片。
轮播布局适合展示推荐内容、广告轮播、精选商品这些场景。
4. 网格布局
Grid 组件跟 WaterFlow 类似,区别是 Grid 的子组件高度一致,严格对齐。适合展示规则的内容:
Grid() {
ForEach(this.infoArray.slice(new WidthBreakpointType(4, 2, 0, 0).getValue(this.mainWindowInfo.widthBp)),
(item: number) => {
// ...
}, (item: number, index: number) => JSON.stringify(item) + index)
}
.width('100%')
.columnsTemplate(`repeat(${new WidthBreakpointType(2, 3, 4, 4).getValue(this.mainWindowInfo.widthBp)}, 1fr)`)
.columnsGap(12)
.rowsGap(12)
Grid 还有个 rowsTemplate 属性可以控制行数。不设置的话,行数 = 展示元素数量 / 列数。

sm 断点是 2 行 2 列网格,每个元素高度一致,严格对齐。

md 断点是 2 行 3 列网格,列间距 12vp,行间距 12vp。

lg 断点是 2 行 4 列网格,充分利用平板的大屏空间展示更多内容。
网格布局适合展示功能入口、快捷操作、应用图标这些场景。注意网格和瀑布流的区别:网格元素高度一致、严格对齐;瀑布流元素高度不一、错落有致。
四、分栏布局:空间够了就分屏展示
分栏布局是把窗口划分成两栏或三栏,每栏展示不同内容。这种方式在大屏设备上特别有用,能充分利用空间。
分栏布局包含三种:侧边栏、单/双栏、三分栏。
1. 侧边栏
SideBarContainer 组件能实现侧边栏效果:
SideBarContainer(this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_SM ? SideBarContainerType.Overlay :
SideBarContainerType.Embed) {
Column() {
// 侧边栏内容
}
.backgroundColor('#F1F3F5')
Column() {
// 主内容区
}
.backgroundColor('#FDBFFC')
.padding({
top: this.getUIContext().px2vp(this.mainWindowInfo.AvoidSystem?.topRect.height) + 12,
bottom: this.getUIContext().px2vp(this.mainWindowInfo.AvoidNavigationIndicator?.bottomRect.height),
left: 16,
right: 16
})
}
.showSideBar(this.isShowingSidebar)
.sideBarWidth(new WidthBreakpointType('80%', '50%', '40%', '40%').getValue(this.mainWindowInfo.widthBp))
.controlButton({ top: this.getUIContext().px2vp(this.mainWindowInfo.AvoidSystem?.topRect.height) + 12 })
这段代码有几个关键点:
SideBarContainerType控制侧边栏类型,sm 断点用 Overlay(浮层),其他断点用 Embed(嵌入)showSideBar控制是否显示侧边栏sideBarWidth控制侧边栏宽度,sm 80%,md 50%,lg 40%

sm 断点默认不显示侧边栏,侧边栏浮在内容区上,宽度 80%。点击控制按钮才会显示。


md 断点默认显示侧边栏,侧边栏和内容区并列展示,宽度 50%。
<
lg 断点默认显示侧边栏,侧边栏和内容区并列展示,宽度 40%。平板上侧边栏占的比例更小,内容区更大。
2. 单/双栏
Navigation 组件能实现单/双栏效果:
Navigation(this.pathStack) {
// ...
}
.mode(this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_SM ? NavigationMode.Stack : NavigationMode.Split)
关键属性是 mode,sm 断点用 Stack(单栏),其他断点用 Split(双栏)。

sm 断点是单栏显示,导航栏和内容区单栏展示,点击导航栏条目跳转到内容区。


md 断点是双栏显示,导航栏和内容区分栏展示,导航栏宽度 50%。

lg 断点是双栏显示,导航栏和内容区分栏展示,导航栏宽度 50%。
单/双栏和侧边栏的区别是:单/双栏的导航栏能控制内容区路由跳转,比如商品列表和商品详情;侧边栏通常不控制内容区内容,比如图文详情和评论区。
3. 聊天场景的特殊处理
聊天场景有个特殊需求:双栏布局下,点击商品链接要全屏展示商品页,隐藏原聊天页。咋实现?
@Builder
PageMap(name: string) {
if (name === 'conversationDetail') {
ConversationDetail({
// ...
})
} else if (name === 'conversationDetailNone') {
ConversationDetailNone();
} else if (name === 'productPage') {
ProductPage({
// ...
})
}
}
build() {
Navigation(this.pathStack) {
ConversationNavBarView({
mainWindowInfo: this.mainWindowInfo,
pageInfos: this.pageInfos,
pathStack: this.pathStack,
})
}
.mode(this.getNavMode())
.navDestination(this.PageMap)
}
getNavMode(): NavigationMode {
if (!this.isNavFullScreen && this.mainWindowInfo.widthBp !== WidthBreakpoint.WIDTH_SM) {
return NavigationMode.Split;
}
return NavigationMode.Stack
}
关键逻辑在 getNavMode() 方法:默认双栏模式(Split),点击商品链接后,设置 isNavFullScreen = true,切换到单栏模式(Stack);返回时,设置 isNavFullScreen = false,恢复双栏模式。

sm 断点是单栏显示,导航栏和内容区单栏展示。



md 断点是双栏显示,导航栏和内容区分栏展示,内容扩展区单独展示,导航栏宽度 50%。


lg 和 xl 断点是双栏显示,导航栏和内容区分栏展示,内容扩展区单独展示,导航栏宽度 50%。

4. 三分栏
三分栏需要结合 SideBarContainer 和 Navigation:
SideBarContainer(new WidthBreakpointType(SideBarContainerType.Overlay, SideBarContainerType.Overlay,
SideBarContainerType.Embed, SideBarContainerType.Embed).getValue(this.mainWindowInfo.widthBp)) {
Column() {
// 第一栏:侧边栏
}
Column() {
Navigation(this.pathStack) {
NavigationBarView({
mainWindowInfo: this.mainWindowInfo,
pageInfos: this.pageInfos,
pathStack: this.pathStack,
isShowingSidebar: this.isShowingSidebar,
isTriView: true
})
}
.width('100%')
.height('100%')
.mode(this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_SM ? NavigationMode.Stack : NavigationMode.Split)
.navBarWidth(this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_MD ? '50%' : '40%')
.navDestination(this.PageMap)
.backgroundColor('#B8EEB2')
}
}
.showSideBar(this.isShowingSidebar)
.sideBarWidth(new WidthBreakpointType('80%', '50%', '20%', '20%').getValue(this.mainWindowInfo.widthBp))

sm 断点默认不显示侧边栏,侧边栏覆盖导航栏,宽度 80%,导航栏和内容区单栏显示。



md 断点默认不显示侧边栏,侧边栏覆盖导航栏,宽度 50%,导航栏和内容区分栏展示,导航栏宽度 50%。


lg 断点默认显示侧边栏,侧边栏嵌入导航栏,宽度 20%,导航栏和内容区分栏展示,导航栏宽度 30%。
三分栏适合邮箱、日历这些有三层结构的应用。
5. 邮箱场景
邮箱有三个层级:账户信息 -> 收件箱 -> 邮件详情。三分栏正好匹配:

上图展示了不同断点下的默认效果,lg 和 xl 断点默认显示侧边栏,其他断点默认不显示。
上图展示了不同断点下的内容效果,选中邮件后展示邮件详情。
实现代码关键是控制 showSideBar 属性,lg 和 xl 断点默认显示侧边栏:
build() {
GridRow() {
GridCol({ span: { sm: 12, md: 12, lg: 12, xl: 12 } }) {
SideBarContainer(new WidthBreakpointType(SideBarContainerType.Overlay, SideBarContainerType.Overlay,
SideBarContainerType.Embed, SideBarContainerType.Embed).getValue(this.mainWindowInfo.widthBp)) {
// 区域 A:账户信息
Column() {
MailSideBar()
}
.width('100%')
.height('100%')
.backgroundColor($r('sys.color.gray_01'))
// 区域 B+C:收件箱 + 邮件详情
Column() {
Stack() {
MailNavigation({
mainWindowInfo: this.mainWindowInfo,
pageInfos: this.pageInfos,
pathStack: this.pathStack,
})
.margin({ top: 18 })
.padding({ left: this.getUIContext().px2vp(this.mainWindowInfo.AvoidSystem?.topRect.left) })
}
}
.width('100%')
.height('100%')
}
.showSideBar(this.isShowingSidebar)
}
}
}
6. 日历场景的特殊处理
日历有个特殊情况:单栏布局下,应该优先展示日历(导航栏),而不是日程(内容区)。

上图展示了日历在不同断点下的布局效果,单栏优先展示日历,双栏展示日历和日程,三栏展示账户信息、日历和日程。
实现关键是利用 onNavigationModeChange 回调,单栏时清空路由栈,只显示导航栏(日历):
Navigation(this.pathStack) {
CalendarView({
mainWindowInfo: this.mainWindowInfo,
pathStack: this.pathStack,
})
}
.navDestination(this.pageMap)
.mode(this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_SM ? NavigationMode.Stack : this.navMode)
.onNavigationModeChange((mode: NavigationMode) => {
if (this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_SM || mode === NavigationMode.Stack) {
this.pathStack.clear(); // 单栏时清空路由,只显示导航栏
} else if (mode === NavigationMode.Split) {
this.pathStack.pushPath({ name: this.selectedItem.date, param: this.selectedItem }, false);
}
})
五、挪移布局:位置挪挪更舒适
挪移布局是把组件位置挪一挪,比如手机上上下布局,平板上左右布局。这种方式适合图文组合、导航栏这些场景。
挪移布局包含两种:插图和文字组合布局、底部/侧边导航。
1. 图文组合布局
比如音乐播放页,手机上是封面在上、歌曲列表在下;平板上是封面在左、歌曲列表在右。
GridRow 和 GridCol 组件能实现这种效果:
GridRow({
columns: { xs: 4, sm: 4, md: 8, lg: 12, xl: 12 },
gutter: 0,
breakpoints: { value: ['320vp', '600vp', '840vp', '1440vp']},
direction: GridRowDirection.Row
}) {
GridCol({
span: { xs: 4, sm: 4, md: 4, lg: 4, xl: 4 },
offset: 0
}) {
// 封面区
}
.height(this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_SM ? this.getGridColHeight() : '100%')
.padding({ top: this.getUIContext().px2vp(this.mainWindowInfo.AvoidSystem?.topRect.height) + 12})
.backgroundColor('#AAD3F1')
GridCol({
span: { xs: 4, sm: 4, md: 4, lg: 8, xl: 8 },
offset: 0
}) {
// 歌曲列表区
}
.backgroundColor(Color.Pink)
.layoutWeight(1)
.padding({ top: this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_SM ? 0 :
this.getUIContext().px2vp(this.mainWindowInfo.AvoidSystem?.topRect.height) })
}
关键属性是 span,控制组件占多少栅格:
- 手机:封面 4 格(占满),歌曲列表 4 格(占满)—— 上下布局
- 平板:封面 4 格,歌曲列表 8 格 —— 左右布局

sm 断点整个窗口划分为 4 栅格,歌单封面区(蓝)占 4 栅格,歌曲列表区(粉)占 4 栅格,上下布局。
md 断点整个窗口划分为 8 栅格,歌单封面区(蓝)占 4 栅格,歌曲列表区(粉)占 4 栅格,左右布局。
lg 断点整个窗口划分为 12 栅格,歌单封面区(蓝)占 4 栅格,歌曲列表区(粉)占 8 栅格,左右布局,歌曲列表占更大比例。
这种布局也适合页面顶部页签与搜索框的组合,页签在上、搜索框在下变成页签在左、搜索框在右。
上图展示了页签和搜索框在不同断点下的布局效果,从上下布局变成左右布局。
2. 底部/侧边导航
Tabs 组件能实现底部导航和侧边导航的切换:
Tabs({
barPosition: this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_LG ? BarPosition.Start : BarPosition.End
}) {
TabContent() {
TopTabView({
pageInfos: this.pageInfos,
mainWindowInfo: this.mainWindowInfo,
firstLevelIndex: this.firstLevelIndex,
tabData: this.tabData
})
}
.tabBar(this.tabBuilder(this.firstTabList[0], 0))
TabContent()
.tabBar(this.tabBuilder(this.firstTabList[1], 1))
TabContent()
.tabBar(this.tabBuilder(this.firstTabList[2], 2))
TabContent()
.tabBar(this.tabBuilder(this.firstTabList[3], 3))
}
.barBackgroundColor('#CCF1F3F5')
.barWidth(this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_LG ? 96 : '100%')
.barHeight(this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_LG ? '100%' : 56 + this.getUIContext().px2vp(this.mainWindowInfo.AvoidNavigationIndicator?.bottomRect.height))
.barMode(this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_LG ? BarMode.Scrollable : BarMode.Fixed,
{ nonScrollableLayoutStyle: LayoutStyle.ALWAYS_CENTER })
.barBackgroundBlurStyle(BlurStyle.COMPONENT_THICK)
.vertical(this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_LG)
.onChange((index: number) => {
this.firstLevelIndex = index;
})
关键属性:
barPosition:sm 和 md 用 End(底部),lg 用 Start(侧边)vertical:lg 断点设为 true,导航栏就变成垂直方向barWidth和barHeight:控制导航栏尺寸

sm 断点分级导航由底部一级导航栏和顶部二级页签组成。
md 断点分级导航由底部一级导航栏和顶部二级页签组成,和 sm 断点类似。
lg 断点分级导航由侧边一级导航栏和顶部二级页签组成,一级导航栏变成侧边。
xl 断点或 PC/2in1 设备,通过侧边栏显示一级和二级导航,导航栏在左侧。
xl 断点或 PC 设备上,可以用 SideBarContainer 实现一级和二级导航的侧边栏:
if ((this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_LG || this.mainWindowInfo.widthBp
=== WidthBreakpoint.WIDTH_XL)&& deviceInfo.deviceType == "2in1") {
SideBarContainer(SideBarContainerType.Embed) {
TabSideBarView({
firstLevelIndex: this.firstLevelIndex,
secondLevelIndex: this.secondLevelIndex,
tabData: this.tabData,
firstTabList: this.firstTabList
})
Column() {
Row() {
Text(this.tabViewModel.getTabNameOfSecondLevel(this.tabViewModel.getTabNameOfFirstLevel(this.firstLevelIndex),
this.secondLevelIndex))
.fontSize('20fp')
.fontWeight(700)
.margin({ left: 16 })
}
.padding({ top: 60, bottom: 14 })
VideoInfoView({
mainWindowInfo: this.mainWindowInfo,
firstLevelIndex: this.firstLevelIndex,
secondLevelIndex: this.secondLevelIndex
})
}
.alignItems(HorizontalAlign.Start)
}
.autoHide(false)
.divider({ strokeWidth: 0.3 })
.showControlButton(false)
.sideBarWidth(240)
.minSideBarWidth(240)
.maxSideBarWidth(240)
} else {
// ...
}
六、缩进布局:居中展示留白更优雅
缩进布局是把内容居中展示,两侧留白。这种方式适合单列列表、表单这些场景。
GridRow 和 GridCol 能实现缩进布局,关键是用 offset 属性控制偏移:
GridRow({
columns: { xs: 4, sm: 4, md: 8, lg: 12, xl: 12 },
gutter: 0,
breakpoints: { value: ['320vp', '600vp', '840vp', '1440vp']},
direction: GridRowDirection.Row
}) {
GridCol({
span: { xs: 4, sm: 4, md: 6, lg: 8, xl: 8 },
offset: { xs: 0, sm: 0, md: 1, lg: 2, xl: 2 }
}) {
// 内容
}
.width('100%')
.height('100%')
}
关键参数:
span:内容占多少栅格offset:内容偏移多少栅格
效果:
- 手机:占 4 格,偏移 0 —— 占满屏幕
- 平板:占 6 格,偏移 1 —— 两侧各留 1 格
- PC:占 8 格,偏移 2 —— 两侧各留 2 格

sm 断点整个窗口划分为 4 栅格,单列列表占 4 列,偏移 0 列,占满屏幕。
md 断点整个窗口划分为 8 栅格,单列列表占 6 列,两侧各偏移 1 列,居中展示。
lg 断点整个窗口划分为 12 栅格,单列列表占 8 列,两侧各偏移 2 列,居中展示,留白更多。
缩进布局适合登录表单、设置页面、详情页这些场景,居中展示让内容更聚焦,两侧留白让视觉更舒适。
七、关键组件和属性总结
整理一下几种布局用到的核心组件和属性:
| 布局方式 | 核心组件 | 关键属性 |
|---|---|---|
| 列表布局 | List | lanes、space |
| 瀑布流布局 | WaterFlow | columnsTemplate、columnsGap、rowsGap |
| 轮播布局 | Swiper | displayCount、prevMargin、nextMargin、indicator |
| 网格布局 | Grid | columnsTemplate、rowsTemplate、columnsGap、rowsGap |
| 侧边栏 | SideBarContainer | showSideBar、sideBarWidth、SideBarContainerType |
| 单/双栏 | Navigation | mode、navBarWidth、onNavigationModeChange |
| 三分栏 | SideBarContainer + Navigation | 组合使用 |
| 图文组合 | GridRow + GridCol | span、offset、columns |
| 导航切换 | Tabs | barPosition、vertical、barWidth、barHeight |
| 缩进布局 | GridRow + GridCol | span、offset |
WidthBreakpointType 类是断点适配的关键工具,它能根据当前断点返回不同的值。使用方式:
new WidthBreakpointType(sm值, md值, lg值, xl值).getValue(this.mainWindowInfo.widthBp)
八、踩坑记录
坑 1:断点值获取时机
我一开始在组件构造时获取断点值,结果发现断点值不对。后来才知道,断点值要在窗口尺寸变化时更新。
正确做法是订阅窗口尺寸变化事件,在回调里更新断点值:
window.on('windowSizeChange', (size) => {
this.mainWindowInfo.widthBp = this.getWidthBreakpoint(size.width);
});
坑 2:侧边栏宽度百分比计算
SideBarContainer 的 sideBarWidth 属性支持百分比,但百分比是相对于父容器宽度计算的。如果父容器没设置宽度,百分比就会出问题。
建议给 SideBarContainer 的父容器设置明确宽度,或者用 width('100%')。
坑 3:Navigation mode 切换时机
Navigation 的 mode 属性切换时,会触发 onNavigationModeChange 回调。但回调触发时机有个坑:首次渲染时会触发一次。
如果你在回调里做了路由操作,要注意首次渲染时别误操作。可以加个判断:
.onNavigationModeChange((mode: NavigationMode) => {
if (this.isFirstRender) {
this.isFirstRender = false;
return; // 首次渲染不处理
}
// 正常处理
})
坑 4:GridCol 的 layoutWeight
GridCol 组件默认会用 layoutWeight 来撑满剩余空间,但有时候会和 span 属性冲突。
如果你想用 span 精确控制栅格数,就不要用 layoutWeight。反之,如果用 layoutWeight,就别设 span。
坑 5:Tabs 的 barHeight 在手机上要加导航指示器高度
手机上有导航指示器,Tabs 的 barHeight 要加上导航指示器高度,不然导航栏会被遮挡:
.barHeight(this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_LG ? '100%' : 56 + this.getUIContext().px2vp(this.mainWindowInfo.AvoidNavigationIndicator?.bottomRect.height))
坑 6:瀑布流和网格的选择
瀑布流和网格看起来差不多,但适用场景不一样:
- 瀑布流:子组件高度不一,适合商品卡片、新闻卡片这些高度不一致的内容
- 网格:子组件高度一致,适合功能入口、应用图标这些高度一致的内容
选错了组件,布局效果会很别扭。
九、总结
多设备页面布局的核心是响应式,响应式的核心是断点和栅格。
四种布局方式覆盖了大部分场景:
- 重复布局:空间够了就多展示,适合列表、瀑布流、轮播、网格
- 分栏布局:空间够了就分屏,适合侧边栏、导航、邮箱、日历
- 挪移布局:位置挪挪更舒适,适合图文组合、导航切换
- 缩进布局:居中展示留白更优雅,适合表单、详情页
掌握了这四种方式,加上断点机制和栅格系统,多设备适配就不是啥难事了。
关键是搞清楚每种场景适合哪种布局方式,然后选对应的组件,设置对应的属性。别硬套公式,得根据实际场景灵活调整。比如聊天场景要全屏展示商品页,日历场景要优先展示日历,这些特殊需求要用特殊方法处理。
更多推荐




所有评论(0)