鸿蒙原生 ArkTS 布局方式之 Flex 默认行为:子组件的拉伸与收缩深度解析

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

一、引言

HarmonyOS NEXT(鸿蒙星河版)自诞生以来,以其全栈自研的底座和原生纯净的生态,吸引了越来越多开发者的关注。在鸿蒙应用开发中,UI 布局是最基础也最核心的能力之一。ArkTS(Ark TypeScript)作为鸿蒙原生应用开发的推荐语言,结合 ArkUI 声明式框架,提供了一套强大而灵活的布局系统。

在众多布局组件中,Flex 无疑是最常用、最灵活的一种。它借鉴了 CSS Flexbox 的设计理念,但在语法和使用方式上又带有鲜明的鸿蒙特色。很多初学者在刚接触 Flex 时,往往会被子组件的"拉伸"和"收缩"行为所困惑——为什么有些子组件会自动填满空间?为什么有些会超出容器后自动压缩?这些行为的背后,其实是 Flex 布局的默认参数在起作用。

本文将以一个完整的 ArkTS 示例应用为载体,深入剖析 Flex 布局的三大默认行为:ItemAlign.Stretch(拉伸)flexShrink(收缩)flexGrow(增长)。通过逐行解读代码和运行效果分析,带你彻底掌握 Flex 布局的精髓。

本文示例代码基于 HarmonyOS NEXT API 24(ArkTS 声明式范式)编写,可在 DevEco Studio 5.0+ 中直接编译运行。


二、HarmonyOS NEXT 与 ArkTS 布局体系概览

2.1 什么是 ArkTS?

ArkTS 是鸿蒙生态原生的应用开发语言,它在 TypeScript 的基础上进行了扩展和约束,专为鸿蒙 ArkUI 框架设计。与标准 TypeScript 相比,ArkTS 具有以下特点:

  • 强类型 + 静态检查:所有类型在编译期确定,减少运行时错误
  • 声明式 UI:通过 @Component@Builder 装饰器描述界面,数据驱动视图更新
  • 响应式状态管理@State@Prop@Link 等装饰器让状态变化自动反映到 UI
  • 安全并发:不支持 any 类型,限制了部分动态特性,换来更高的运行效率

2.2 鸿蒙布局体系的核心组件

ArkUI 提供了丰富的布局组件,大致可分为三类:

类别 组件 适用场景
线性布局 Flex、Row、Column 一维排列,最常用
层叠布局 Stack 子组件重叠放置
相对布局 RelativeContainer 子组件间、子组件与容器间的相对定位
弹性布局 Flex(加强版) 支持 grow / shrink / basis 弹性伸缩

其中,Flex 是最全能的一维布局组件。RowColumn 实际上是 Flex 的特例——Row 等价于 Flex({ direction: FlexDirection.Row })Column 等价于 Flex({ direction: FlexDirection.Column })

2.3 Flex 布局的核心概念

要理解 Flex,我们必须先掌握两个轴的概念:

  • 主轴(Main Axis):布局排列的方向。FlexDirection.Row 时主轴为水平方向(从左到右),FlexDirection.Column 时主轴为垂直方向(从上到下)。
  • 交叉轴(Cross Axis):与主轴垂直的方向。Row 时交叉轴为垂直方向,Column 时交叉轴为水平方向。

Flex 布局的所有行为——拉伸、收缩、对齐、间距——都是围绕这两个轴展开的。


三、演示①:默认拉伸(ItemAlign.Stretch)

3.1 现象与原理

运行应用的第一屏,你会看到三个彩色方块并排排列,它们的高度都被拉伸到了容器的高度。这就是 Flex 的第一个默认行为

Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Stretch }) {
  Text('子A\n拉伸')
    .backgroundColor('#FF6B6B')
    .width(80)
    // 没有设置 height
  Text('子B\n拉伸')
    .backgroundColor('#4ECDC4')
    .layoutWeight(1)
    // 没有设置 height
  Text('子C\n拉伸')
    .backgroundColor('#45B7D1')
    .layoutWeight(1)
    // 没有设置 height
}
.width('100%')
.height(100)

