在这里插入图片描述

在这里插入图片描述

一、引言

HarmonyOS NEXT 是鸿蒙生态的里程碑版本,实现了从系统底层到应用框架的完全自研。与之配套的 ArkUI 框架提供了一套完整的声明式 UI 布局体系,其核心设计理念是「弹性」——布局容器和子组件能够根据可用空间自动调整尺寸和排列方式,从而适配从智能手表到折叠屏的各种屏幕尺寸。

ArkUI 的布局体系可归纳为五大类容器,覆盖了移动端开发的所有布局需求:

容器类别 核心容器 排列维度 核心属性
弹性容器 Flex / Column / Row 一维(水平/垂直) flexGrow / flexShrink / flexBasis / flexWrap
层叠容器 Stack Z 轴(层叠) alignContent / alignSelf / zIndex
网格容器 Grid 二维(行+列) columnsTemplate / rowsTemplate / columnStart
列表容器 List 一维(滚动) listDirection / divider / sticky
标签容器 Tabs 视图切换 barPosition / scrollable / animationDuration

本文将深入这五大类容器的 13 种布局模式,包含完整的代码示例、布局公式、典型应用场景和性能对比。


二、项目架构与入口

2.1 应用入口 EntryAbility

鸿蒙应用继承 UIAbility,在 onWindowStageCreate 中加载首页面:

import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';

export default class EntryAbility extends UIAbility {
  onWindowStageCreate(windowStage: window.WindowStage): void {
    windowStage.loadContent('pages/TabsBottomDemo', (err) => {
      if (err.code) {
        hilog.error(0x0000, 'App', 'Failed: %{public}s', JSON.stringify(err));
        return;
      }
    });
  }
}

loadContent 参数为页面路径(不含 .ets 后缀),框架自动解析并加载对应页面。

2.2 页面通用骨架

每个演示页面遵循一致的结构:

Column(height:100%, width:100%)
├── 标题栏(蓝底白字,固定)
└── Scroll 滚动区
    └── Column
        ├── SectionCard 演示块 1
        ├── SectionCard 演示块 2
        ├── ...
        └── TechPanel 技术说明

三、Column 主轴对齐

3.1 核心概念

Column 是最基础的垂直布局容器。主轴(Main Axis)方向为「从上到下」,交叉轴(Cross Axis)方向为「从左到右」:

  • justifyContent → 控制主轴(垂直)方向的对齐
  • alignItems → 控制交叉轴(水平)方向的对齐

3.2 6 种对齐值

枚举值 效果 CSS 类比
FlexAlign.Start 顶部起始排列 flex-start
FlexAlign.Center 垂直居中 center
FlexAlign.End 底部对齐 flex-end
FlexAlign.SpaceBetween 两端对齐 space-between
FlexAlign.SpaceAround 子项左右间距相等 space-around
FlexAlign.SpaceEvenly 子项及两端间距相等 space-evenly

3.3 代码实现

@Entry
@Component
struct ColumnStartPage {
  build() {
    Column() {
      // 标题区
      Column() { /* ... */ }
        .backgroundColor('#2d5f8a')

      // 核心:Column + justifyContent(Start)
      Column() {
        ForEach(this.infoList, (item, idx) => {
          InfoCard({ item, index: idx })
        })
      }
      .alignItems(HorizontalAlign.Start)     // 交叉轴左对齐
      .justifyContent(FlexAlign.Start)       // ★ 主轴从顶部开始 ★
      .layoutWeight(1)                        // 撑满剩余高度
    }
    .width('100%').height('100%')
    .backgroundColor('#eef2f7')
  }
}

关键layoutWeight(1) 让 Column 填满父容器剩余高度,否则 justifyContent 的效果不可见——当容器高度等于内容高度时,Start、Center、End 的差异看不出来。


四、Flex 弹性容器与四种方向

Flex 是比 Column/Row 更底层、更灵活的弹性容器。Column 等效于 flexDirection(Column) 的 Flex,Row 等效于 flexDirection(Row) 的 Flex。

4.1 四种方向

