鸿蒙原生 ArkTS 布局体系完全实战指南:从 Flex/Stack/Grid 到 List/Tabs 的 13 种布局模式

一、引言

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 中加载首页面:

typescript 复制代码
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 代码实现

typescript 复制代码
@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 示例

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

4.3 ColumnReverse 聊天列表

typescript 复制代码
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 核心代码

typescript 复制代码
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) 控制行内等分宽度:

typescript 复制代码
@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 密度切换

typescript 复制代码
@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 单项填充示例

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

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

6.3 等比分配

typescript 复制代码
// 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 垂直方向页面骨架

typescript 复制代码
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 三种实战模式

typescript 复制代码
// ★ 模式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 控制压缩比例:

typescript 复制代码
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(左上对齐)

typescript 复制代码
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(顶部居中)

typescript 复制代码
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(底部居中)

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

8.5 Overlay(层叠盖层 + ZIndex)

typescript 复制代码
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 覆盖整体对齐

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

8.7 position 精准定位

typescript 复制代码
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 固定列数

typescript 复制代码
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 中通过代码实现:

typescript 复制代码
@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 行定义

typescript 复制代码
// 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 跨列

typescript 复制代码
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 垂直列表

typescript 复制代码
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 水平列表

typescript 复制代码
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 粘性标题

typescript 复制代码
// 通讯录分组:标题粘性吸顶
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 顶部标签

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

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

typescript 复制代码
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(图标+文字+角标)

typescript 复制代码
@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 中间突出按钮

typescript 复制代码
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

typescript 复制代码
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 布局方案。掌握这些模式后,布局不再是代码的堆砌,而是对屏幕空间的优雅分配。