关键点在于 alignItems: ItemAlign.Stretch——这是 Flex 的默认值! 也就是说,即使你不写 alignItems,效果也是一样的。Stretch 的含义是:在交叉轴方向上,将子组件拉伸至与容器在该方向上的尺寸一致。

在本例中:

  • 主轴:水平方向(Row)
  • 交叉轴:垂直方向
  • 容器高度:100px
  • 子组件未设 height → 沿垂直方向被拉伸至 100px

3.2 什么时候会被拉伸?

子组件被拉伸需要满足一个关键条件:在交叉轴方向上没有设置固定尺寸。换句话说:

  • height 不设置 → 在 Row 方向 Flex 中被拉伸(沿垂直方向)
  • width 不设置 → 在 Column 方向 Flex 中被拉伸(沿水平方向)

如果子组件设置了固定高度,拉伸就不会生效。这也是演示①中第二组示例所展示的:

Text('子D\n固定\n40px')
  .height(40)   // 固定高度,不被拉伸
Text('子F\n(拉伸)')
  // 不设 height,被拉伸至 100px

运行结果中,子D 只有 40px 高,子F 则充满整个容器——两者形成鲜明对比。

3.3 layoutWeight 的特殊作用

细心的读者可能会注意到,示例中使用了 .layoutWeight(1)。这是 ArkUI 特有的属性,与 CSS Flexbox 中的 flex 属性类似,但作用机制略有不同。layoutWeight 让子组件按权重分配主轴上的剩余空间。当容器宽度超出子组件内容宽度时,设置了 layoutWeight 的子组件会等比例放大。

在本演示中,layoutWeight(1) 让子B 和子C 均分 A 之外的空间,形成三栏等宽(近似)的效果。值得注意的是,layoutWeightflexGrow 都可以实现空间分配,但 layoutWeight 的优先级更高,且不依赖于容器的 Flex 类型——它在 RowColumn 中同样有效。

3.4 实际开发中的意义

ItemAlign.Stretch 在日常开发中应用极为广泛:

  • 列表项:让每个列表项在行方向等高
  • 导航栏:按钮自动填满导航栏高度
  • 卡片布局:卡片内容区域等宽或等高

掌握 Stretch 的触发条件(不设交叉轴尺寸),可以避免很多布局"对不齐"的困扰。


四、演示②:收缩行为(flexShrink)

4.1 现象与原理

切换到第二个演示场景,你会看到三个子组件被放置在一个 宽度只有 300px 的容器中,而每个子组件的宽度被设为 120px,总宽 360px。超出的部分没有被截断或换行,而是自动收缩了。

这就是 Flex 的第二个默认行为flexShrink 的默认值为 1

Flex({ direction: FlexDirection.Row }) {
  Text('子A\n收缩=1').width(120)  // flexShrink 默认=1
  Text('子B\n收缩=2').width(120).flexShrink(2)  // 收缩比是 A/C 的两倍
  Text('子C\n收缩=1').width(120)  // flexShrink 默认=1
}
.width(300)

4.2 收缩值的计算方式

当子组件总宽度超出容器时,多出的宽度会按 flexShrink 值的比例从各子组件中扣减。计算公式如下:

总溢出宽度 = 子组件原始宽度之和 - 容器宽度
             = 360px - 300px = 60px

收缩比例因子 = 子组件的 flexShrink 值 / 所有子组件 flexShrink 总和
             = 各子组件的 flexShrink / (1 + 2 + 1)

子A 收缩量 = 60px × (1/4) = 15px → 最终宽度 = 120px - 15px = 105px
子B 收缩量 = 60px × (2/4) = 30px → 最终宽度 = 120px - 30px = 90px
子C 收缩量 = 60px × (1/4) = 15px → 最终宽度 = 120px - 15px = 105px

可以看到,子B 的 flexShrink=2,收缩量是 A/C 的两倍,所以它被压缩得最多(90px)。这个比例关系在实际运行效果中可以清晰地观察到。

4.3 禁止收缩:flexShrink = 0