方向 排列 代码首项位置 典型场景
FlexDirection.Row 从左→右 最左侧 水平导航栏
FlexDirection.RowReverse 从右→左 最右侧 RTL 语言
FlexDirection.Column 从上→下 最顶部 表单列表
FlexDirection.ColumnReverse 从下→上 最底部 聊天列表

4.2 RowReverse 示例

Flex() {
  Text('A(先写)')
  Text('B(中间)')
  Text('C(后写)')
}
.flexDirection(FlexDirection.RowReverse)
// 显示结果:[C] [B] [A] ← 代码先写的 A 在最右侧

4.3 ColumnReverse 聊天列表

Flex() {
  ChatBubble({ msg: '早上好!', isSelf: false })     // 最早 → 顶部
  ChatBubble({ msg: '下午三点开会.', isSelf: true })
  ChatBubble({ msg: '好的收到。', isSelf: false })    // 最新 → 底部
}
.flexDirection(FlexDirection.ColumnReverse)

容易混淆的点

  • RowReverse 只反转排列顺序,不反转每个组件内部的内容方向
  • justifyContent(Start) 在 RowReverse 下等效于「右侧对齐」
  • alignItems 不受反向影响,始终控制交叉轴方向

五、Flex + Wrap 响应式卡片流

当子组件数量多到一行放不下时,flexWrap(FlexWrap.Wrap) 让子组件自动换行到下一行,形成流式布局。

5.1 核心代码

Flex() {
  ForEach(PRODUCTS, (product) => {
    ProductCard({ product, cardSizeMode: this.cardSizeMode })
  })
}
.flexDirection(FlexDirection.Row)       // 主轴水平
.flexWrap(FlexWrap.Wrap)                // ★ 允许换行 ★
.justifyContent(FlexAlign.Start)        // 行内左对齐
.alignItems(ItemAlign.Start)            // 交叉轴顶部对齐
.width('100%')

5.2 layoutWeight 等分宽度

子组件使用 layoutWeight(1) 控制行内等分宽度:

@Component
struct ProductCard {
  @Link cardSizeMode: number;   // 0=小(3列) / 1=中(2列) / 2=大(1列)

  build() {
    Column() {
      Text(this.product.emoji).fontSize(this.cardSizeMode === 2 ? 36 : 26)
      Text(this.product.name).fontSize(this.cardSizeMode === 2 ? 18 : 14)
      Text(`¥${this.product.price}`).fontSize(this.cardSizeMode === 2 ? 22 : 18)
    }
    .layoutWeight(1)                    // ★ 同一行中等分宽度 ★
    .margin(6)
  }
}

5.3 flexGrow vs layoutWeight

属性 分配对象 公式
flexGrow 剩余空间(容器 − 所有子项内容尺寸) 内容宽 + 剩余空间 × (growN / Σgrow)
layoutWeight 容器全部空间 容器宽 × (weightN / Σweight)

在换行布局中,layoutWeight 更常用——每行独立计算权重分配,不受其他行子项内容宽度的干扰。

5.4 @State 密度切换

@State cardSizeMode: number = 0;

Row() {
  ForEach([0, 1, 2], (mode: number) => {
    Button(['紧凑(3列)', '适中(2列)', '宽大(1列)'][mode])
      .layoutWeight(1)
      .backgroundColor(this.cardSizeMode === mode ? '#2d5f8a' : '#e8ecf0')
      .onClick(() => { this.cardSizeMode = mode; })
  })
}

六、Flex + flexGrow 弹性增长

flexGrow 是弹性布局最重要的单个属性,它定义了子组件在剩余空间中的分配比例。

6.1 核心公式

子项 N 的增长量 = 剩余空间 × (growN ÷ Σgrow)

剩余空间 = 容器主轴尺寸 − 所有子组件内容尺寸之和

6.2 单项填充示例

Flex() {
  Text('A:grow(0)')   // 不增长
  Text('B:grow(0)')   // 不增长
  Text('C:grow(1)')   // ★ 填充所有剩余空间 ★
}
.flexDirection(FlexDirection.Row)
.width('100%')

