FEATURED · 精选文章

鸿蒙ArkUI布局完全指南:从Row/Column到Grid的实战与避坑

发布时间 / 2026/9/11 5:40:27
来源 / 创域科博编辑部
栏目 / 资讯中心
鸿蒙ArkUI布局完全指南:从Row/Column到Grid的实战与避坑 做鸿蒙应用开发有一阵子了每次聊到“构建布局”很多刚入门的朋友第一反应是这不就是把组件往页面上一摆吗可真到了自己从零搭一个页面的时候光是Row、Column、Stack、Grid这些容器组件怎么选、属性怎么配、嵌套怎么组织就够折腾一下午。尤其是基础篇Day09这节《构建布局详解下》我把它当成自己项目里最常用布局方案的完整复盘来写顺便把实际开发中踩过的坑也一并整理出来。如果你正在学HarmonyOS应用开发或者已经能写简单页面但总觉得布局代码“不够顺”这篇内容可以帮你把布局这块地基打牢。布局直接影响的不只是“好不好看”还决定了页面在不同尺寸设备上稳不稳、滚动顺不顺、状态更新时会不会闪。网络上关于鸿蒙应用布局的教程不少但多数只给个代码片段不太讲为什么这么写。这篇文章会把容器选型、核心属性、完整案例、常见坑位都过一遍你完全可以照着敲一遍再改造成自己的页面。1. 布局不是摆控件是在定信息架构我见过不少新手写页面看到设计稿第一件事就是往上堆组件堆完发现大屏手机没问题换个小屏就溢出或者横屏直接乱掉。根子在于没有先把布局当成“信息结构”来设计而是当成了“摆放位置”。1.1 先想清楚页面的信息层级再动手写布局一个页面通常由几个功能块组成顶部标题区、核心内容区、底部操作区或者卡片列表、表单、悬浮入口。布局的本质就是把屏幕这个二维空间切成若干区域再按信息重要程度决定每个区域的尺寸和位置。举个例子一个商品详情页用户最关心的是商品图、价格、加购按钮那么图片区就应当占据视觉中心和较大空间而用户不常看的商品参数就放到折叠区域或靠后的位置。用HarmonyOS ArkUI来写的时候这意味着你要先决定哪些内容需要在同一行内展示水平方向用Row垂直方向用Column哪些内容需要叠在一起比如图片上的播放按钮、价格标签用Stack哪些内容需要在有限空间内自动换行或压缩用Flex哪些内容是规整的宫格、网格用Grid或GridRow/GridCol。这个决策过程比写代码本身更重要。判断错了容器后面改起来成本很高因为布局代码往往是嵌套的牵一发而动全身。我习惯先在草稿纸上画“区域树”把页面拆成若干个矩形块再标出每个矩形块之间的包含关系最后才在ArkTS里写代码。1.2 五个常用布局组件怎么选ArkUI提供的布局组件很多但日常开发真正高频使用的就那几个。它们不是互相替代的关系而是各管一段。布局组件布局方式典型场景注意事项Row水平方向线性排列顶部导航、按钮组、标签行子组件过多时需要处理溢出或换行Column垂直方向线性排列页面主体、表单、列表单项常用于外层容器整体纵向排版Stack层叠排列子组件重叠图片上叠加文字、悬浮按钮、蒙层注意子组件的对齐和层级顺序Flex弹性布局支持换行标签云、筛选条件、工具栏属性与CSS Flexbox相似但不完全相同Grid网格布局宫格入口、照片墙、商品列表大量数据时配合LazyForEach或懒加载选型时有一个很实用的判断标准如果页面内容是从上到下一行一行读的优先Column如果某一个区块内需要横向排多个元素优先Row如果希望子组件重叠定位优先Stack如果横向元素数量不固定且可能换行优先Flex如果元素排列有固定的行列关系优先Grid。很多新人喜欢所有地方都用Stack来绝对定位觉得这样省事。但绝对定位最大的问题是适配性差尤其是不同屏幕宽度下偏移量很难精确控制。能用线性布局完成的尽量不要用绝对定位。2. 核心属性与参数实操选好布局组件之后下一步就是调属性。HarmonyOS ArkUI的布局属性看着不多但组合起来很灵活而且有些属性名字和前端CSS很像含义却不一样特别容易搞混。2.1 Row和Column的间距、对齐和权重线性布局里最核心的三个能力是间距、对齐、权重。间距通过space属性控制。它表示子组件之间的间隔单位是vp虚拟像素不是px。在代码里写space: 12指的是子组件之间保留12vp的空白。实际效果比px更稳定因为在不同密度屏幕上vp会自动换算避免在小屏上显得拥挤。Column({ space: 12 }) { Text(第一行) Text(第二行) Text(第三行) } .width(100%)这段代码会让三个文本在垂直方向上依次排列每两个之间间隔12vp。如果间距是均匀的用space最省事如果某些地方需要更大间距可以在具体组件上用margin去微调。对齐属性有两个维度。横向对齐用alignItems竖向对齐用justifyContent。Row里alignItems控制子组件在交叉轴垂直方向的对齐方式可选值有Top、Center、BottomjustifyContent控制主轴水平方向的排列方式可选值有Start、Center、End、SpaceBetween、SpaceAround、SpaceEvenly。Column则相反。权重是线性布局里最常用也最容易出错的一个属性。在ArkUI里一般用layoutWeight来实现。它的作用是让子组件按比例瓜分父容器的剩余空间。Row() { Text(左侧) .layoutWeight(1) .backgroundColor(#ff6699) Text(右侧) .layoutWeight(2) .backgroundColor(#6699ff) } .width(100%) .height(80)这个例子中左右两个文本会按1:2分割父容器宽度。注意layoutWeight和固定宽度是冲突的如果一个子组件已经设置了.width(100)又同时设置layoutWeight在部分场景下权重会覆盖固定宽度导致实际表现和你预期不一致。我一般会在使用权重的组件上直接不写固定尺寸避免纠缠。还有个容易被忽略的点当父容器是Row或Column时如果子组件的内容尺寸超过了剩余空间layoutWeight也不会按你想的比例压缩这时优先检查父容器的高度或宽度是否被固定死了。2.2 Stack层叠布局的定位技巧Stack是层叠容器后写的子组件会盖在先写的子组件上面。这个特性非常适合做“图片上的播放按钮”“卡片上的角标”“底部半透明遮罩”。Stack({ alignContent: Alignment.BottomEnd }) { Image($r(app.media.cover)) .width(200) .height(120) .backgroundColor(#cccccc) Text(3:42) .fontSize(12) .fontColor(Color.White) .backgroundColor(#99000000) .padding({ left: 6, right: 6, top: 2, bottom: 2 }) .borderRadius(4) .margin(8) } .width(200) .height(120)这段代码会在图片右下角显示一个类似“3:42”的时间标签。这里的alignContent是Stack的对齐容器作用对象是“没有明确位置信息的子组件”。如果子组件设置了position或offsetalignContent就会失效。Stack实际开发中很常用但有一个反直觉的点Stack的大小默认是“包裹子组件”也就是会跟最大的子组件一致。如果你希望Stack占满父容器必须显式写.width(100%).height(100%)或者设置宽高为具体值。否则可能出现悬浮元素定位不按预期走的情况。如果要做全屏遮罩我习惯在Stack里放一个Column或Row作为内容区再在内容区下方放一个透明背景层这样比完全用position控制要稳定得多。因为position是相对父容器定位的屏幕旋转或尺寸变化时容易跑偏。2.3 Flex弹性布局与自适应排列Flex在HarmonyOS里其实有点像“加强版的Row/Column”它的核心价值在于换行和自适应。比如筛选标签这种数量不固定、可能要自动换行的场景用Flex会比用Row手动换行省很多事。Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.Start }) { ForEach(this.tags, (tag: string) { Text(tag) .padding({ left: 16, right: 16, top: 8, bottom: 8 }) .margin(6) .backgroundColor(#f2f2f2) .borderRadius(16) }, (tag: string) tag) }Flex的wrap属性默认为NoWrap也就是不换行。如果想让标签在空间不够时自动换行要显式设置wrap: FlexWrap.Wrap。另一个容易搞混的是flexDirection它决定主轴方向Row还是Column很多时候你直接用Row或Column包裹反而更简单Flex真正的用武之地是“需要换行的横向排列”。Flex的flexBasis、flexGrow、flexShrink这三个属性在ArkUI里也存在但平时的使用频率远低于layoutWeight而且和CSS Flexbox并不完全一致。如果你只是想让子元素等比伸缩直接用layoutWeight更直观。如果想控制单个子元素的初始尺寸和压缩策略再去研究flexBasis和flexShrink。我自己的经验是Flex组件适合数量不固定且内容长短不一的场景比如搜索历史标签、商品属性筛选。如果是固定几个固定宽度的按钮用Row加space就足够了不需要上Flex。2.4 Grid网格布局与LazyForEach懒加载Grid在HarmonyOS里针对大量数据做了专门优化。如果列表数据可能超过几十条甚至上百条用Grid配合LazyForEach会比ForEach更靠谱因为LazyForEach只在滚动到附近时才创建组件滑动时性能好很多。Grid() { LazyForEach(this.gridData, (item: string) { GridItem() { Column() { Text(item) .fontSize(14) } .width(100%) .height(80) .backgroundColor(#e8f0fe) .borderRadius(8) .justifyContent(FlexAlign.Center) } }, (item: string) item) } .columnsTemplate(1fr 1fr) .columnsGap(10) .rowsGap(10) .padding(10)columnsTemplate是关键属性它定义了列宽模板。1fr 1fr代表两列等宽1fr 2fr代表第一列占一份第二列占两份。这里的fr单位和CSS里的fr类似按比例分配可用空间。还可以写成100vp 1fr这种固定加自适应的组合适合边栏加内容的布局。Grid里有一个容易踩坑的点内容是否为“无限滚动列表”。如果数据量不大用ForEach就行如果数据可能很多必须用LazyForEach否则一次性创建几十上百个GridItem首帧绘制会明显卡顿。基础篇阶段不用追求极致性能但代码习惯要从一开始养好。rowsTemplate控制行高模板如果没设置行高会根据内容自适应。columnsGap和rowsGap分别控制列间距和行间距。这四个参数组合起来基本能覆盖绝大多数的网格需求。3. 实战从零搭一个首页信息流页面光看属性列表记不住我习惯把知识点串到一个完整页面里。这次我们做一个简化版的“首页信息流”包含顶部标题、轮播位、功能宫格、推荐列表四块。3.1 页面结构和数据模型这个页面从上到下是顶部标题栏显示“首页”二字右侧一个消息入口图片轮播位一张横图右下角显示页码角标功能宫格4个功能入口两行两列推荐列表若干卡片每张卡片左边是标题右边是状态按钮。对应的信息结构树是Column ├── Row顶栏 ├── Stack轮播图 角标 ├── Grid功能宫格 └── List推荐列表结构确定后再往每一层填具体组件布局代码就不会乱。下面我直接用ArkTS写一个精简可运行的版本为了简化图片资源用纯色块代替。3.2 代码实现与关键点说明Entry Component struct HomePage { State message: string 首页 State pageIndex: number 1 private gridItems: string[] [推荐, 分类, 收藏, 设置] private listItems: string[] [HarmonyOS布局入门, ArkUI组件实践, 状态管理速览, 项目打包上线] build() { Column() { // 顶部标题栏 Row() { Text(this.message) .fontSize(20) .fontWeight(FontWeight.Bold) .layoutWeight(1) Text(消息) .fontSize(14) .fontColor(#666666) } .width(100%) .padding({ left: 16, right: 16, top: 12, bottom: 12 }) Scroll() { Column({ space: 12 }) { // 轮播位图片 页码角标 Stack({ alignContent: Alignment.BottomEnd }) { Column() .width(100%) .height(150) .backgroundColor(#d0e4ff) .borderRadius(12) Text(${this.pageIndex}/3) .fontSize(12) .fontColor(Color.White) .backgroundColor(#66000000) .padding({ left: 8, right: 8, top: 2, bottom: 2 }) .borderRadius(10) .margin(12) } .width(100%) .height(150) // 功能宫格 Grid() { ForEach(this.gridItems, (item: string) { GridItem() { Column() { Text(item) .fontSize(14) } .width(100%) .height(72) .backgroundColor(#f5f7fa) .borderRadius(8) .justifyContent(FlexAlign.Center) } }, (item: string) item) } .columnsTemplate(1fr 1fr) .columnsGap(10) .rowsGap(10) .height(154) // 推荐列表 ForEach(this.listItems, (item: string) { Row() { Text(item) .fontSize(16) .layoutWeight(1) Button(查看) .fontSize(12) .height(28) } .width(100%) .padding(12) .backgroundColor(#ffffff) .borderRadius(8) }, (item: string) item) // 底部留白 Blank() .height(20) } .width(100%) .padding({ left: 16, right: 16 }) } .layoutWeight(1) .scrollBar(BarState.Off) } .width(100%) .height(100%) .backgroundColor(#f2f4f7) } }有几个点需要重点说。顶部栏我用Row加layoutWeight(1)让标题占据左侧剩余空间右侧消息按钮自然靠右。这是常见的一行两列布局写法。轮播位用Stack图片区域和页码角标叠放。这里页码角标只是静态展示真实项目里需要根据轮播索引动态变化只需要把这个Text绑定到状态变量上就行。功能宫格用GridcolumnsTemplate设置两列Grid本身固定高度为154因为两行72vp加上10vp的行间距正好154。这里如果内容不再增加固定高度没问题如果后续功能入口会变多就得考虑让Grid高度自适应或者滚动。推荐列表用ForEach。这里数据只有四条用ForEach没问题。如果数据量可能膨胀推荐把List这一层换成List组件加LazyForEach滚动性能会好很多。外层滚动用ScrollScroll里面再放Column。这是ArkUI里比较常规的整页滚动布局。注意Scroll的滚动方向默认为垂直如果内容超出屏幕就能滑动如果内容没有超出Scroll就处于“刚好一屏”的状态不会有滚动条也不会报错。3.3 如何让页面在不同屏幕尺寸下保持合理初学者常常忽略了“换一台设备就变形”的问题。上面的例子中功能宫格固定为两列在手机宽度下合理但在平板或折叠屏展开态下两格可能会被拉得非常宽看起来奇怪。处理办法有两种。第一种是用百分比宽度配合最大宽度限制.width(100%) .constraintSize({ maxWidth: 600 })这样页面在大屏上不会无限拉伸。第二种是用ArkUI的栅格布局GridRow/GridCol把一行的列数从2改成4或6在大屏下自动增加列数。栅格布局在基础篇可以先了解实际做多端适配时再深入。我用得比较多的做法是内容区固定最大宽度大屏居中小屏直接占满。配合constraintSize和margin的auto值能覆盖大多数场景。真正的平板适配还需要处理字体缩放、边距、分栏等那是另一个层面的问题。4. 我踩过的布局坑和排查方法代码能跑起来不代表布局没问题。很多问题是在真机上才暴露的尤其是不同屏幕、不同系统字体大小下。下面这些都是我实际开发中遇到过、排查过、最后找到原因的典型案例。4.1 布局溢出组件被挤到屏幕外表现真机运行时页面右侧或底部有一部分内容看不到或者报“布局溢出”的警告日志。最典型的原因是内容超出了父容器而父容器又没有给子组件提供滚动能力。比如Column里有多个卡片总高度超过了屏幕高度外层没有Scroll包裹底部的卡片就被截断了。排查方法很简单先确认最外层是否设置了height(100%)再看内容是否被Scroll包裹。如果是横向溢出重点检查Row里有没有子组件设置了过宽的固定宽度或者Text内容过长没有换行。Text组件我一般会显式设置maxLines和textOverflow比如Text(这是一段很长很长很长很长的描述) .maxLines(1) .textOverflow({ overflow: TextOverflow.Ellipsis })这样即使内容再长也只会显示省略号不会把布局撑破。4.2 权重为什么没生效layoutWeight是线性布局里最常用的适配手段但偶尔会碰到“设置了权重子组件却没有按比例分配”的情况。我遇到过的原因基本是三个父容器没有固定尺寸或没有明确撑满可用空间子组件同时设置了固定宽高权重和固定值产生冲突父容器是Stack或Flex而不是Row/ColumnlayoutWeight在部分场景下不适用。其中第二点最容易中招。如果你写了.width(100).layoutWeight(1)在部分版本上布局结果是“宽度按权重来”但代码可读性很差别人维护时看不懂你到底想让它多宽。我的建议是用layoutWeight的组件就不要写固定尺寸写固定尺寸就不要用layoutWeight二选一。4.3 滚动嵌套引起的滑动冲突页面里既有外层Scroll内部又有List或Grid时手势滑动可能不跟手。常见表现是手指在列表区域上下滑动时页面不动或者卡住。这个问题在基础篇不一定非要解决得很深但要知道大方向内部可滚动组件优先消费手势外层才滑动如果希望整个页面跟着滚就不要在内部单独放List。简化方案是内部不用List用ForEach生成普通卡片放进外层Scroll。如果数据量很大内部必须用List那就需要控制内部列表的高度为固定值比如高度设为“恰好显示几项”剩下的交给外层Scroll。这种方式虽然不完美但能解决大部分简单场景下的滑动冲突。4.4 页面刚加载时布局跳动表现进入页面时图片还没加载出来整个布局高度是塌陷的图片加载完成后下方内容突然被顶下去用户会看到明显的跳动。解决思路是给图片提前占位。例如轮播图区域直接给一个固定的宽高或者用aspectRatio锁定宽高比。不要只写Image组件而完全不设尺寸。哪怕先用一个背景色块占位也比加载后突然撑开好得多。Column() .width(100%) .aspectRatio(16 / 9) .backgroundColor(#e5e5e5) .borderRadius(12)如果你知道图片一定是16:9直接给父容器设置aspectRatio图片加载后无论尺寸怎么变都不会把布局顶乱。4.5 常见问题速查表现象可能原因处理建议内容超出屏幕被遮挡缺少Scroll或高度计算错误外层加Scroll并核对是否height(100%)row溢出横向滚动子组件固定宽度超过父容器减少固定宽高改为layoutWeight或百分比权重不生效子组件同时设置了固定宽高不要混用权重组件不写固定尺寸Stack子组件位置不对alignContent被position覆盖子组件不设置position用alignContent对齐Grid首屏卡顿全部用ForEach渲染大量数据改用LazyForEach懒加载图片加载后内容跳动图片区域无固定高度或宽高比设置aspectRatio或固定占位高度5. 布局之外的性能与状态管理注意点布局写完之后还有一个经常被忽视的环节状态更新对布局的影响。HarmonyOS应用开发里页面数据一变布局就会重新渲染。如果状态管理设计得不好布局代码再漂亮体验也会糟糕。5.1 避免过度嵌套我见过一些页面一个简单的列表卡片外面套了三层Column两层Row每层还都设置了padding和margin。这种代码虽然跑得起来但可读性和渲染性能都很差。ArkUI的布局系统会在状态变化时重新测量和绘制层级越多计算量越大。能用一个Row完成的事情不要套四层。一个简单的判断标准如果某层容器下面只有一个子组件那这层大概率是多余的可以直接去掉。// 冗余嵌套 Column() { Row() { Column() { Text(内容) } } }// 精简后 Text(内容)代码量减少视觉效果完全一致性能还更好。写布局的时候多回头看看有没有可以砍掉的容器层。5.2 状态变量和布局刷新State是ArkUI里最常见的状态装饰器。只要状态变了依赖它的组件就会重新刷新。这本身很好用但如果你把很多不相关的数据都放在同一个State对象里那么每改一个字段整个页面都会被刷新。比如一个页面同时有用户信息、列表数据、弹窗开关把它们拆成多个State字段比放在一个大的对象里更利于精确控制刷新范围。基础篇阶段不要求极致的性能优化但养成分散管理状态的习惯后面做复杂页面时会轻松很多。如果你已经接触到了Observed和ObjectLink说明开始进入状态管理深水区了。布局本身不会随着状态自动“变聪明”只有在你设计了合理的刷新粒度之后布局才能在数据变化时只更新该更新的部分。5.3 从基础篇就开始养成的好习惯布局组件命名要清晰比如底部操作区用BottomBar不要叫View1padding和margin建议用统一的间距体系比如4vp、8vp、12vp、16vp而不是随手填颜色值用资源文件管理方便后面一键换肤每个页面最外层都设置背景色避免默认白色闪烁。这些都是“布局之外”的事但决定了你的项目能不能长期维护。结尾布局这部分我在做鸿蒙应用开发时反复练了很多遍从最开始照着文档敲Row和Column到后来看到设计稿就能在脑子里生成一棵布局树中间靠的不是背属性而是不断拿真实页面去拆、去练。特别是Stack和权重这两个点建议你多花时间做几个小实验改几个值看看效果比看十篇文章都管用。如果你正在学基础篇的构建布局我个人的建议是把本文的首页信息流例子自己敲一遍然后尝试改成“我的页面”加一个头像、几个菜单项、一个退出按钮。改完你基本就掌握了80%的日常布局需求。后面遇到不明白的属性优先去官方文档查当前版本的说明再回来对照自己的代码理解会更深。
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