在某些场景下,我们不希望子组件被压缩。例如,一个图标按钮的宽度必须保持固定,不能被旁边的文字挤小。这时可以将 flexShrink 设为 0:

Text('关键按钮').width(80).flexShrink(0)

设为 0 后,该子组件将始终维持其设定的宽度,超出部分由其他允许收缩的子组件承担。

4.4 实际开发中的意义

flexShrink 的默认行为(=1)确保了 Flex 布局在空间不足时不会发生内容溢出或布局断裂。这在以下场景中尤为重要:

  • 响应式布局:不同屏幕宽度下,子组件自动适应
  • 动态内容:后端返回的长文本不会撑破容器边界
  • 多语言适配:不同语言的文字长度差异很大,flexShrink 保证了布局稳定性

五、演示③:增长行为(flexGrow)

5.1 现象与原理

第三个演示场景展示了 flexGrow 的效果:子A 固定 80px 宽度,子B 和 子C 分别设置了 flexGrow(1)flexGrow(2)。容器宽度设为 100%,所以存在大量剩余空间——这些空间被 B 和 C 按 1:2 的比例分配了。

Flex({ direction: FlexDirection.Row }) {
  Text('A\n固定\n80px')
    .width(80)
    .flexGrow(0)    // 默认值,不增长

  Text('B\ngrow=1')
    .flexGrow(1)    // 占剩余空间的 1/3

  Text('C\ngrow=2')
    .flexGrow(2)    // 占剩余空间的 2/3
}
.width('100%')

5.2 增长值的计算方式

flexGrow 的计算与 flexShrink 类似,但方向相反:

剩余宽度 = 容器宽度 - 子组件原始宽度之和
               = 360px(假设宽屏) - 80px(A的宽度)= 280px

分配比例 = 各子组件的 flexGrow / flexGrow 总和

B 获得 = 280px × (1/3) ≈ 93px → 最终宽度 ≈ 93px
C 获得 = 280px × (2/3) ≈ 187px → 最终宽度 ≈ 187px

B 的宽度是 C 的一半,与代码中 flexGrow(1)flexGrow(2) 的比值完全对应。

5.3 flexGrow 与 layoutWeight 的区别

特性 flexGrow layoutWeight
适用范围 仅 Flex 容器 Row、Column、Flex 均可
默认值 0(不增长) 未设置
计算基准 内容宽度 + 剩余空间 严格按权重分配
与内容的关系 保留内容空间后分配剩余 直接按权重分配总空间

简单来说,flexGrow 更接近 CSS Flexbox 的行为,保留子组件的基础宽度后再分配剩余空间;而 layoutWeight 则更加"霸道",直接按比例重新分配总宽度。

5.4 实际开发中的意义

flexGrow 是实现自适应布局的核心手段:

  • 搜索栏:输入框 flexGrow=1 填满剩余空间,搜索按钮固定宽度
  • 表格头:各列按权重分配宽度
  • 弹窗内容:内容区填满弹窗扣除标题和按钮后的空间

六、完整代码逐段解析

为了让读者更好地理解整个应用的架构,这里给出完整的代码结构分析:

6.1 整体框架