这是「固定左 + 固定右 + 弹性中间」布局模式的实现原理。

6.3 等比分配

// 1:1:1 → 三等分
grow(1) : grow(1) : grow(1)  // 每项 = 剩余空间 × 1/3

// 1:2:1 → 中间是两边 2 倍
grow(1) : grow(2) : grow(1)  // A=剩余×1/4, B=剩余×1/2, C=剩余×1/4

6.4 垂直方向页面骨架

Flex() {
  Row() {}.flexGrow(0).height(48)     // 顶部固定
  Column() {}.flexGrow(1)             // ★ 内容区弹性 ★
  Row() {}.flexGrow(0).height(40)     // 底部固定
}
.flexDirection(FlexDirection.Column)
.height('100%')

flexGrow(1) 让中间内容区自动填满头尾之外的所有剩余高度,适配从 iPhone SE 到折叠屏的任何屏幕高度。


七、Flex + flexBasis 基准尺寸

flexBasis 设置子组件在弹性分配前的主轴初始尺寸,flexGrow/flexShrink 在此之上叠加。

7.1 最终尺寸公式

最终尺寸 = flexBasis + 增长量(grow) − 压缩量(shrink)

7.2 flexBasis 取值

含义 示例
'auto' 由内容或 width/height 决定 .flexBasis('auto')
'80px' 固定初始尺寸 80vp .flexBasis('80px')
'33%' 初始尺寸为容器主轴尺寸的 33% .flexBasis('33%')

7.3 三种实战模式

// ★ 模式1:固定左栏 + 弹性右栏
Flex() {
  Column() {}.flexBasis('80px').flexGrow(0)    // 侧栏固定
  Column() {}.flexBasis('auto').flexGrow(1)    // 主区弹性
}
.flexDirection(FlexDirection.Row)

// ★ 模式2:等百分比
Flex() {
  Column() {}.flexBasis('33.3%').flexGrow(0)
  Column() {}.flexBasis('33.3%').flexGrow(0)
  Column() {}.flexBasis('33.3%').flexGrow(0)
}

// ★ 模式3:垂直骨架
Flex() {
  Row() {}.flexBasis('56px').flexGrow(0)       // 顶栏
  Column() {}.flexBasis('auto').flexGrow(1)    // 内容弹性
  Row() {}.flexBasis('48px').flexGrow(0)       // 底栏
}
.flexDirection(FlexDirection.Column).height('100%')

7.4 flexBasis + flexShrink 压缩

当容器宽度不足以容纳所有子项的 flexBasis 之和时,flexShrink 控制压缩比例:

Flex() {
  Box({ basis:'100px', shrink:0 })   // 不缩小,保持100px
  Box({ basis:'100px', shrink:1 })   // 缩小
  Box({ basis:'100px', shrink:1 })   // 缩小
  Box({ basis:'100px', shrink:1 })   // 缩小
}
.flexDirection(FlexDirection.Row)
.width(300)   // 4×100=400 > 300,溢出100px
// A:100px, B/C/D 各约 67px

八、Stack 层叠布局

Stack 是 Z 轴层叠容器——子组件沿 Z 轴堆叠而非沿 X/Y 轴排列。后写的子组件覆盖在先写的上方。alignContent 控制所有子组件整体的对齐位置。

8.1 9 种 Alignment

水平\垂直 Top Center Bottom
Start TopStart 左上 Start 左中 BottomStart 左下
Center TopCenter 上中 Center 正中 BottomCenter 下中
End TopEnd 右上 End 右中 BottomEnd 右下

8.2 TopStart(左上对齐)

Stack() {
  Box({ w:200, h:150, label:'底层' })
  Box({ w:160, h:100, label:'中层' })
  Box({ w:100, h:80, label:'顶层' })
}
.alignContent(Alignment.TopStart)

8.3 Top(顶部居中)

