你真以为 Row/Column 只是“横竖排队”?那为什么一到多设备适配你就开始怀疑人生?
本文分享了鸿蒙开发中布局设计的核心要点与实战经验。首先解析了ArkUI布局原理,将父容器比作"房东"、子组件比作"租客"的测量与摆放机制,对比了Row/Column和Flex布局的适用场景。其次提出了自适应布局策略,强调通过媒体查询实现断点切换的响应式设计,并给出手机/平板双列布局的代码示例。接着针对多设备适配,建议以窗口尺寸变化为核心管理策略,展示了标签流
👋 你好,欢迎来到我的博客!我是【菜鸟学鸿蒙】
我是一名在路上的移动端开发者,正从传统“小码农”转向鸿蒙原生开发的进阶之旅。为了把学习过的知识沉淀下来,也为了和更多同路人互相启发,我决定把探索 HarmonyOS 的过程都记录在这里。
🛠️ 主要方向:ArkTS 语言基础、HarmonyOS 原生应用(Stage 模型、UIAbility/ServiceAbility)、分布式能力与软总线、元服务/卡片、应用签名与上架、性能与内存优化、项目实战,以及 Android → 鸿蒙的迁移踩坑与复盘。
🧭 内容节奏:从基础到实战——小示例拆解框架认知、专项优化手记、实战项目拆包、面试题思考与复盘,让每篇都有可落地的代码与方法论。
💡 我相信:写作是把知识内化的过程,分享是让生态更繁荣的方式。
如果你也想拥抱鸿蒙、热爱成长,欢迎关注我,一起交流进步!🚀
1)Flex / Column / Row 原理:别只会“摆组件”,要懂它怎么“算尺寸”🧠
ArkUI 的布局,本质上逃不掉两个动作:
- 测量(measure):父容器把“约束”给子组件(你最多多大?最少多大?)
- 摆放(layout):父容器拿到子组件的“期望尺寸”,再决定你放哪、留多少间距、谁先谁后
你可以把它理解成:
- 父容器:房东(决定房间最大/最小、怎么分配)
- 子组件:租客(在约束内报个“我想要的大小”)
- 最终:房东拍板(你想要不代表你能要到🤣)
1.1 Row / Column:线性布局的“规则感”很强
-
Row:主轴横向(从左到右排)
-
Column:主轴纵向(从上到下排)
-
常见调控点:
- 主轴:对齐/间距(比如均分、两端对齐)
- 交叉轴:居中、拉伸等
- 子项分配:
layoutWeight()(很关键!)
你会发现 Row/Column 的体验像“排队”:
顺序明确、规则明确,只要别乱加固定宽高,整体非常稳。
1.2 Flex:更像“会变形的队列”,自由但也更容易翻车🙃
Flex 比 Row/Column 多了几件“更社会”的事:
- wrap:一行放不下就换行
- grow/shrink:空间富余怎么分?不够怎么挤?
- basis:你“默认想占多少”
一句话总结:
Row/Column 更适合“结构稳定”的页面骨架;
Flex 更适合“标签流、按钮组、卡片流”这种会变化的内容。
2)自适应布局策略:别上来就写 375 适配,先把“策略层”想清楚😤
我自己做项目时,基本遵循这套顺序(很土,但真的稳):
2.1 第 1 层:单位与约束先选对
- 优先用 vp(视口相关单位)、百分比、
layoutWeight - 少用“拍脑袋固定 px”(除非你明确就是固定尺寸组件,比如图标按钮)
- 文本区域要给弹性空间:别把 Text 卡死,长文案一来就爆
2.2 第 2 层:用“可切换布局”应对尺寸断点(手机/平板/分屏)
最舒服的自适应不是“每个组件都算一遍”,而是:
到某个宽度,直接换布局结构。
比如:
- 窄屏:Column(纵向单列)
- 宽屏:Row(左右双列 / 主次分栏)
下面给你一个“能直接用”的示例:用 media query 监听窗口宽度,动态切换布局。
示例:窄屏单列 / 宽屏双列(ArkTS + ArkUI)
import mediaquery from '@ohos.mediaquery'
@Entry
@Component
struct ResponsivePage {
@State private isWide: boolean = false
private listener?: mediaquery.MediaQueryListener
aboutToAppear() {
// 你可以按项目定义断点:600vp、840vp等
this.listener = mediaquery.matchMediaSync('(min-width: 600vp)')
this.isWide = this.listener.matches
this.listener.on('change', (e) => {
this.isWide = e.matches
})
}
aboutToDisappear() {
this.listener?.off('change')
}
build() {
Column({ space: 12 }) {
Text('ArkUI 自适应布局示例')
.fontSize(20)
.fontWeight(FontWeight.Bold)
if (this.isWide) {
// 宽屏:左右分栏
Row({ space: 12 }) {
this.LeftPanel()
.layoutWeight(1)
this.RightPanel()
.layoutWeight(2)
}
.height('80%')
} else {
// 窄屏:上下结构
Column({ space: 12 }) {
this.LeftPanel()
this.RightPanel()
}
}
}
.padding(16)
}
@Builder
LeftPanel() {
Column({ space: 8 }) {
Text('左侧:过滤/导航').fontSize(16).fontWeight(FontWeight.Medium)
Button('按钮 A')
Button('按钮 B')
}
.padding(12)
.borderRadius(12)
.backgroundColor(0xF5F5F5)
}
@Builder
RightPanel() {
Column({ space: 8 }) {
Text('右侧:内容列表').fontSize(16).fontWeight(FontWeight.Medium)
List() {
ForEach([1,2,3,4,5,6,7,8], (i: number) => {
ListItem() {
Text(`Item ${i}`).padding(12)
}
}, (i: number) => String(i))
}
}
.padding(12)
.borderRadius(12)
.backgroundColor(0xFFFFFF)
}
}
这类“结构切换式响应式”有个巨大的好处:
你不是在对抗每一像素,而是在管理布局形态。(省头发!)
3)多设备尺寸适配:手机、平板、折叠屏、分屏…你得把它当“窗口”而不是“设备”🪟
3.1 核心观念:适配的是“窗口尺寸变化”
同一台设备上:
- 竖屏/横屏
- 分屏/浮窗
- 折叠展开/合上
都会导致可用宽高变化。
所以建议:
- 把“宽度断点”作为策略核心(上面 isWide 就是)
- 关键容器用
layoutWeight或百分比,让它自动伸缩 - 列表/滚动区域要明确可用高度(后面会讲坑)
3.2 图片与卡片:给它“可保持比例”的策略
常见做法:
- 图片用固定比例(比如 16:9)+ 自适应宽度
- 卡片内边距固定(vp)+ 内容区域弹性(weight)
示例:Flex 做标签流(wrap),宽了多排,窄了自动换行
@Entry
@Component
struct ChipFlow {
private tags: string[] = ['鸿蒙', 'ArkUI', 'Stage', '性能优化', '分布式', 'IPC', '多端适配', '安全模型', '数据库', '网络']
build() {
Column({ space: 12 }) {
Text('Flex 标签流:自动换行')
.fontSize(18)
.fontWeight(FontWeight.Bold)
Flex({
direction: FlexDirection.Row,
wrap: FlexWrap.Wrap,
justifyContent: FlexAlign.Start,
alignItems: ItemAlign.Center
}) {
ForEach(this.tags, (t: string) => {
Text(t)
.padding({ left: 10, right: 10, top: 6, bottom: 6 })
.margin({ right: 8, bottom: 8 })
.borderRadius(999)
.backgroundColor(0xF2F3F5)
}, (t: string) => t)
}
}
.padding(16)
}
}
4)常见布局陷阱:这些坑我都踩过,你别再踩一遍了😭
坑 1:List/Scroll 放在 Column 里不设高度 → 直接“挤没了”
表现:页面空白、列表不显示、或者只显示一丢丢。
原因:Column 会让子组件按内容测量,如果滚动容器拿不到合理高度,就会出怪事。
✅ 修法:给它高度或 layoutWeight(1)。
Column() {
Text('标题').fontSize(18)
// ❌ 常见错误:List 不知道自己该多高
// List() { ... }
// ✅ 正确:给列表一个可伸缩的剩余空间
List() {
ForEach([1,2,3,4,5], (i: number) => {
ListItem() { Text(`Row ${i}`).padding(12) }
}, (i: number) => String(i))
}
.layoutWeight(1)
}
.height('100%')
.padding(16)
坑 2:一堆固定宽高叠加 → 多设备直接爆炸
你在 6.1 寸上刚刚好,到了平板上就一坨留白;再到小屏上直接溢出。
✅ 修法:优先用 weight / % / 自适应断点结构切换。
坑 3:Row 里文字过长,把按钮挤没了(特别常见!)
✅ 修法:给 Text 一个可伸缩区域,让按钮保住。
Row({ space: 8 }) {
Text('这是一段超级长的标题超级长超级长……')
.layoutWeight(1) // 让它吃剩余空间
Button('操作')
}
.padding(12)
坑 4:Flex 用了 shrink/grow 但没想清楚“谁该让步”
表现:某些机型下按钮被压扁、文字被裁切、布局“像被踩了一脚”。
✅ 修法:给关键元素明确最小尺寸;把可压缩的交给文本/次要区域。
坑 5:嵌套滚动(Scroll 里放 List / List 里放 Scroll)
表现:滚动手势冲突、回弹诡异、性能不稳定。
✅ 修法:尽量保持“一个方向一个滚动容器”,复杂场景用更合适的容器组合(比如 List 分组、懒加载等),别硬套。
结尾:布局写到最后,拼的不是组件,是“约束思维”😮💨
ArkUI 的布局系统其实很讲道理:你给清楚约束,它就给你稳定结果;你全靠固定值硬怼,它就用多设备狠狠怼回来。
所以我常反问自己一句:**“我是在描述界面,还是在赌运气?”**😅
📝 写在最后
如果你觉得这篇文章对你有帮助,或者有任何想法、建议,欢迎在评论区留言交流!你的每一个点赞 👍、收藏 ⭐、关注 ❤️,都是我持续更新的最大动力!
我是一个在代码世界里不断摸索的小码农,愿我们都能在成长的路上越走越远,越学越强!
感谢你的阅读,我们下篇文章再见~👋
✍️ 作者:某个被流“治愈”过的 移动端 老兵
📅 日期:2025-11-05
🧵 本文原创,转载请注明出处。
更多推荐



所有评论(0)