【共创季稿事节】鸿蒙原生 ArkTS 布局之道:Flex 实现垂直居中 —— 最简洁的居中方案

鸿蒙原生 ArkTS 布局之道:Flex 实现垂直居中 ------ 最简洁的居中方案


一、引言:一个老问题的新解法

在前端与客户端开发中,"将一个元素在父容器内水平和垂直双向居中"堪称最经典也最折磨人的布局需求之一。在 Web 端,我们经历过 margin: 0 auto 配合 line-height 的奇技淫巧,见证了 display: flex 配合 justify-content: center 与 align-items: center 如何用两行代码终结了这场长达二十年的"居中战争"。如今,在鸿蒙原生 ArkTS 开发中,历史惊人相似地重演了。

HarmonyOS NEXT API 24 作为鸿蒙生态的最新里程碑,对 ArkUI 声明式框架进行了大量优化。其中,Flex 弹性布局接口迎来了重要更新:justifyContent 与 alignItems 被正式纳入 FlexOptions 构造参数 ,使开发者可以在创建 Flex 容器的瞬间一次性声明主次轴的排列策略,无需在链式调用中二次追加。

本文将以一个完整可运行的 .ets 文件为例,深度拆解这一"一行代码 + 一行 alignItems 实现完美居中"的极简方案,并揭示其背后的布局原理、API 演进脉络以及工程实践中的最佳抉择。


二、代码先行:完整示例速览

以下代码基于 API 24 编写,已通过 hvigorw assembleHap 编译验证。删除原有 pages/Index.ets 内容并替换为本代码,即可在模拟器或真机上看到效果。

typescript 复制代码
/**
 * 示例:Flex 实现垂直居中 ------ 最简洁的居中方案
 *
 * 核心技术:
 *   - Flex:弹性布局容器,默认主轴为水平方向(row)
 *   - justifyContent(FlexAlign.Center):主轴(水平)居中
 *   - alignItems(ItemAlign.Center):交叉轴(垂直)居中
 *
 * 一句话总结:
 *   只需在 Flex 容器上设置 justifyContent 与 alignItems 两个属性,
 *   即可实现子组件的水平和垂直双向完美居中,无需计算偏移量。
 */

@Entry
@Component
struct Index {
  build() {
    // 【核心】Flex 弹性容器,居中参数通过 FlexOptions 构造器传入
    Flex({
      justifyContent: FlexAlign.Center,
      alignItems: ItemAlign.Center
    }) {
      // 被居中的内容卡片
      Column() {
        Text('\u{1F3AF}')          // 靶心 Emoji
          .fontSize(48)

        Text('完美居中')
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
          .fontColor('#FF6C6C6C')
          .margin({ top: 12 })

        Text('Flex + justifyContent + alignItems')
          .fontSize(14)
          .fontColor('#FF999999')
          .margin({ top: 8 })

        Divider()
          .width('60%')
          .color('#FFE0E0E0')
          .margin({ top: 16, bottom: 16 })

        Text('\u300C\u4E00\u884C\u4EE3\u7801 + alignItems \u5B9E\u73B0\u5B8C\u7F8E\u5C45\u4E2D\u300D')
          .fontSize(13)
          .fontColor('#FF333333')
          .textAlign(TextAlign.Center)
          .lineHeight(20)

        Text('\u2022 justifyContent(FlexAlign.Center)  \u2192 \u6C34\u5E73\u5C45\u4E2D')
          .fontSize(12)
          .fontColor('#FF666666')
          .margin({ top: 8 })

        Text('\u2022 alignItems(ItemAlign.Center)      \u2192 \u5782\u76F4\u5C45\u4E2D')
          .fontSize(12)
          .fontColor('#FF666666')
          .margin({ top: 4 })
      }
      .width('85%')
      .padding(24)
      .borderRadius(16)
      .backgroundColor('#FFF8F8FF')
      .shadow({
        radius: 12,
        color: '#33000000',
        offsetX: 0,
        offsetY: 4
      })
      .alignItems(HorizontalAlign.Center)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#FFE8F0FE')
  }
}

运行效果:一张柔蓝色背景铺满屏幕,一张带有柔和阴影的白色卡片精准悬浮在屏幕正中央,卡片内以优雅的排版展示技术要点说明。


三、逐行拆解:FlexOptions 构造参数详解

3.1 Flex 构造器签名变迁

在 API 24 中,Flex 组件支持两种使用形态:

形态 写法 适用场景
构造参数式 Flex({ justifyContent, alignItems, ... }) { content } 布局参数固定不变,声明即定型
链式属性式 Flex() { content }.justifyContent(...).alignItems(...) 需按条件动态切换布局参数

API 24 推荐优先使用 构造参数式,理由有三:

