一、响应式组件到底是啥玩意儿

前面那篇写了页面布局场景,说的是四种布局方式咋用。这篇说啥呢?说的是这些布局方式背后用到的组件,每个组件都有哪些属性可以配合断点做适配。

HarmonyOS 的 ArkUI 框架搞了一套响应式组件体系。啥意思?就是组件的属性能根据断点动态变化。比如 List 组件的 lanes 属性,sm 断点是 1 列,md 断点是 2 列,lg 断点是 3 列。你不用写三套代码,一套代码就能搞定所有设备。

这套体系的核心是断点机制和属性绑定。断点把屏幕宽度分成几个区间,组件属性根据当前断点值自动切换。窗口尺寸变化时,组件重新计算布局和样式,实现从手机竖屏到横屏、折叠屏折叠状态切换、窗口分屏等场景的实时适配。

二、响应式组件和布局场景对应关系

先看个表,搞清楚每个组件能干啥:

响应式组件 布局场景 响应式布局方式
List 重复列表 重复布局
WaterFlow 重复瀑布流 重复布局
Swiper 重复轮播视图 重复布局
Grid 重复网格 重复布局
SideBarContainer 侧边栏 分栏布局
Navigation 单/双栏 分栏布局
Navigation + SideBarContainer 三分栏 分栏布局
Tabs 底部/侧边导航 挪移布局
GridRow/GridCol 插图和文字组合 挪移布局
GridRow/GridCol 单列列表 缩进布局

这个表咋用?你先确定要实现啥布局场景,然后找对应的组件。比如要实现重复列表,就用 List;要实现侧边栏,就用 SideBarContainer。


三、List 组件属性详解

List 是最常用的组件之一,能实现重复列表布局。

属性 说明
lanes 设置不同断点对应 List 组件布局的列数或行数,及列间距

lanes 属性咋用?结合 WidthBreakpointType 类:

List({ space: 8 }) {
  // ...
}
.lanes(new WidthBreakpointType(1, 2, 3, 3).getValue(this.mainWindowInfo.widthBp), 12)

上面这行代码,sm 断点 1 列,md 断点 2 列,lg 断点 3 列,列间距 12vp。



上图展示了不同断点下的列表布局效果,sm 单列,md 双列,lg 三列。


四、WaterFlow 组件属性详解

WaterFlow 是瀑布流组件,能实现重复瀑布流布局。

属性 说明
WaterFlowLayoutMode 优先考虑视窗内布局信息或涉及动态切换列数时,建议使用 SLIDING_WINDOW 模式
columnsTemplate 设置不同断点下瀑布流布局的列数
columnsGap 设置不同断点下列间距
itemConstraintSize 设置不同断点下 WaterFlow 所有子组件的约束尺寸

columnsTemplate 属性用 repeat() 函数动态控制列数:

WaterFlow() {
  // ...
}
.columnsTemplate(`repeat(${new WidthBreakpointType(2, 3, 4, 4).getValue(this.mainWindowInfo.widthBp)}, 1fr)`)
.columnsGap(12)

sm 断点 2 列,md 断点 3 列,lg 断点 4 列。



上图展示了不同断点下的瀑布流布局效果,sm 双列,md 三列,lg 四列。

itemConstraintSize 属性能控制子组件的约束尺寸,比如最小宽度、最大宽度。这在瀑布流里很有用,能保证每个卡片不会太小或太大。


五、Swiper 组件属性详解

Swiper 是轮播组件,能实现重复轮播视图。

属性 说明
index 设置不同断点下默认展示的子组件索引值
indicator 设置不同断点下是否显示导航点指示器和不同样式
displayCount 设置不同断点下 Swiper 视窗内元素显示的个数
nextMargin 设置不同断点下的后边距,用于露出后一项的一小部分
prevMargin 设置不同断点下的前边距,用于露出前一项的一小部分
onChange() 根据不同断点,设置索引值

这些属性组合起来能实现很灵活的轮播效果:

Swiper() {
  // ...
}
.displayCount(new WidthBreakpointType(1, 2, 3, 3).getValue(this.mainWindowInfo.widthBp))
.indicator(this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_SM ? Indicator.dot() : false)
.prevMargin(new WidthBreakpointType(0, 12, 64, 64).getValue(this.mainWindowInfo.widthBp))
.nextMargin(new WidthBreakpointType(0, 12, 64, 64).getValue(this.mainWindowInfo.widthBp))

这段代码啥意思?sm 断点展示 1 个元素,显示圆点指示器,无前后边距;md 和 lg 断点展示多个元素,不显示指示器,有前后边距。

onChange() 回调有个坑:lg 断点下展示 3 张图片时,当前显示索引为 index 的图片组(包含 index, index+1, index+2),返回最右侧索引应为 index+2。这个细节要注意,不然索引值会错乱。


六、Grid 组件属性详解

Grid 是网格组件,能实现重复网格布局。

属性 说明
columnsTemplate 设置不同断点下网格布局列的数量
rowsTemplate 设置不同断点下网格布局行的数量
columnsGap 设置不同断点下的列间距
rowsGap 设置不同断点下的行间距