@Entry
@Component
struct Index {
  build() {
    Column() {
      // ... 标题 ...
      // ... 演示①:默认拉伸 ...
      // ... 演示②:收缩行为 ...
      // ... 演示③:增长行为 ...
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
  }
}
  • @Entry:标记该组件为应用的入口页面
  • @Component:声明这是一个 ArkTS 组件
  • struct Index:采用结构体(struct)而非类(class)定义,这是 ArkTS 的推荐方式
  • build():描述 UI 结构的核心方法,不允许有副作用

6.2 色彩搭配

示例中使用了四组颜色,分别对应不同的语义:

颜色 色值 含义
珊瑚红 #FF6B6B 第一个子组件
碧蓝绿 #4ECDC4 第二个子组件
天蓝 #45B7D1 第三个子组件
暖黄 #FDCB6E 特殊的固定组件

这些色彩来自扁平化配色方案,在深色文字背景下具有较高的辨识度。

6.3 布局层次

整个页面的布局层次如下:

Column(根容器,100% × 100%,灰底)
├── Text(标题:Flex 布局:子组件拉伸与收缩)
├── Text(演示①标题)
├── Text(演示①说明)
├── Flex(演示①容器:300px × 100px)
│   ├── Text(子A:红底,80px 宽,无高度→拉伸)
│   ├── Text(子B:绿底,layoutWeight=1,无高度→拉伸)
│   └── Text(子C:蓝底,layoutWeight=1,无高度→拉伸)
├── Text(演示②标题)
├── Text(演示②说明)
├── Flex(演示②容器:300px × 100px)
│   ├── Text(子A:120px,flexShrink=1)
│   ├── Text(子B:120px,flexShrink=2)
│   └── Text(子C:120px,flexShrink=1)
├── Text(演示③标题)
├── Text(演示③说明)
└── Flex(演示③容器:100% × 100px)
    ├── Text(A:80px 固定,flexGrow=0)
    ├── Text(B:flexGrow=1)
    └── Text(C:flexGrow=2)

根节点使用 Column,三个演示场景纵向排列,每个场景内部的子组件通过 Flex 横向排列。这种"嵌套"结构是 ArkUI 布局的典型写法。

6.4 关于 .ets 文件

.ets 是 ArkTS 源文件的扩展名(Extension TypeScript)。在 HarmonyOS NEXT 项目中,页面文件通常放在 entry/src/main/ets/pages/ 目录下,通过 @Entry 装饰器标注为可路由的页面。


七、flexGrow、flexShrink 与 Stretch 的协同工作

在实际项目中,这三种行为很少单独出现,它们通常是同时作用的。理解它们的协同关系,才能真正掌握 Flex 布局。

7.1 同时生效的场景

假设一个 Flex 容器宽度为 400px,高度为 120px,包含三个子组件:

子A:宽度 150px,高度不设,flexGrow=1,flexShrink=1
子B:宽度 100px,高度不设,flexGrow=0,flexShrink=2
子C:宽度 200px,高度不设,flexGrow=2,flexShrink=1

分析各子组件的最终尺寸:

  1. 拉伸(alignItems=Stretch,默认):三个子组件的高度都被拉伸至 120px
  2. 收缩:总宽度 450px > 400px,超出 50px。flexShrink 总和 = 1+2+1=4
    • 子A 收缩 50×(1/4)=12.5px → 137.5px
    • 子B 收缩 50×(2/4)=25px → 75px
    • 子C 收缩 50×(1/4)=12.5px → 187.5px
  3. 增长:此例中总宽超出,不留剩余空间,故 flexGrow 不生效

Flex 布局的处理优先级是:先确定基础尺寸 → 收缩至容器内(如需要)→ 分配剩余空间(如有剩余)。收缩和增长不会同时发生——收缩只在空间不足时有效,增长只在空间有余时有效。

7.2 Column 方向下的表现

前面的示例都是在 FlexDirection.Row(水平排列)下进行的。如果改为 FlexDirection.Column(垂直排列),轴的角色会互换:

  • 主轴:垂直方向
  • 交叉轴:水平方向
  • Stretch:水平拉伸子组件至容器宽度
  • flexShrink / flexGrow:在垂直方向上收缩/增长

综合场景中展示了一段 Column 方向的 Flex:

Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Stretch }) {
  Text('固定高 50px').height(50)       // 固定高度,不被拉伸
  Text('增长=1').flexGrow(1)            // 在垂直方向增长
  Text('增长=2').flexGrow(2)            // 在垂直方向增长(两倍)
}
.width('100%')
.height(180)

这里,子组件在水平方向上被拉伸填满容器宽度,同时在垂直方向上按 flexGrow 比例分配剩余高度。这种"交叉轴拉伸 + 主轴弹性"的组合,是实现自适应布局的常用手法。


八、Flex 布局常见陷阱与避坑指南

8.1 陷阱一:显式设置交叉轴尺寸导致拉伸失效

// ❌ 错误:设了 height,不会被拉伸
Text('子组件').height(50)

// ✅ 正确:不设 height,或者使用 .height('auto')
Text('子组件')