  1. 编译期类型检查更严格 :FlexOptions 接口对每个字段有精确的类型约束,IDE 可以在编码阶段捕获拼写错误或类型不匹配。
  2. 性能微优化:构造参数在组件创建阶段一次性解析,避免了链式调用可能触发的多次属性合并开销(尽管在实际场景中差异可以忽略不计)。
  3. 语义自文档化 :Flex({ justifyContent: ..., alignItems: ... }) 读起来就像是"创建一个弹性容器,其主轴居中对齐、交叉轴居中对齐",布局意图一目了然。

3.2 justifyContent 的完整枚举

justifyContent 接受 FlexAlign 枚举,该枚举在 API 24 中定义了 六种取值:

枚举值 行为描述
FlexAlign.Start 子元素紧贴主轴起点排列(默认值)
FlexAlign.Center 子元素在主轴上居中对齐
FlexAlign.End 子元素紧贴主轴终点排列
FlexAlign.SpaceBetween 首尾元素贴边,剩余空间均匀分布在元素之间
FlexAlign.SpaceAround 每个元素两侧空间相等,首尾空间为间隔的一半
FlexAlign.SpaceEvenly 所有元素之间及首尾的空间完全相等

当 flexDirection 为默认值 FlexDirection.Row 时,主轴为 水平方向 ,因此 FlexAlign.Center 实现了水平居中。

3.3 alignItems 的完整枚举

alignItems 接受 ItemAlign 枚举,在 API 24 中定义了 五种取值:

枚举值 行为描述
ItemAlign.Start 子元素紧贴交叉轴起点(默认值)
ItemAlign.Center 子元素在交叉轴上居中对齐
ItemAlign.End 子元素紧贴交叉轴终点
ItemAlign.Stretch 子元素沿交叉轴方向拉伸至容器尺寸(要求子元素未显式设置该维度尺寸)
ItemAlign.Baseline 子元素按文本基线对齐

当 Flex 默认水平排列时,交叉轴为 垂直方向 ,因此 ItemAlign.Center 实现了垂直居中。

3.4 两属性协同的几何逻辑

Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) 的联合效果可以用一句话概括:"将唯一的子元素(或所有子元素形成的整体)同时置于主轴的中央和交叉轴的中央。"

其底层逻辑如下:

  1. Flex 首先沿主轴(水平方向)分配空间,Center 值将子元素推至中间位置;
  2. 然后沿交叉轴(垂直方向)分配空间,Center 值再次将子元素推至中间位置;
  3. 两次定位叠加的结果就是子元素被精确地固定在父容器的几何中心。

这种逻辑的优雅之处在于它是 声明式 的------你只需要告诉框架"我要居中",而不需要关心父容器尺寸变化、子元素尺寸变化等动态因素。布局引擎会自动响应所有变化。


四、对比分析:为什么 Flex 是最简洁的居中方案?

在 ArkUI 框架中,实现居中远不止一种方式。让我们逐一对比,看看为什么 Flex 方案能从众多候选中脱颖而出。

4.1 方案一:RelativeContainer + alignRules

typescript 复制代码
RelativeContainer() {
  Text('居中').alignRules({
    center: { anchor: '__container__', align: VerticalAlign.Center },
    middle: { anchor: '__container__', align: HorizontalAlign.Center }
  })
}
.height('100%').width('100%')

缺点:

  • 每行代码都需要同时指定 center 和 middle,否则无法实现双向居中
  • 需要理解 __container__ 锚点语义和 alignRules 的复杂语法
  • 当子元素数量增多时,每个子元素都需要独立的 alignRules 声明
  • 代码冗长,嵌套层级深,不易阅读维护

4.2 方案二:Stack + position

typescript 复制代码
Stack() {
  Text('居中')
    .position({ x: '50%', y: '50%' })
    .translate({ x: '-50%', y: '-50%' })
}
.height('100%').width('100%')

缺点:

  • 需要手动计算偏移量,且使用百分比偏移时依赖子元素自身的尺寸
  • position + translate 组合写起来不够直观,调试时需要同时考虑两个属性
  • 不适合动态尺寸场景,子元素尺寸变化后需重新计算偏移

4.3 方案三:Column + Row 嵌套

typescript 复制代码
Column() {
  Row() {
    Text('居中')
  }
  .width('100%')
  .justifyContent(FlexAlign.Center)
}
.height('100%')
.width('100%')
.justifyContent(FlexAlign.Center)