Grid 和 WaterFlow 类似,但 Grid 的子组件高度一致,严格对齐:

Grid() {
  // ...
}
.columnsTemplate(`repeat(${new WidthBreakpointType(2, 3, 4, 4).getValue(this.mainWindowInfo.widthBp)}, 1fr)`)
.columnsGap(12)
.rowsGap(12)



上图展示了不同断点下的网格布局效果,sm 2 列,md 3 列,lg 4 列。

rowsTemplate 属性能控制行数,不设置的话行数 = 展示元素数量 / 列数。你想精确控制行数,就设置 rowsTemplate。


七、SideBarContainer 组件属性详解

SideBarContainer 是侧边栏组件,能实现分栏布局。

属性 说明
showSideBar 设置不同断点下侧边栏是否默认显示
SideBarContainerType 设置不同断点下容器内侧边栏样式
sideBarWidth 设置不同断点下侧边栏的宽度
minSideBarWidth 设置不同断点下侧边栏最小宽度
maxSideBarWidth 设置不同断点下侧边栏最大宽度

SideBarContainerType 有两种:

  • Overlay:侧边栏浮在内容区上(适合手机)
  • Embed:侧边栏嵌入,和内容区并列(适合平板)
SideBarContainer(this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_SM ? SideBarContainerType.Overlay :
  SideBarContainerType.Embed) {
  // ...
}
.showSideBar(this.isShowingSidebar)
.sideBarWidth(new WidthBreakpointType('80%', '50%', '40%', '40%').getValue(this.mainWindowInfo.widthBp))


上图展示了不同断点下的侧边栏布局效果,sm 浮层,md 和 lg 嵌入。

minSideBarWidth 和 maxSideBarWidth 能限制侧边栏宽度的范围,防止用户拖动时太窄或太宽。


八、Navigation 组件属性详解

Navigation 是导航组件,能实现单/双栏布局。

属性 说明
mode 设置不同断点下导航栏的显示模式:单栏或双栏
navBarWidth 设置不同断点下导航栏的宽度

mode 有两种:

  • Stack:单栏模式(适合手机)
  • Split:双栏模式(适合平板)
Navigation(this.pathStack) {
  // ...
}
.mode(this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_SM ? NavigationMode.Stack : NavigationMode.Split)
.navBarWidth('50%')


上图展示了不同断点下的单/双栏布局效果,sm 单栏,md 和 lg 双栏。


九、SideBarContainer + Navigation 组合属性详解

三分栏需要组合使用 SideBarContainer 和 Navigation。

SideBarContainer 属性

属性 说明
showSideBar 设置不同断点下侧边栏是否默认显示
sideBarWidth 设置不同断点下侧边栏的宽度
minSideBarWidth 设置不同断点下侧边栏最小宽度
maxSideBarWidth 设置不同断点下侧边栏最大宽度

Navigation 属性

属性 说明
mode 设置不同断点下导航栏的显示模式:单栏或双栏
navBarWidth 设置不同断点下导航栏的宽度

组合使用时,SideBarContainer 作为第一栏,Navigation 作为第二和第三栏:

SideBarContainer(SideBarContainerType.Embed) {
  Column() {
    // 第一栏
  }
  
  Column() {
    Navigation(this.pathStack) {
      // 第二栏和第三栏
    }
    .mode(NavigationMode.Split)
    .navBarWidth('30%')
  }
}
.showSideBar(this.isShowingSidebar)
.sideBarWidth('20%')

上图展示了不同断点下的三分栏布局效果,lg 断点三栏并列。


十、Tabs 组件属性详解

Tabs 是标签页组件,能实现底部/侧边导航。

属性 说明
vertical 设置不同断点下 Tabs 为横向或纵向
barPosition 设置不同断点下 Tabs 的页签位置
barMode 设置不同断点下 TabBar 的布局模式,以及 Scrollable 模式下 TabBar 的布局样式
barWidth 设置不同断点下 TabBar 的宽度
barHeight 设置不同断点下 TabBar 的高度

这些属性组合起来能实现底部导航和侧边导航的切换:

Tabs({ barPosition: this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_LG ? BarPosition.Start : BarPosition.End }) {
  // ...
}
.vertical(this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_LG)
.barWidth(this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_LG ? 96 : '100%')
.barHeight(this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_LG ? '100%' : 56)

sm 和 md 断点,barPosition 是 End(底部),vertical 是 false(横向),底部导航;lg 断点,barPosition 是 Start(侧边),vertical 是 true(纵向),侧边导航。


上图展示了不同断点下的底部/侧边导航布局效果,sm 和 md 底部导航,lg 侧边导航。


十一、GridRow/GridCol 组件属性详解

GridRow 和 GridCol 是栅格组件,能实现挪移布局和缩进布局。

GridRow 属性

属性 说明
columns 设置不同断点下栅格容器划分的布局列数
breakpoints 设置断点的划分阈值与横向断点一致

GridCol 属性

属性 说明
span 设置不同断点下栅格子组件的占用列数
offset 设置不同断点下栅格子组件的偏移列数
order 设置不同断点下栅格子组件的序号