Row 方向的 Flex 中,height('auto') 表示"自动计算高度",在 Stretch 作用下会被覆盖为容器高度。而 height(50) 表示"固定 50px",Stretch 不会覆盖固定值。

8.2 陷阱二:flexShrink = 0 但依然溢出

Text('很长很长的文字').width(300).flexShrink(0)

如果 Text 内容本身有最小宽度限制(比如连续无断点的英文字符串),即使设置了 flexShrink(0) 也可能无法压缩到理想的宽度。这时需要配合 textOverflowmaxLines 属性来实现文字截断。

8.3 陷阱三:flexGrow 与 layoutWeight 混用

两者都用于空间分配,但计算方式不同。混用时可能导致意料之外的宽度分配。建议在同一个 Flex 容器中只使用其中一种

8.4 陷阱四:忽视容器的尺寸约束

Flex 的弹性行为是以容器的尺寸为基准的。如果容器的父级没有明确的尺寸约束(比如父级也是 auto 高度),Flex 容器的尺寸可能不符合预期。建议始终为 Flex 容器设置明确的 widthheight,或者使用 .width('100%').layoutWeight(1) 让容器在父级中占据确定的空间。


九、进阶:使用 Flex 构建真实页面

掌握了 Flex 的三大默认行为后,我们可以用它构建各种常见的 UI 布局。下面是一个简单的示范:

9.1 自适应搜索栏

Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center }) {
  TextInput({ placeholder: '搜索...' })
    .flexShrink(1)    // 空间不足时收缩
    .layoutWeight(1)  // 优先填满剩余空间

  Button('搜索')
    .width(64)
    .flexShrink(0)    // 固定宽度,绝不收缩
}
.width('100%')
.height(40)

9.2 等分布局(三栏)

Flex({ direction: FlexDirection.Row }) {
  Text('左侧').layoutWeight(1)
  Text('中间').layoutWeight(1)
  Text('右侧').layoutWeight(1)
}
.width('100%')

9.3 底部固定 + 内容自适应

Column() {
  Flex({ direction: FlexDirection.Column }) {
    // 主内容区:增长填满剩余空间
    Text('内容区域').flexGrow(1).layoutWeight(1)
    // 底部按钮:固定高度
    Button('确认').height(48).flexShrink(0)
  }
  .width('100%')
  .height('100%')
}
.width('100%')
.height('100%')

十、总结

本文通过一个完整的 ArkTS 示例应用,深入剖析了 Flex 布局的三大默认行为:

默认行为 属性 默认值 作用条件
拉伸 alignItems ItemAlign.Stretch 子组件未设交叉轴尺寸
收缩 flexShrink 1 子组件总尺寸超出容器
增长 flexGrow 0 子组件总尺寸小于容器

这三个行为共同构成了 Flex 布局的"弹性"基础。理解它们,就等于掌握了 ArkUI 布局最核心的能力。在实际开发中,我们往往不需要显式设置这些属性——Flex 的默认值已经覆盖了大部分常见场景的需求。这就是为什么我们说"Flex 默认行为"如此重要:用最少的代码,实现最自然的弹性效果。

最后,给读者几点建议:

  1. 先理解主轴和交叉轴的关系,再学习具体属性
  2. 区分 Stretch(沿交叉轴拉伸)和 flexGrow(沿主轴增长),两者作用方向不同
  3. 多用 .layoutWeight(1) 实现水平等分,它比 flexGrow 更直观
  4. 在真机或模拟器中调试布局,勾选 DevEco Studio 的 Inspector 面板查看布局线框

Flex 布局的弹性哲学,本质上是让 UI 去适应容器,而非让容器去适应 UI。这种"约束自适配"的思想,与鸿蒙"万物互联、多端协同"的理念不谋而合。掌握 Flex,你就掌握了鸿蒙 UI 开发的半壁江山。


本文配套完整示例代码可在鸿蒙项目 entry/src/main/ets/pages/Index.ets 中查看,基于 HarmonyOS NEXT API 24 开发。

作者:AtomCode(deepseek-v4-flash)

日期:2025年

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