缺点:

  • 引入了一层多余的 Row 容器,增加了虚拟节点的数量
  • 代码结构冗长,明明是居中一个元素却需要两层容器嵌套
  • 布局语义模糊,阅读代码时需要从两个方向分别理解

4.4 方案四:Flex 构造参数式(本文推荐)

typescript 复制代码
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
  Text('居中')
}
.height('100%').width('100%')

优点:

  • 单容器无嵌套,节点树最扁平
  • 一行声明完成双向居中,代码量最少
  • 语义自明:看到 justifyContent 和 alignItems 就知道布局意图是居中
  • 子元素数量无限制:一个子元素居中,多个子元素也居中(作为整体居中)
  • 自适应:父容器尺寸变化时自动重新计算,无需任何手动干预
  • 与 Web Flexbox 概念对标,降低 Web 开发者迁移成本

4.5 综合评分表

维度 RelativeContainer Stack + 偏移 Column+Row 嵌套 Flex(推荐)
代码行数 7 ~ 9 5 ~ 6 6 ~ 7 3 ~ 4
容器嵌套层数 1 1 2 1
手动偏移计算 否 是 否 否
语义清晰度 ★★★ ★★ ★★★ ★★★★★
动态尺寸适应 ★★★★ ★★ ★★★★ ★★★★★
Web 开发者友好 ★★ ★ ★★★ ★★★★★

五、API 24 新增特性与最佳实践

5.1 与之前 API 版本的关键差异

API 24(SDK 7.0.0)相比 API 12~23 在 Flex 布局方面有若干重要变化:

  1. FlexOptions 构造参数成为一等公民 :此前 justifyContent 和 alignItems 仅能通过链式方法设置,API 24 强化了构造参数形式并推荐为首选写法。
  2. FlexAlign 枚举值精简与规范化 :去除了历史遗留的冗余别名,所有枚举值统一采用帕斯卡命名(如 FlexAlign.Center),不再支持旧版的大小写变体。
  3. ItemAlign 枚举新增 Baseline 值 :为文本混排场景提供了精确的对齐控制,此前该能力仅存在于 Row 和 Column 组件中。
  4. 编译期验证增强 :FlexOptions 中的误写字段(如 justifyContent: 'center' 字符串而非 FlexAlign.Center 枚举)会直接报编译错误,而非运行时静默忽略。

5.2 常见踩坑与避坑指南

坑一:误以为 justifyContent 可以独立决定垂直居中

许多从 CSS 背景迁移过来的开发者会习惯性地认为 justifyContent 就是"居中"的全部。在默认 FlexDirection.Row 下,justifyContent 仅控制水平方向。要实现垂直居中,必须同时 设置 alignItems: ItemAlign.Center。

正确做法:

typescript 复制代码
// 双向居中,二者缺一不可
Flex({
  justifyContent: FlexAlign.Center,  // 水平
  alignItems: ItemAlign.Center       // 垂直
})
坑二:在 Column 子节点内部误用 alignItems

注意卡片内部的 .alignItems(HorizontalAlign.Center) 与卡片外部 Flex 上的 .alignItems(ItemAlign.Center) 是两类不同的 API:

  • Flex().alignItems(ItemAlign.Center) ------ 作用于 flex 容器,控制所有 flex 子项在交叉轴上的对齐方式
  • Column().alignItems(HorizontalAlign.Center) ------ 作用于 Column 容器,控制其子项在水平方向上的对齐方式

两者名称相同但类型不同,使用时务必匹配正确的容器类型和枚举类型,否则编译会报错。

坑三:Flex 容器高度未设置导致视觉上未居中

如果父容器没有显式设置高度(或 height('100%')),而它的父级也没有固定高度,那么 Flex 容器自身的尺寸取决于子元素,此时 alignItems: ItemAlign.Center 的效果将不可见------因为交叉轴的实际可用空间等于子元素高度,无所谓"居中"可言。

黄金法则 :在使用 Flex 进行居中布局时,务必确保 Flex 容器的 width 和 height 都有明确的数值或百分比。

typescript 复制代码
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center })
  .width('100%')    // 必须有
  .height('100%')   // 必须有

5.3 进阶:多子元素居中与间距控制

本示例中 Flex 只有一个子元素(Column 卡片),但 Flex 的居中能力在多个子元素场景下同样强大:

typescript 复制代码
Flex({
  justifyContent: FlexAlign.SpaceEvenly,
  alignItems: ItemAlign.Center
}) {
  Button('左').width(80)
  Button('中').width(80)
  Button('右').width(80)
}
.width('100%').height(200)

上述代码实现了三个按钮水平均匀分布且垂直居中的效果,仅靠构造参数中的两个属性就完成了布局,不需要任何额外的容器嵌套。

如果需要子元素换行排列,可以额外指定 wrap: FlexWrap.Wrap:

typescript 复制代码
Flex({
  justifyContent: FlexAlign.Center,
  alignItems: ItemAlign.Center,
  wrap: FlexWrap.Wrap
}) {
  // 动态生成的多个子组件
}

在 API 24 中,FlexOptions 支持的全部字段包括:

字段名 类型 说明
direction FlexDirection 主轴方向(Row / Column / RowReverse / ColumnReverse)
wrap FlexWrap 是否换行(NoWrap / Wrap / WrapReverse)
justifyContent FlexAlign 主轴对齐方式
alignItems ItemAlign 交叉轴对齐方式
alignContent FlexAlign 多行时的行间对齐方式(仅在 wrap 启用时有效)

六、工程实战:在真实项目中应用 Flex 居中

typescript 复制代码
@Entry
@Component
struct LoginPage {
  build() {
    Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
      Column() {
        Image($r('app.media.logo')).width(120).height(120)
        TextInput({ placeholder: '请输入账号' }).margin({ top: 24 })
        TextInput({ placeholder: '请输入密码' }).margin({ top: 12 })
        Button('登录').width('100%').margin({ top: 24 })
      }
      .width('85%')
    }
    .width('100%').height('100%')
    .backgroundColor('#FFFFFF')
  }
}

6.2 加载中状态全屏居中

typescript 复制代码
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
  Column() {
    LoadingProgress().width(48).height(48)
    Text('加载中...')
      .fontSize(14)
      .fontColor('#FF666666')
      .margin({ top: 16 })
  }
}
.width('100%').height('100%')
.backgroundColor('#80FFFFFF')

6.3 空状态提示居中

typescript 复制代码
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
  Column() {
    Image($r('app.media.empty_icon')).width(80).height(80)
    Text('暂无数据').fontSize(16).fontColor('#FF999999').margin({ top: 16 })
  }
}
.width('100%').height('100%')

以上三个场景的共同模式都是:外层 Flex 负责双向居中,内层 Column 负责垂直排列内容。这种"Flex + Column"的组合是 ArkTS 布局中最常用、最稳定的居中范式。


七、总结与展望

7.1 核心要点回顾

  1. Flex 是 HarmonyOS NEXT 中最推荐的全屏居中容器 ,相比 RelativeContainer 和 Stack,它在代码简洁性、语义清晰度和动态适应能力上全面胜出。
  2. justifyContent 控制主轴居中,alignItems 控制交叉轴居中,两者组合实现双向居中,缺一不可。
  3. API 24 推荐使用构造参数式 Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }),类型安全且语义自明。
  4. 务必为 Flex 容器设置明确的宽高,否则居中无法生效。

7.2 从 API 24 展望未来

随着鸿蒙生态的持续演进,ArkUI 布局方案正在向更声明化、更类型安全的方向发展。API 24 对 FlexOptions 构造参数的强化只是一个开始。可以预见,未来的 API 版本可能会:

  • 引入更丰富的布局组合约束,减少显式容器嵌套;
  • 增强编译期布局语义检查,在编码阶段发现潜在的面板溢出或重叠问题;
  • 与 @Builder、@BuilderParam 等组件化方案更深度的融合,使布局代码的复用达到新的高度。

作为开发者,掌握 Flex 布局------这个既古老又现代的布局模型------将为你在鸿蒙世界中的 UI 开发之路打下最坚实的基础。


附录:API 24 下完整项目结构参考

复制代码
app6201/
├── entry/
│   ├── src/main/ets/
│   │   ├── entryability/EntryAbility.ets
│   │   └── pages/
│   │       └── Index.ets          ← 本文所有代码存放位置
│   ├── src/main/resources/
│   │   └── base/profile/
│   │       └── main_pages.json     ← 路由配置,指向 pages/Index
│   └── build-profile.json5
├── build-profile.json5             ← compatibleSdkVersion: "7.0.0(24)"
├── hvigor/hvigor-config.json5
└── oh-package.json5

本文配套示例代码已通过 hvigorw --mode module -p module=entry -p product=default assembleHap 编译验证,你可以在 DevEco Studio NEXT(API 24)中直接打开项目并运行查看效果。