Stack() {
  Row() {}.width('100%').height(64).backgroundColor('#2d5f8a')  // 背景
  Text('📢 系统通知').fontSize(16).fontColor('#ffffff')          // 居中标题
  Text('✕').alignSelf(ItemAlign.End).margin({ top:8, right:14 }) // 右上关闭
}
.alignContent(Alignment.Top)

8.4 Bottom(底部居中)

Stack() {
  Column() {}.width('100%').height(160).backgroundColor('#1a1a2e')
  Row() { Text('🎵'), Text('歌名') }
  Row() { Text('⏮'), Text('⏸'), Text('⏭') }
}
.alignContent(Alignment.Bottom)

8.5 Overlay(层叠盖层 + ZIndex)

Stack() {
  ImageScreen()                              // L1: 图片(zIndex默认0)
  Column() {}.width('100%').height('100%')
    .backgroundColor('#22000000').zIndex(1)  // L2: 半透明蒙层
  Text('👟 运动鞋').alignSelf(ItemAlign.Start)
    .margin(10).zIndex(2)                    // L3: 左上标签
  Row() { Text('¥ 1,299').fontColor('#e74c3c') }
    .alignSelf(ItemAlign.End).width('100%')
    .zIndex(5)                               // L4: 底部价格条(最上层)
}
.alignContent(Alignment.TopStart)

ZIndex 规则:数值越大越靠前;相同值时后写覆盖先写;无 ZIndex 默认值为 0。

8.6 alignSelf 覆盖整体对齐

Stack() {
  Box('A:跟随')                              // 默认跟随 TopStart
  Box('B:左上').alignSelf(ItemAlign.Start)   // 覆盖为左上
  Box('C:右上').alignSelf(ItemAlign.End)     // 覆盖为右上
}
.alignContent(Alignment.Top)

8.7 position 精准定位

Stack() {
  Dot({ x:'10%', y:'10%', label:'A' })
  Dot({ x:'50%', y:'30%', label:'B' })
  Dot({ x:'80%', y:'60%', label:'C' })
}
.alignContent(Alignment.TopStart)

.position({x, y}) 以 Stack 左上角为原点,支持像素和百分比。


九、Grid 网格布局

Grid 是 ArkUI 最强大的二维布局容器,通过 columnsTemplaterowsTemplate 同时控制行和列的尺寸。

9.1 固定列数

Grid() {
  ForEach(items, (item) => {
    GridItem() {
      Column() { Text(item.label) }.width('100%').height(64)
    }
  })
}
.columnsTemplate('1fr 1fr 1fr')   // ★ 三列等宽 ★
.columnsGap(8).rowsGap(8)
.width('100%')

9.2 fr 单位详解

模板 含义
'1fr 1fr 1fr' 三列等宽
'1fr 2fr 1fr' 中间列是两侧 2 倍
'80px 1fr 1fr' 第一列固定 + 后两列等分剩余
'1fr 1fr 1fr 1fr' 四列等宽

9.3 自适应列数

CSS 的 repeat(auto-fill, minmax(...)) 在 ArkUI 中通过代码实现:

@State adaptiveCols: string = '1fr 1fr 1fr';
private minColWidth: number = 100;   // 最小列宽 (vp)

Grid() { /* ... */ }
.columnsTemplate(this.adaptiveCols)
.onAreaChange((_oldW, _oldH, newW, _newH) => {
  // ★ 自适应公式 ★
  let n = Math.floor(newW / this.minColWidth);
  n = Math.max(1, Math.min(n, 8));
  this.adaptiveCols = Array(n).fill('1fr').join(' ');
})

当容器宽度 360vp 时 n=3(3列),480vp 时 n=4(4列),250vp 时 n=2(2列)。

9.4 rowsTemplate 行定义

// 3×3 九宫格
.columnsTemplate('1fr 1fr 1fr')
.rowsTemplate('1fr 1fr 1fr')

// 头尾固定 + 中间弹性(页面骨架)
.columnsTemplate('1fr')
.rowsTemplate('80px 1fr 80px')