GridRow 把屏幕划分成若干栅格,GridCol 占用若干栅格。通过 span 和 offset 属性,能实现灵活的布局:

GridRow({
  columns: { xs: 4, sm: 4, md: 8, lg: 12, xl: 12 },
  breakpoints: { value: ['320vp', '600vp', '840vp', '1440vp'] }
}) {
  GridCol({
    span: { xs: 4, sm: 4, md: 4, lg: 4, xl: 4 },
    offset: { xs: 0, sm: 0, md: 0, lg: 2, xl: 2 }
  }) {
    // 内容
  }
}

这段代码啥意思?GridRow 划分栅格:sm 4 格,md 8 格,lg 12 格。GridCol 占用 4 格,lg 断点偏移 2 格,居中展示。


上图展示了不同断点下的挪移布局效果,sm 上下布局,md 和 lg 左右布局。


上图展示了不同断点下的缩进布局效果,sm 占满屏幕,md 和 lg 居中展示留白。

order 属性能控制子组件的显示顺序,比如手机上图片在上、文字在下;平板上文字在上、图片在下。这个属性很少用,但在特殊场景下很有价值。


十二、属性使用技巧总结

用这些响应式属性时,有几个技巧要注意:

1. 属性值绑定方式

有两种方式绑定断点值:

方式一:用 WidthBreakpointType 类

.lanes(new WidthBreakpointType(1, 2, 3, 3).getValue(this.mainWindowInfo.widthBp))

方式二:直接判断断点

.vertical(this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_LG)

方式一适合多个断点有不同值的情况,方式二适合只有两种情况(比如 sm 和其他)。

2. 属性组合使用

很多场景需要多个属性组合:

  • List:lanes + space
  • Swiper:displayCount + prevMargin + nextMargin + indicator
  • Tabs:barPosition + vertical + barWidth + barHeight

别只设置一个属性,要组合设置才能达到预期效果。

3. 动态切换的注意事项

动态切换断点时,有些属性会触发组件重新渲染,比如 lanes、columnsTemplate。这可能导致滚动位置丢失或动画闪烁。

解决办法:

  • 用 LazyForEach 而不是 ForEach,减少重新渲染
  • 在 onChange 回调里保存状态,切换后恢复

4. 断点边界值

断点的边界值要注意:

  • sm < 600vp
  • md 600vp - 840vp
  • lg 840vp - 1440vp
  • xl > 1440vp

如果某个屏幕宽度正好是 600vp,它会属于 md 还是 sm?要看具体实现。建议在设计时考虑边界情况,避免布局突变。


十三、踩坑记录

坑 1:WaterFlow 的 WaterFlowLayoutMode

瀑布流动态切换列数时,如果不设置 WaterFlowLayoutMode,可能出现布局错乱。建议用 SLIDING_WINDOW 模式:

WaterFlow() {
  // ...
}
.waterFlowLayoutMode(WaterFlowLayoutMode.SLIDING_WINDOW)

坑 2:Swiper 的 onChange 索引计算

lg 断点下展示 3 张图片时,onChange 回调返回的索引值要注意:

.onChange((index: number) => {
  if (this.mainWindowInfo.widthBp === WidthBreakpoint.WIDTH_LG) {
    // 当前显示的是 index, index+1, index+2
    // 返回的最右侧索引是 index+2,不是 index
  }
})

坑 3:SideBarContainer 的宽度百分比

sideBarWidth 用百分比时,百分比是相对于父容器宽度。如果父容器没设置宽度,百分比会出问题。

建议:

SideBarContainer() {
  // ...
}
.width('100%') // 先设置父容器宽度
.sideBarWidth('40%')

坑 4:Tabs 的 barHeight 要加导航指示器高度

手机上有导航指示器,Tabs 的 barHeight 要加上导航指示器高度:

.barHeight(56 + this.getUIContext().px2vp(this.mainWindowInfo.AvoidNavigationIndicator?.bottomRect.height))

不加的话,导航栏会被遮挡。

坑 5:GridCol 的 order 属性很少用但很强大

order 属性能改变子组件显示顺序,比如手机上图片在上、文字在下;平板上文字在上、图片在下。这个功能很少用,但在某些场景下能实现很酷的效果。


十四、总结

这篇把响应式组件的属性都列了一遍,每个组件有哪些属性能配合断点做适配,一目了然。

核心要点:

  • 重复布局:用 List、WaterFlow、Swiper、Grid,核心属性是 lanes/columnsTemplate/displayCount
  • 分栏布局:用 SideBarContainer、Navigation,核心属性是 showSideBar/mode/sideBarWidth
  • 挪移布局:用 Tabs、GridRow/GridCol,核心属性是 vertical/span/offset
  • 缩进布局:用 GridRow/GridCol,核心属性是 span/offset

用这些属性时,要注意组合使用,别只设置一个属性。还要注意动态切换时的状态保存,避免布局错乱。

掌握了这些属性,多设备适配就不是啥难事了。关键是搞清楚每个属性的作用,然后根据场景灵活组合。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