// 日历网格(表头固定 + 日期等分)
.columnsTemplate('1fr 1fr 1fr 1fr 1fr 1fr 1fr')  // 7 列
.rowsTemplate('40px 1fr 1fr 1fr 1fr 1fr')           // 表头+5行

9.5 GridItem 跨列

Grid() {
  // 横幅:跨 3 列(占整行)
  GridItem() { Banner() }
    .columnStart(1).columnEnd(3)   // ★ 跨第1到第3列 ★
  
  // 普通卡片
  ForEach(PRODUCTS, (p) => {
    GridItem() { ProductCard(p) }
  })
}
.columnsTemplate('1fr 1fr 1fr')

十、List 列表容器

List 是鸿蒙的高性能长列表容器,支持按需渲染——只渲染可见区域内的列表项。与 Scroll + Column 的全量渲染不同,List 在数据量大时依然流畅。

10.1 垂直列表

List() {
  ForEach(messages, (msg) => {
    ListItem() {
      // 列表项内容
      Row() {
        Text(msg.avatar).fontSize(24)
        Column() {
          Text(msg.name).fontSize(15)
          Text(msg.message).fontSize(13).fontColor('#666')
        }
      }.padding(12)
    }
  }
}
.listDirection(Axis.Vertical)   // 垂直方向(默认)
.divider({ strokeWidth: 0.5, color: '#e8ecf0', startMargin: 72 })
.width('100%').height(320)

10.2 水平列表

List() {
  ForEach(PRODUCTS, (product) => {
    ListItem() {
      ProductCard(product)
    }
    .width(160)                           // ★ 水平 ListItem 必须设宽度 ★
    .margin({ left: 6, right: 6 })
  }
}
.listDirection(Axis.Horizontal)           // ★ 水平方向 ★
.height(180)
.scrollBar(BarState.Off)                  // 隐藏滚动条
.edgeEffect(EdgeEffect.Spring)            // 边缘回弹

10.3 粘性标题

// 通讯录分组:标题粘性吸顶
ForEach(groups, (group) => {
  ListItem() {
    Text(group.initial)  // 分组标题
  }
  .sticky(Sticky.Normal)                  // ★ 粘性效果 ★

  ForEach(group.contacts, (c) => {
    ListItem() { ContactRow(c) }
  })
})

10.4 List vs Scroll+Column 选择指南

数据量 推荐方案 原因
< 10 项 Scroll+Column 或 List 均可
10~50 项 List 按需渲染,内存更低
50~1000+ 项 List + LazyForEach 必须使用,否则卡顿

十一、Tabs 标签页容器

Tabs 是 ArkUI 中最常用的多视图切换容器,由 TabBar(标签栏)+ TabContent(内容区)组成。

11.1 顶部标签

Tabs() {
  TabContent() { HomePage() }.tabBar('🏠 首页')
  TabContent() { DiscoverPage() }.tabBar('🔍 发现')
  TabContent() { MessagePage() }.tabBar('💬 消息')
  TabContent() { MinePage() }.tabBar('👤 我的')
}
.barPosition(BarPosition.Start)   // ★ 标签栏在顶部 ★
.width('100%').height(300)

11.2 底部标签(主流 App 导航模式)

Tabs() {
  ForEach(this.tabs, (tab, idx) => {
    TabContent() {
      PageContent(tab.icon, tab.label)
    }.tabBar(this.BottomTabBuilder(idx, tab))
  })
}
.barPosition(BarPosition.End)     // ★ 标签栏在底部(核心)★
.barHeight(60)                     // 自定义标签栏高度
.width('100%').height('100%')

11.3 自定义底部 Tab(图标+文字+角标)

@Builder
BottomTabBuilder(index: number, tab: TabData) {
  Column() {
    Stack() {
      Text(tab.icon).fontSize(22)
        .fontColor(this.currentIndex === index ? '#2d5f8a' : '#999')
      if (tab.badge > 0) {
        Text(`${tab.badge}`).fontSize(9).fontColor('#ffffff')
          .backgroundColor('#e74c3c').borderRadius(8)
          .padding({ left: 4, right: 4, top: 1, bottom: 1 })
      }
    }.width(30).height(26)
    Text(tab.label).fontSize(10)
      .fontColor(this.currentIndex === index ? '#2d5f8a' : '#999')
      .margin({ top: 2 })
  }
}

11.4 中间突出按钮

if (tab.isCenter) {
  // ★ 中间突出按钮 ★
  Column() { Text('➕').fontSize(28).fontColor('#ffffff') }
    .width(52).height(52)
    .backgroundColor('#e74c3c').borderRadius(26)
    .offset({ y: -12 })               // 向上突出 TabBar
} else {
  // 普通标签
  Column() { Text(tab.icon), Text(tab.label) }
}

11.5 Tabs 常用属性速查

属性 作用 示例
barPosition 标签栏位置 Start(顶部) / End(底部)
scrollable 可滑动切换 true / false
animationDuration 切换动画时长(ms) 300
barWidth 标签栏宽度 '100%'
barHeight 标签栏高度 48 / 60
onChange 切换回调 (idx) => {}

十二、容器选择决策树

面对一个 UI 需求时,可以按照以下决策树选择合适的容器:

需要「视图切换」(多页)?
  ├── 是 → Tabs
  │     ├── 主界面导航 → barPosition(End) 底部
  │     └── 内容分类 → barPosition(Start) 顶部
  └── 否 → 继续

需要「层叠/覆盖」效果?
  ├── 是 → Stack
  │     ├── 图片叠文字 → alignContent(TopStart) + alignSelf
  │     ├── 头像+徽章 → alignContent(TopStart) + alignSelf(End)
  │     └── 多层层叠 → ZIndex 控制顺序
  └── 否 → 继续

需要「二维网格」?
  ├── 是 → Grid
  │     ├── 固定列数 → columnsTemplate('1fr 1fr 1fr')
  │     ├── 自适应列数 → @State + onAreaChange
  │     └── 固定行高 → rowsTemplate('80px 1fr 80px')
  └── 否 → 继续

需要「滚动长列表」?
  ├── 是 → List
  │     ├── 垂直列表 → listDirection(Vertical)
  │     ├── 水平列表 → listDirection(Horizontal)
  │     └── 数据量>50 → List + LazyForEach
  └── 否 → 继续 → Flex / Column / Row
  ├── 一维排列 → Flex + flexDirection
  │     ├── 从左到右 → Row / FlexDirection.Row
  │     ├── 从右到左 → FlexDirection.RowReverse
  │     ├── 从上到下 → Column / FlexDirection.Column
  │     └── 从下到上 → FlexDirection.ColumnReverse
  ├── 需要弹性增长 → flexGrow
  ├── 需要初始尺寸 → flexBasis
  ├── 需要换行 → flexWrap(Wrap)
  └── 需要等分 → layoutWeight

十三、性能优化建议

13.1 避免深层嵌套

Flex/Stack/Grid/List 可以嵌套,但深度建议控制在 3-4 层以内。过深的布局树会增加每次状态变更时的计算开销。

13.2 @State 最小化作用域

@State 的变化会触发所在组件及其子组件的 build() 重渲染。尽量将状态作用域限制在真正需要更新的层级。

13.3 长列表用 List

数据量超过 20 项时必须用 List 而非 Scroll + Column,因为 List 只渲染可见区域,Scroll + Column 全量渲染。

13.4 阴影与圆角的性能

过多的 shadowborderRadius 会影响滚动帧率。建议:

  • 长列表卡片使用轻柔阴影(color: '#10000000'
  • 对焦点卡片才使用重阴影

13.5 懒加载 LazyForEach

当 List 或 Grid 中的子项超过 50 项时,使用 LazyForEach 替代 ForEach

LazyForEach(this.dataSource, (item) => {
  ListItem() { ComplexCard(item) }
}, (item) => item.id.toString())

十四、常见布局陷阱

14.1 flexGrow 不生效

原因:容器没有设置明确的主轴尺寸(width/height),
     或容器尺寸刚好等于子项内容尺寸(无剩余空间)
解决:确保容器有明确的主轴尺寸,或用 layoutWeight

14.2 子项溢出容器

原因:所有子项的 flexShrink 都是 0(默认 shrink=1,但可被覆盖)
解决:给需要压缩的子项设置 flexShrink(1)

14.3 Wrap 不换行

原因:flexWrap 默认为 NoWrap
解决:显式设置 .flexWrap(FlexWrap.Wrap)

14.4 Stack 方向混淆

误区:Stack(TopStart) 中所有子项一定在左上角
正解:alignSelf 可突破整体对齐约束
记忆法:alignContent = 整体对齐,alignSelf = 个体突破

14.5 ColumnReverse 下 Start/End 方向

误区:Start 仍然是「顶部」
正解:ColumnReverse 下 Start = 底部,End = 顶部
记忆法:Start 永远指向主轴的起始位置

十五、布局属性速查表

15.1 Flex 弹性属性

属性 作用于 默认值 说明 CSS 类比
flexGrow 子组件 0 放大比例(≥0) flex-grow
flexShrink 子组件 1 缩小比例(≥0) flex-shrink
flexBasis 子组件 'auto' 主轴初始尺寸 flex-basis
flexWrap 容器 NoWrap 换行控制 flex-wrap
justifyContent 容器 Start 主轴对齐(6种) justify-content
alignItems 容器 Stretch 交叉轴对齐(4种) align-items
layoutWeight 子组件 0 比例分配全部空间

15.2 Stack 层叠属性

属性 作用 取值
alignContent 子组件整体对齐 9 种 Alignment
alignSelf 单子项突破约束 ItemAlign.Start/Center/End
zIndex Z 轴顺序 number(越大越靠前)
position 左上角原点定位 {x, y} 百分比或像素

15.3 Grid 网格属性

属性 作用 示例
columnsTemplate 列宽模板 '1fr 1fr 1fr'
rowsTemplate 行高模板 '80px 1fr 80px'
columnsGap 列间距 8
rowsGap 行间距 8
columnStart/End GridItem 跨列 number(从1开始)
rowStart/End GridItem 跨行 number(从1开始)

15.4 List 列表属性

属性 作用 示例
listDirection 滚动方向 Axis.Vertical / Axis.Horizontal
divider 分隔线 {strokeWidth, color, startMargin}
sticky 粘性标题 Sticky.Normal
scrollBar 滚动条 BarState.Auto / Off
edgeEffect 边缘效果 EdgeEffect.Spring

15.5 Tabs 标签属性

属性 作用 示例
barPosition 标签栏位置 BarPosition.Start / End
scrollable 滑动切换 true / false
animationDuration 切换动画(ms) 300
barHeight 标签栏高度 60

十六、结语

从最简单的 Column + justifyContent 基础对齐,到 Flex 四种方向与弹性伸缩,再到 Stack 的九种层叠对齐与 ZIndex 控制,从 Grid 的固定列与自适应列网格,到 List 的高性能长列表,最后到 Tabs 的顶部/底部标签导航——这 13 种布局模式覆盖了鸿蒙应用开发中 95% 以上的界面布局需求。

回顾这些布局模式,可以归纳出三条核心设计哲学:

  1. 永远不用固定尺寸。能用 flexGrowlayoutWeightflexBasis 的地方,就不写 width(100)height(200)——弹性布局的本质是让空间分配自适应。

  2. 拥抱「剩余空间」思维flexGrow 分配剩余空间,layoutWeight 重算全部空间,flexBasis 定义初始起点。理解这三个概念,弹性布局就不再是魔法。

  3. 选对容器,事半功倍。Flex 用于一维排列,Stack 用于层叠覆盖,Grid 用于二维网格,List 用于长列表滚动,Tabs 用于多视图切换——每个容器有自己最擅长的领域。

鸿蒙原生 ArkTS 的弹性布局体系,为开发者提供了一套完备、高效、声明式的 UI 布局方案。掌握这些模式后,布局不再是代码的堆砌,而是对屏幕空间的优雅分配。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