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

1. 引言:从鸿蒙布局体系说起

1.1 鸿蒙 ArkUI 布局引擎简介

HarmonyOS NEXT 自 API 12(对应 SDK 6.x 系列)开始全面转向原生 ArkTS 生态,移除了对 Android 兼容层的依赖,布局引擎使用自研的 ArkUI 框架。ArkUI 采用声明式 UI 范式——开发者通过链式调用描述「界面应该长什么样」,框架负责底层的测量、布局和渲染。

ArkUI 的布局系统深受 Flexbox(弹性盒模型)启发,但做了鸿蒙原生的简化和增强。核心布局容器有三种:

容器 主轴方向 适用场景
Column 垂直(从上到下) 列表、表单、纵向信息流
Row 水平(从左到右) 导航栏、按钮组、标签行
Flex 可自定义方向 复杂弹性布局

这三种容器共享同一套布局属性体系——justifyContent(主轴排列)、alignItems(交叉轴对齐)、alignContent(多行对齐),理解其中一种就能举一反三。

1.2 为什么选择 Column + Center 作为切入点

在纵向布局中,「居中」是最常用的需求之一:弹窗居中、加载状态居中、登录表单居中、空状态提示居中…… 然而,很多开发者对 FlexAlign.Center 的理解停留在「让元素居中」这个表面层次,不清楚它与其他居中方式(如 FlexAlign.SpaceBetween + margin 微调)的本质区别。

本文以 Column + justifyContent(FlexAlign.Center) 为主线,由浅入深,逐行解析完整示例代码,并对比全部六种 FlexAlign 模式,帮助读者彻底掌握鸿蒙 ArkUI 主轴布局。

1.3 本文面向的读者

  • 刚接触鸿蒙 ArkTS 开发,想系统学习布局体系的初学者
  • 有 Android(LinearLayout / ConstraintLayout)或 iOS(Auto Layout)背景,正迁移到鸿蒙的开发者
  • 已经写过一些 ArkUI 页面,但对布局属性理解不够深入的进阶开发者
  • 需要一份可复用的布局示例代码的工程团队

2. Column 容器全面解读

2.1 Column 的定义与基本语法

Column 是 ArkUI 中最基础的纵向布局容器。它的子组件沿着 垂直方向(主轴) 依次排列,子组件的宽度默认撑满容器(除非显式设置 width)。

基本语法:

Column() {
  // 子组件列表
  Text('第一个组件')
  Button('第二个组件')
  Image({ src: $r('app.media.icon') })
}
// 链式布局属性
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
.width('100%')
.height('100%')
.backgroundColor('#f5f5f5')

2.2 Column 的构造函数

查阅 HarmonyOS NEXT API 24 的官方 API 参考,Column 的构造函数签名如下:

interface ColumnConstructor {
  (options?: ColumnOptions): ColumnAttribute;
}

interface ColumnOptions {
  space?: string | number;  // 子组件之间的间距
}

关键点:

  • space 参数可以直接在构造函数中传入,用于设置子组件之间的间距
  • 例如 Column({ space: 12 }) 等价于在每个子组件上手动设置 .margin({ bottom: 12 })
  • 使用 space 更简洁,且不会影响第一个和最后一个子组件的外边距
// 推荐写法:使用 space 统一管理间距
Column({ space: 16 }) {
  Text('项目一')
  Text('项目二')
  Text('项目三')
}
// 等价于手写 margin
Column() {
  Text('项目一')
  Text('项目二').margin({ top: 16 })
  Text('项目三').margin({ top: 16 })
}

2.3 Column 的核心布局属性一览

属性 作用维度 参数类型 功能简述
justifyContent 主轴(垂直) FlexAlign 子组件在垂直方向的排列方式
alignItems 交叉轴(水平) HorizontalAlign 子组件在水平方向的对齐方式
alignContent 多行主轴 FlexAlign 多行时整行在主轴的对齐(需 flexWrap)
space 子组件间距 number | string 子组件之间的固定间距(类似 gap)
width 容器尺寸 Length 容器宽度
height 容器尺寸 Length 容器高度
layoutWeight 权重分配 number | string 在父容器剩余空间中按权重分配尺寸
padding 内边距 Padding | number 容器内容区与边框的距离
margin 外边距 Margin | number 容器与兄弟组件的距离
constraintSize 约束范围 ConstraintSizeOptions 限制容器的最大/最小宽高

3. 主轴与交叉轴:理解 Flex 布局核心概念

3.1 什么是主轴(Main Axis)

在 ArkUI 的弹性布局体系中,主轴 是子组件排列所沿的方向。

  • 对于 Column:主轴 = 垂直方向(从上到下),英文 main axis 的方向是 vertical
  • 对于 Row:主轴 = 水平方向(从左到右),英文 main axis 的方向是 horizontal

justifyContent 这个属性的名称非常好记:justify(对齐)Content(内容)沿着主轴(Main Axis)

3.2 什么是交叉轴(Cross Axis)

交叉轴 是与主轴垂直的方向。

  • 对于 Column:交叉轴 = 水平方向(从左到右)
  • 对于 Row:交叉轴 = 垂直方向(从上到下)

alignItems 控制子组件在交叉轴上的对齐方式。

3.3 图示理解


             ┌─── 主轴方向(垂直)───┐
             │                       │
             │   ┌───────────────┐   │
             │   │   子组件 1     │   │
             │   └───────────────┘   │
             │                       │
             │   ┌───────────────┐   │
     交叉轴 ──┼──│   子组件 2     │──┼── 交叉轴
     (水平) │   └───────────────┘   │ (水平)
             │                       │
             │   ┌───────────────┐   │
             │   │   子组件 3     │   │
             │   └───────────────┘   │
             │                       │
             └─── 主轴方向(垂直)───┘

3.4 主轴尺寸与子组件尺寸的关系

一个容易被忽视的关键点:justifyContent 在有剩余空间(剩余主轴尺寸)时才产生视觉效果

  • 如果子组件的总高度正好等于容器高度,FlexAlign.Center 看起来和 FlexAlign.Start 没有区别
  • 只有当容器高度 > 子组件总高度时,居中的效果才显现

这就是为什么在示例代码中,我们把 Column 的高度设为 height(0) + layoutWeight(1),确保它占据屏幕剩余空间,从而产生足够的「留白」来展示居中效果:

Column() {
  // 子组件内容
}
.justifyContent(FlexAlign.Center)
.width('100%')
.height(0)        // 高度交由 layoutWeight 分配
.layoutWeight(1)  // 占满父容器剩余空间 → 产生足够的上下留白

4. justifyContent 属性详解

4.1 属性签名

interface ColumnAttribute {
  justifyContent(value: FlexAlign): ColumnAttribute;
}

FlexAlign 是一个枚举类型,定义了子组件在主轴方向上的排列策略。

4.2 FlexAlign 枚举值完整列表

枚举值 行为描述 空间分配方式
FlexAlign.Start 从主轴起始位置开始排列 所有剩余空间在尾部
FlexAlign.Center 整体居中 剩余空间平分在首尾
FlexAlign.End 从主轴结束位置开始排列 所有剩余空间在首部
FlexAlign.SpaceBetween 首尾贴边,中间均匀分布 剩余空间插在子组件之间
FlexAlign.SpaceAround 每个子组件两侧间距相等 首尾间距 = 中间间距的一半
FlexAlign.SpaceEvenly 所有间距(含首尾)完全相等 所有间距值相等

4.3 视觉对比效果

假设有一个高度为 400 的 Column 容器,包含 3 个高度为 60 的子组件(总高度 180,剩余空间 220):

Start:       [组件1] [组件2] [组件3] ............. (空白在底部)
Center:      ..... [组件1] [组件2] [组件3] ..... (空白在上下)
End:         ............. [组件1] [组件2] [组件3] (空白在顶部)
SpaceBetween: [组件1] .... [组件2] .... [组件3] (首尾贴边)
SpaceAround:  ...[组件1]...[组件2]...[组件3]... (首尾间距 = 中间/2)
SpaceEvenly: ..[组件1]..[组件2]..[组件3].. (所有间距相等)

4.4 属性设置的链式调用位置

在 ArkUI 中,布局属性的设置必须在 build() 方法的组件闭包之后链式调用。注意:布局属性只能在组件作用域内设置,不能提取到变量中复用(除非使用 @Styles@Extend 修饰器)。

// ✅ 正确:链式调用
Column() {
  Text('内容')
}
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)

// ❌ 错误:不能在闭包内设置
Column() {
  Text('内容')
  this.justifyContent(FlexAlign.Center)  // 编译错误
}

5. FlexAlign.Center 深度剖析

5.1 工作原理

FlexAlign.Center 的布局计算分三步:

  1. 测量阶段:框架遍历所有子组件,计算其在主轴方向上的总尺寸(总高度)
  2. 计算剩余空间:剩余空间 = 容器内容区高度 - 子组件总高度 - 固定间距(space)
  3. 分配阶段:将剩余空间平分成两份,一份放在子组件起始位置(顶部留白),一份放在结束位置(底部留白)

数学表达:

topPadding = (containerHeight - totalChildrenHeight - totalSpacing) / 2
bottomPadding = (containerHeight - totalChildrenHeight - totalSpacing) / 2

5.2 Center 与 SpaceEvenly 的区别

这是初学者最容易混淆的地方。

  • FlexAlign.Center:子组件之间没有额外间距,子组件作为一个整体在容器中居中。子组件之间的间距只来自构造函数中的 space 参数或手动设置的 margin
  • FlexAlign.SpaceEvenly:除了首尾间距外,子组件之间的间距也被强制均匀分配。即使子组件之间设置了 marginSpaceEvenly 会重新计算间距来填充剩余空间。

举例说明:

// Center:子组件之间用 space 控制间距
Column({ space: 8 }) {
  Text('A')
  Text('B')
  Text('C')
}
.justifyContent(FlexAlign.Center)
// 结果:A、B、C 紧挨着(间距 8),整体居中

// SpaceEvenly:所有间距强制相等
Column() {
  Text('A')
  Text('B')
  Text('C')
}
.justifyContent(FlexAlign.SpaceEvenly)
// 结果:A、B、C 之间的间距与首尾间距完全相等

5.3 Center 在实际场景中的表现

当一个 Column 容器使用 FlexAlign.Center 时,子组件之间的相对顺序保持不变,子组件的排列顺序就是它们在闭包中声明的顺序。这是与某些流式布局的关键区别——FlexAlign.Center 不会重新排序子组件。

Column(FlexAlign.Center) + 子组件顺序:[A, B, C]

结果:
┌─────────────┐
│    (留白)    │  ← 顶部留白 = 总留白 / 2
│      A       │
│      B       │  ← 子组件顺序不变,间距由 space 控制
│      C       │
│    (留白)    │  ← 底部留白 = 总留白 / 2
└─────────────┘

6. 完整示例代码逐行解析

6.1 项目文件结构

entry/src/main/ets/pages/
├── Index.ets             // 主页(可选入口)
└── ColumnCenterDemo.ets  // ★ 本文核心示例文件

路由注册文件:

entry/src/main/resources/base/profile/main_pages.json

6.2 路由注册

{
  "src": [
    "pages/Index",
    "pages/ColumnCenterDemo"
  ]
}

main_pages.json 中添加 "pages/ColumnCenterDemo" 后,该页面即成为可路由目标。开发者可以通过以下方式跳转:

router.pushUrl({
  url: 'pages/ColumnCenterDemo'
});

或使用 Navigation 组件进行声明式导航。

6.3 头部注释与导入

/**
 * ============================================================
 *  鸿蒙原生 ArkTS 布局示例 — Column + justifyContent(FlexAlign.Center)
 *  功能:演示 Column 主轴(垂直方向)居中分布布局
 *        所有子组件作为一个整体在容器垂直方向居中排列
 *  场景:居中弹窗 / 登录页 / 加载状态 / 垂直居中卡券
 *  核心技术:
 *    - Column 容器(主轴:垂直方向)
 *    - justifyContent(FlexAlign.Center) — 子组件在主轴垂直居中分布
 *    - alignItems(HorizontalAlign.Center) — 子组件在交叉轴(水平)居中对齐(辅助)
 * ============================================================
 */

// ── 导入 ArkUI 基础能力 ──
import { hilog } from '@kit.PerformanceAnalysisKit';

解析

  • 文件头的多行注释清晰说明了文件用途、场景和技术要点。这是团队协作中的良好实践,方便其他开发者快速理解文件意图。
  • import { hilog } from '@kit.PerformanceAnalysisKit' 导入鸿蒙的日志工具包。hilog 是 HarmonyOS 的日志系统(HiLog 的缩写),替代了 Android 的 Log 或 iOS 的 NSLog。使用格式:hilog.info(0x0000, TAG, 'message'),其中 0x0000 是日志域(domain),TAG 是标签。

6.4 常量与数据模型

const TAG = 'ColumnCenterDemo';

interface FeatureItem {
  icon: string;
  name: string;
  color: string;
}

解析

  • TAG 常量用于日志输出的标签,遵循 Android / 鸿蒙的日志惯例,使用类名或文件名作为 TAG。
  • FeatureItem 接口定义了功能入口的数据模型:icon(图标 emoji 或图标名)、name(显示名称)、color(主题色,用于生成背景)。

6.5 子组件:FeatureIcon

@Component
struct FeatureIcon {
  private item: FeatureItem = { icon: '', name: '', color: '#3a7bd5' };

  build() {
    Column() {
      // 图标
      Text(this.item.icon)
        .fontSize(28)
        .lineHeight(48)
        .textAlign(TextAlign.Center)
        .width(48)
        .height(48)
        .backgroundColor(this.item.color + '22')   // 低透明度背景
        .borderRadius(12)

      // 名称
      Text(this.item.name)
        .fontSize(12)
        .fontColor('#444444')
        .lineHeight(18)
        .margin({ top: 6 })
    }
    .alignItems(HorizontalAlign.Center)
    .width(72)
  }
}

逐行解析

  1. @Component 装饰器将该结构体声明为一个可复用的 ArkUI 组件。被 @Component 修饰的 struct 必须实现 build() 方法。

  2. private item: FeatureItem = { icon: '', name: '', color: '#3a7bd5' } 声明了一个私有属性,类型为 FeatureItem。注意 ArkTS 要求属性必须有初始值,或者通过构造函数传入。默认的三元组提供了一个安全默认值。

  3. 图标部分

    • Text(this.item.icon) — 使用 emoji 作为图标显示,这是鸿蒙 ArkUI 支持的优秀特性。emoji 在 HarmonyOS 上原生渲染,无需额外资源文件。
    • .fontSize(28) — 图标大小 28fp(font pixel,鸿蒙的字体单位,类似 sp)。
    • .lineHeight(48) — 行高设为 48,确保图标垂直方向有足够空间。
    • .textAlign(TextAlign.Center) — 文字居中对齐。对于单个字符的 emoji,这个属性看似多余,但保持代码的完备性是良好习惯。
    • .width(48).height(48) — 设置固定宽高,形成一个方形的图标区域。
    • .backgroundColor(this.item.color + '22') — 动态生成低透明度背景色。'22' 是十六进制透明度值(约 13% 不透明度)。例如 #3a7bd522 表示在 #3a7bd5 蓝色基础上叠加了 13% 不透明度的背景。
    • .borderRadius(12) — 圆角半径为 12vp,形成圆角方形。
  4. 名称部分

    • .fontSize(12).fontColor('#444444') — 12fp 字号,深灰色文字。
    • .margin({ top: 6 }) — 顶部外边距 6vp,与图标产生间距。
  5. 外层 Column

    • .alignItems(HorizontalAlign.Center) — 子组件在交叉轴(水平)居中。因为图标和文字宽度不同,居中保证视觉对齐。
    • .width(72) — 固定宽度 72vp,确保四个功能入口在 Row 中宽度一致。

6.6 主页面结构

@Entry
@Component
struct ColumnCenterPage {
  private readonly features: FeatureItem[] = [
    { icon: '📷', name: '相机', color: '#3a7bd5' },
    { icon: '🎵', name: '音乐', color: '#e74c3c' },
    { icon: '📁', name: '文件', color: '#2ecc71' },
    { icon: '⚙️', name: '设置', color: '#f39c12' },
  ];

  build() {
    Column() {
      // 区域 1:页面标题区
      // 区域 2:核心演示区
      // 区域 3:布局技术说明面板
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#eef2f7')
  }
}

解析

  • @Entry 装饰器标记该组件为页面的入口点。一个页面文件中只能有一个 @Entry 组件。
  • features 数组是只读的 FeatureItem 列表,定义了四个功能入口数据。
  • 外层 Column 撑满全屏(width('100%').height('100%')),背景色为浅灰蓝色。

6.7 区域 1:页面标题区

// ========== 区域 1:页面标题区 ==========
Column() {
  Text('📐 Column + justifyContent(Center)')
    .fontSize(20)
    .fontWeight(FontWeight.Bold)
    .fontColor('#ffffff')
    .lineHeight(28)

  Text('主轴(垂直)居中分布 · 子组件整体居中对齐')
    .fontSize(12)
    .fontColor('#cce0ff')
    .margin({ top: 4 })
}
.alignItems(HorizontalAlign.Start)
.width('100%')
.padding({ top: 20, bottom: 16, left: 20, right: 20 })
.backgroundColor('#2d5f8a')

解析

  • 标题区使用深蓝色背景(#2d5f8a),白色主标题 + 浅蓝色副标题。
  • .alignItems(HorizontalAlign.Start) 左对齐文字——标题通常左对齐阅读体验更好。
  • .padding({ top: 20, bottom: 16, left: 20, right: 20 }) 设置内边距。注意 padding 可以接受对象或单个数字。对象形式可以分别控制四个方向的值。
  • 主标题字号 20fp,加粗;副标题 12fp,通过 .margin({ top: 4 }) 与主标题产生 4vp 间距。注意这里用的是 margin 在副标题上,而不是 spacepadding——这是组件级间距的细粒度控制方式。

6.8 区域 2:核心演示区(FlexAlign.Center 的核心见证)

// ============================================================
// ========== 区域 2:核心演示区 — Column + Center ↓↓↓ ==========
//   所有子组件作为一个整体,在垂直方向居中排列
// ============================================================
Column() {
  // ── 2.1 区域标题 ──
  Text('🎯 功能入口(垂直居中排列)')
    .fontSize(15)
    .fontWeight(FontWeight.Bold)
    .fontColor('#1a1a2e')
    .margin({ bottom: 8 })

  Divider()
    .height(1)
    .width('100%')
    .color('#e8e8e8')
    .margin({ bottom: 16 })

  // ── 2.2 功能图标行 ──
  Row() {
    ForEach(this.features, (item: FeatureItem) => {
      FeatureIcon({ item })
    }, (item: FeatureItem) => item.name)
  }
  .alignItems(VerticalAlign.Center)
  .width('100%')
  .justifyContent(FlexAlign.SpaceEvenly)
  .margin({ bottom: 16 })

  Divider()
    .height(1)
    .width('100%')
    .color('#e8e8e8')
    .margin({ bottom: 16 })

  // ── 2.3 居中按钮 ──
  Button('🚀 开始体验(居中演示按钮)')
    .width(200)
    .height(46)
    .backgroundColor('#3a7bd5')
    .fontColor('#ffffff')
    .borderRadius(23)            // 胶囊按钮
    .fontSize(15)
    .fontWeight(FontWeight.Medium)
    .onClick(() => {
      hilog.info(0x0000, TAG, 'start clicked');
    })

  // ── 2.4 说明文字 ──
  Text('点击按钮即可体验居中布局效果')
    .fontSize(12)
    .fontColor('#888888')
    .lineHeight(18)
    .margin({ top: 12 })
}
// ★★★ 核心布局属性设置 ★★★
.alignItems(HorizontalAlign.Center)     // 交叉轴(水平)居中
.justifyContent(FlexAlign.Center)       // ★ 主轴(垂直)居中分布
.width('100%')
.height(0)                              // 高度由 layoutWeight 撑满
.layoutWeight(1)                        // 占满屏幕剩余空间
.padding(24)
.backgroundColor('#ffffff')
.borderRadius(12)
.margin({ left: 12, right: 12, top: 10, bottom: 12 })
.shadow({ radius: 6, color: '#1a000000', offsetX: 0, offsetY: 2 })

逐段深入解析

6.8.1 区域标题与分隔线(2.1)
Text('🎯 功能入口(垂直居中排列)')
  .fontSize(15)
  .fontWeight(FontWeight.Bold)
  .fontColor('#1a1a2e')
  .margin({ bottom: 8 })

提示标题,15fp 加粗,深色文字。

Divider()
  .height(1)
  .width('100%')
  .color('#e8e8e8')
  .margin({ bottom: 16 })

Divider 是 ArkUI 内置的分隔线组件。这里设为 1vp 高度,100% 宽度,浅灰色。

6.8.2 功能图标行(2.2)
Row() {
  ForEach(this.features, (item: FeatureItem) => {
    FeatureIcon({ item })
  }, (item: FeatureItem) => item.name)
}
.alignItems(VerticalAlign.Center)
.width('100%')
.justifyContent(FlexAlign.SpaceEvenly)
.margin({ bottom: 16 })

ForEach 详解

ForEach 是 ArkUI 中用于循环渲染列表的内置组件,签名如下:

ForEach<T>(
  arr: T[],
  itemGenerator: (item: T, index?: number) => void,
  keyGenerator?: (item: T, index?: number) => string
)
  • arr:要遍历的数组
  • itemGenerator:为每个数组元素生成子组件的函数
  • keyGenerator(可选):生成唯一 key 的函数,用于 Diff 算法优化。如果提供,框架使用 key 来追踪元素的变化(添加、删除、重排),而不是根据索引。不提供 key 的情况下,ArkUI 会使用索引作为默认 key。

在示例中,(item: FeatureItem) => item.name 作为 key 生成器,使用功能名称作为唯一标识。考虑到名称本身不重复(相机、音乐、文件、设置),这是一个合理的选择。如果数据可能重复,建议使用 item.name + Math.random() 或一个唯一的 id 字段。

Row 属性:Row 与 Column 是镜像关系——Row 的主轴是水平方向,交叉轴是垂直方向。justifyContent(FlexAlign.SpaceEvenly) 让四个图标在 Row 中水平均匀分布。

6.8.3 居中按钮(2.3)
Button('🚀 开始体验(居中演示按钮)')
  .width(200)
  .height(46)
  .backgroundColor('#3a7bd5')
  .fontColor('#ffffff')
  .borderRadius(23)            // 胶囊按钮
  .fontSize(15)
  .fontWeight(FontWeight.Medium)
  .onClick(() => {
    hilog.info(0x0000, TAG, 'start clicked');
  })

.borderRadius(23) 值等于高度(46)的一半,形成「胶囊按钮」效果(两端为半圆)。

鸿蒙 Button 详解

Button 组件在 ArkUI 中有多种构造方式:

// 方式一:简单文字按钮
Button('文字')

// 方式二:自定义内容按钮
Button() {
  Image($r('app.media.icon'))
  Text('自定义内容')
}

// 方式三:带点击事件
Button('按钮')
  .onClick(() => { /* 处理事件 */ })

// 方式四:带类型参数
Button({ type: ButtonType.Capsule, stateEffect: true }) {
  Text('胶囊按钮')
}

这里的 .borderRadius(23) 是通过属性设置圆角,而不是通过 ButtonType.Capsule。两种方式效果类似,但属性设置更灵活(可以精确控制圆角大小)。

6.8.4 说明文字(2.4)
Text('点击按钮即可体验居中布局效果')
  .fontSize(12)
  .fontColor('#888888')
  .lineHeight(18)
  .margin({ top: 12 })

12fp 灰色辅助说明文字,通过 .margin({ top: 12 }) 与按钮产生间距。

6.8.5 ★★★ 核心:主轴居中属性设置 ★★★
.alignItems(HorizontalAlign.Center)     // 交叉轴(水平)居中
.justifyContent(FlexAlign.Center)       // ★ 主轴(垂直)居中
.width('100%')
.height(0)                              // 高度由 layoutWeight 分配
.layoutWeight(1)                        // 占满剩余空间
.padding(24)
.backgroundColor('#ffffff')
.borderRadius(12)
.margin({ left: 12, right: 12, top: 10, bottom: 12 })
.shadow({ radius: 6, color: '#1a000000', offsetX: 0, offsetY: 2 })

.justifyContent(FlexAlign.Center) — 这是整篇文章的核心。它告诉 Column 容器:所有子组件作为一个整体在垂直方向居中。

.alignItems(HorizontalAlign.Center) — 子组件在水平方向居中。如果不设置此项,子组件默认拉伸至 Column 宽度(Column 的默认 alignItems 是 HorizontalAlign.Stretch)。设置 Center 后,子组件按自身宽度居中显示。

layoutWeight 机制详解

.height(0)        // 初始高度设为 0
.layoutWeight(1)  // 分配父容器 Flex 布局中的剩余空间权重

layoutWeight 是 ArkUI 弹性布局中的重要概念。当父容器使用 ColumnRow 时,子组件的 layoutWeight 决定它在剩余空间中的分配比例。

计算过程:

  1. 父容器测量所有未设置 layoutWeight 的子组件,确定它们占据的空间
  2. 从父容器总尺寸中减去已占空间,得到「剩余空间」
  3. 将剩余空间按 layoutWeight 权重分配给设置了该属性的子组件

示例中:父 Column 撑满全屏(height('100%')),标题区占用固定高度,核心演示区通过 layoutWeight(1) 占满剩余所有空间。这样核心演示区的 Column 就有足够的高度来展示「垂直居中」效果。

shadow 属性

.shadow({ radius: 6, color: '#1a000000', offsetX: 0, offsetY: 2 })
参数 说明
radius 6 阴影模糊半径,越大阴影越柔和
color ‘#1a000000’ 阴影颜色 + 透明度(‘1a’ = 10% 不透明度)
offsetX 0 水平偏移量
offsetY 2 垂直偏移量,正值表示向下偏移 2vp

颜色字符串规则:'#AARRGGBB',其中 AA 是十六进制透明度(00=全透明, FF=不透明)。'1a' ≈ 10% 不透明度。

6.9 区域 3:布局技术说明面板

说明面板由三个部分组成:

  1. 布局要点列表:用 Row() + 带圆点的 Text() 模拟无序列表
  2. FlexAlign 对比表格:使用 Column() + Row() 模拟表格
  3. 核心代码展示:使用带背景的 Column() 模拟代码块
6.9.1 布局要点列表
Row() {
  Text('●').fontColor('#3a7bd5').fontSize(10).margin({ right: 8 })
  Text('Column 容器的「主轴」= 垂直方向(从上到下)').fontSize(12).fontColor('#555')
}
.alignItems(VerticalAlign.Top).margin({ bottom: 5 })

这种模式用 Row 模拟列表项:左侧是蓝色圆点,右侧是说明文字。.alignItems(VerticalAlign.Top) 确保圆点与文字的顶部对齐,而不是默认居中对齐。

6.9.2 FlexAlign 对比表格
Column() {
  // 表头
  Row() {
    Text('对齐方式').width(80).fontSize(11).fontWeight(FontWeight.Bold).fontColor('#2d5f8a')
    Text('效果描述').width(140).fontSize(11).fontWeight(FontWeight.Bold).fontColor('#2d5f8a')
  }
  .width('100%')
  .padding({ top: 6, bottom: 6 })
  .backgroundColor('#eef2f7')
  .borderRadius({ topLeft: 6, topRight: 6 })

  // 数据行:Start
  Row() {
    Text('Start').width(80).fontSize(11).fontColor('#555')
    Text('顶部起始排列,紧凑于顶').width(140).fontSize(11).fontColor('#555')
  }
  .width('100%')
  .padding({ top: 5, bottom: 5 })

  // 数据行:Center(红色高亮)
  Row() {
    Text('★ Center').width(80).fontSize(11).fontWeight(FontWeight.Bold).fontColor('#e74c3c')
    Text('整体垂直居中 ★').width(140).fontSize(11).fontWeight(FontWeight.Bold).fontColor('#e74c3c')
  }
  .width('100%')
  .padding({ top: 5, bottom: 5 })
  .backgroundColor('#fff5f5')
  // ... 其余行省略
}
.width('100%')
.border({ width: 1, color: '#e0e4e8' })
.borderRadius(6)

表格实现技巧

  • 使用 Column 作为表格容器,Row 作为行
  • 表头背景色为 #eef2f7,数据行交替使用白色和浅红色
  • borderRadius({ topLeft: 6, topRight: 6 }) 只给表头上方加圆角
  • 表格容器本身 .borderRadius(6) + .border({ width: 1, color: '#e0e4e8' }) 给整个表格加边框和圆角
  • Center 行高亮(红色文字 + 浅红背景),强调这是本示例的核心模式

.borderRadius 对象语法

// 全面控制四个角的圆角
.borderRadius({
  topLeft: 6,
  topRight: 6,
  bottomLeft: 0,
  bottomRight: 0
})

// 或简写:四个角一致
.borderRadius(6)
6.9.3 核心代码展示
Column() {
  Text('Column() {').fontSize(12).fontColor('#2d5f8a').fontFamily('Courier New')
  Text('  // 子组件将作为一个整体垂直居中').fontSize(12).fontColor('#999').fontFamily('Courier New')
  Text('  Text("上方留白与下方留白相等")').fontSize(12).fontColor('#c7254e').fontFamily('Courier New')
  Text('  Button("居中按钮")').fontSize(12).fontColor('#c7254e').fontFamily('Courier New')
  Text('}').fontSize(12).fontColor('#2d5f8a').fontFamily('Courier New')
  Text('.alignItems(HorizontalAlign.Center)   // 交叉轴水平居中')
    .fontSize(12).fontColor('#c7254e').fontWeight(FontWeight.Bold).fontFamily('Courier New')
  Text('.justifyContent(FlexAlign.Center)      // ★ 主轴垂直居中 ★')
    .fontSize(12).fontColor('#2d5f8a').fontWeight(FontWeight.Bold).fontFamily('Courier New')
}
.alignItems(HorizontalAlign.Start)
.width('100%')
.padding(12)
.backgroundColor('#f0f4f8')
.borderRadius(8)

代码块设计思路

  • 使用 fontFamily('Courier New') 模拟等宽字体代码块
  • 关键字(ColumnFlexAlign.Center)用蓝色 #2d5f8a,字符串/值用红色 #c7254e,注释用灰色 #999
  • 关键行加粗(fontWeight(FontWeight.Bold)
  • 背景色 #f0f4f8 浅灰蓝,与普通内容区区分
  • padding(12) + borderRadius(8) 形成内边距和圆角

7. FlexAlign 六种模式对比与选型

7.1 对比总表

模式 顶部间距 底部间距 中间间距 适用场景
Start 0 全部 固定 列表、信息流
Center 一半 一半 固定 弹窗、登录页、加载状态
End 全部 0 固定 底部操作栏、FAB 按钮组
SpaceBetween 0 0 均匀 导航栏、选项组
SpaceAround 一半 一半 均匀 标签栏、功能入口
SpaceEvenly 均匀 均匀 均匀 均匀分布的菜单

7.2 选型决策树

子组件需要固定间距?
├── 是 → 子组件使用 margin 或 Column space 参数
│   └── 整体位置?
│       ├── 顶部 → FlexAlign.Start
│       ├── 居中 → FlexAlign.Center ← ★ 本文主题
│       └── 底部 → FlexAlign.End
└── 子组件间距需要自适应撑满?
    ├── 首尾无间距 → FlexAlign.SpaceBetween
    ├── 首尾有半间距 → FlexAlign.SpaceAround
    └── 首尾间距一致 → FlexAlign.SpaceEvenly

7.3 常见误区

误区一:Center 会让子组件之间也均匀分布

FlexAlign.Center 只控制整体居中,不会改变子组件之间的间距。如果需要在居中的同时子组件之间也有间距,必须通过 Column({ space: 16 }) 或子组件的 margin 来实现。

// ❌ 错误期待:以为 Center 会自动间距均匀
Column() {
  Text('A')
  Text('B')
  Text('C')
}
.justifyContent(FlexAlign.Center)
// 结果:A、B、C 紧挨在一起(无间距),整体居中

// ✅ 正确:Center + space 控制间距
Column({ space: 12 }) {
  Text('A')
  Text('B')
  Text('C')
}
.justifyContent(FlexAlign.Center)
// 结果:A、B、C 间距 12vp,整体居中

误区二:Column 高度不够时居中无效果

// ❌ 父容器没有给 Column 分配足够的空间
Column() {
  Column() {
    Text('内容')
  }
  .justifyContent(FlexAlign.Center)
  .height(100)   // 高度刚够,没有剩余空间
}
// ↓ 修正 ↓
Column() {
  Column() {
    Text('内容')
  }
  .justifyContent(FlexAlign.Center)
  .height('100%')  // 撑满父容器
}

误区三:混淆 justifyContent 和 alignItems

  • justifyContent 控制主轴方向的排列
  • alignItems 控制交叉轴方向的对齐

对于 Column:

  • justifyContent → 垂直方向
  • alignItems → 水平方向

对于 Row:

  • justifyContent → 水平方向
  • alignItems → 垂直方向

记忆口诀:justifyContent 是「顺着内容排列的方向」,alignItems 是「横着对过去」


8. alignItems 交叉轴对齐协同

8.1 Column 的 alignItems 取值

Column 的交叉轴是水平方向,alignItems 接受 HorizontalAlign 枚举:

枚举值 行为 说明
HorizontalAlign.Start 子组件左对齐 子组件从容器左边开始
HorizontalAlign.Center 子组件水平居中 子组件在容器中水平居中
HorizontalAlign.End 子组件右对齐 子组件从容器右边开始
HorizontalAlign.Stretch 子组件水平拉伸 子组件宽度撑满容器(默认值)

8.2 justifyContent + alignItems 组合效果

以 Column 为例,不同的排列组合会产生不同的视觉效果:

// 组合一:居中对齐 + 居中排列
Column() { /* 子组件 */ }
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
// 效果:子组件整体在容器正中央(水平和垂直都居中)

// 组合二:左对齐 + 顶部排列
Column() { /* 子组件 */ }
.alignItems(HorizontalAlign.Start)
.justifyContent(FlexAlign.Start)
// 效果:子组件在容器左上角

// 组合三:左对齐 + 居中排列
Column() { /* 子组件 */ }
.alignItems(HorizontalAlign.Start)
.justifyContent(FlexAlign.Center)
// 效果:子组件在垂直方向居中,但左侧对齐
// 适用于:左侧标签 + 居中内容的组合

// 组合四:拉伸 + 居中排列
Column() { /* 子组件 */ }
.alignItems(HorizontalAlign.Stretch)
.justifyContent(FlexAlign.Center)
// 效果:子组件撑满宽度,垂直居中
// 适用于:居中卡片内的内容区

8.3 默认值的重要性

Column 的 alignItems 默认值是 HorizontalAlign.Stretch。这意味如果不显式设置,子组件会被水平拉伸。

// 默认行为:子组件被拉伸至 Column 宽度
Column() {
  Button('按钮')     // 会被拉伸到和 Column 一样宽
  Text('文字')       // 也会被拉伸到和 Column 一样宽
}
// 这通常不是设计师想要的效果

// 显式设置:子组件保持自身宽度
Column() {
  Button('按钮')     // 维持自身宽度
  Text('文字')       // 维持自身宽度
}
.alignItems(HorizontalAlign.Center)  // 并且居中

9. 常见布局场景与最佳实践

9.1 场景一:全屏居中加载状态

@Entry
@Component
struct LoadingPage {
  build() {
    Column() {
      // 加载动画
      LoadingProgress()
        .width(48)
        .height(48)
        .color('#3a7bd5')

      // 提示文字
      Text('加载中,请稍候...')
        .fontSize(14)
        .fontColor('#888888')
        .margin({ top: 16 })
    }
    .alignItems(HorizontalAlign.Center)
    .justifyContent(FlexAlign.Center)
    .width('100%')
    .height('100%')
    .backgroundColor('#ffffff')
  }
}

9.2 场景二:居中弹窗

@Entry
@Component
struct DialogPage {
  build() {
    Column() {
      // 弹窗卡片
      Column() {
        Text('确认删除?')
          .fontSize(18)
          .fontWeight(FontWeight.Bold)

        Text('删除后数据不可恢复,请谨慎操作。')
          .fontSize(14)
          .fontColor('#666666')
          .margin({ top: 12 })

        Row({ space: 12 }) {
          Button('取消')
            .width(100)
            .height(40)
            .backgroundColor('#e8e8e8')
            .fontColor('#333333')

          Button('确认')
            .width(100)
            .height(40)
            .backgroundColor('#e74c3c')
            .fontColor('#ffffff')
        }
        .margin({ top: 24 })
      }
      .alignItems(HorizontalAlign.Center)
      .padding(24)
      .backgroundColor('#ffffff')
      .borderRadius(16)
      .shadow({ radius: 16, color: '#33000000' })
      .width(280)
    }
    .alignItems(HorizontalAlign.Center)
    .justifyContent(FlexAlign.Center)
    .width('100%')
    .height('100%')
    .backgroundColor('#66000000')   // 半透明遮罩
  }
}

9.3 场景三:空状态提示

@Entry
@Component
struct EmptyStatePage {
  build() {
    Column() {
      // 空状态图标
      Text('📭')
        .fontSize(64)

      Text('暂无消息')
        .fontSize(16)
        .fontWeight(FontWeight.Medium)
        .fontColor('#333333')
        .margin({ top: 16 })

      Text('当您收到消息时,将在此处显示')
        .fontSize(13)
        .fontColor('#999999')
        .margin({ top: 8 })

      Button('去逛逛')
        .width(160)
        .height(42)
        .backgroundColor('#3a7bd5')
        .borderRadius(21)
        .fontSize(15)
        .fontColor('#ffffff')
        .margin({ top: 24 })
    }
    .alignItems(HorizontalAlign.Center)
    .justifyContent(FlexAlign.Center)
    .width('100%')
    .height('100%')
    .backgroundColor('#f5f5f5')
  }
}

9.4 最佳实践总结

  1. 始终考虑容器高度是否足够FlexAlign.Center 的效果依赖于容器有剩余空间。使用 layoutWeightheight('100%') 确保容器有足够高度。

  2. space 参数优先于手动 margin:当所有子组件间距一致时,使用 Column({ space: 16 }) 比在每个子组件上设置 margin 更简洁且不易出错。

  3. key 生成器避免索引:在 ForEach 中始终提供基于数据的 key 生成器,而不是依赖默认索引 key。这样在列表增删改时,框架可以正确复用和更新组件。

  4. 颜色值使用完整十六进制:鸿蒙 ArkUI 的颜色字符串支持 '#RGB''#ARGB''#RRGGBB''#AARRGGBB' 四种格式。推荐始终使用 #AARRGGBB 格式,明确包含透明度信息。

  5. 分隔线使用 Divider 组件:使用内置的 Divider() 组件而不是用 Column() + backgroundColor 模拟分割线。Divider 在性能和行为上都更优。

  6. 嵌套 Column 时注意属性能见度:外层 Column 的 justifyContentalignItems 不会穿透到内层 Column。每层容器独立管理自己子组件的布局。


10. 性能优化与注意事项

10.1 避免不必要的嵌套

Column 容器的嵌套会增加布局计算的复杂度。以下是不好的实践:

// ❌ 过度嵌套
Column() {
  Column() {        // 这层嵌套无意义
    Column() {      // 这层也没有实际作用
      Text('内容')
    }
  }
}

建议:每个 Column 嵌套都应该有明确的布局目的。

10.2 layoutWeight 的合理使用

layoutWeight 虽然方便,但滥用会导致布局难以调试:

// ⚠️ 多个 layoutWeight 子组件容易让人困惑
Column() {
  Column().layoutWeight(1)   // 占 1 份
  Column().layoutWeight(2)   // 占 2 份
  Column().layoutWeight(1)   // 占 1 份
}
// 总份数 = 4,三个子组件分别占 25%、50%、25%

建议:使用 layoutWeight 时不超过 2-3 个子组件,且权重值保持简单(1、2、3 等小整数)。

10.3 避免在 build() 中做复杂计算

// ❌ 不推荐:在 build 中计算
build() {
  Column() {
    Text(`当前时间:${this.getCurrentTime()}`)  // 每次刷新都调用
  }
}

// ✅ 推荐:在状态变量中计算
@State currentTime: string = this.getCurrentTime();
build() {
  Column() {
    Text(`当前时间:${this.currentTime}`)
  }
}

10.4 使用 @State 管理动态数据

如果布局的动态变化受数据驱动,使用 @State 等装饰器:

@Entry
@Component
struct DynamicCenterDemo {
  @State private items: string[] = ['项目一', '项目二', '项目三'];

  build() {
    Column() {
      ForEach(this.items, (item: string) => {
        Text(item)
          .fontSize(16)
          .padding(12)
          .backgroundColor('#f0f4f8')
          .borderRadius(8)
          .margin({ bottom: 8 })
      }, (item: string) => item)

      Button('添加项目')
        .onClick(() => {
          this.items.push(`项目${this.items.length + 1}`);
        })
    }
    .alignItems(HorizontalAlign.Center)
    .justifyContent(FlexAlign.Center)
    .width('100%')
    .height('100%')
    .padding(20)
  }
}

items 数组变化时,框架自动重新布局,新添加的项目会作为整体一起居中。

10.5 性能指标参考

在 HarmonyOS NEXT 模拟器/真机上,一个 Column 容器直接管理 50 个以内的子组件时,性能完全无压力。超过 50 个建议使用 List 组件(虚拟列表)以获得更好的滚动性能。

子组件数量 推荐容器 说明
1-10 Column 直接管理,简单高效
10-50 Column / Scroll + Column Column 可配合 Scroll 实现滚动
50+ List 虚拟列表,只渲染可见项

11. 常见问题 FAQ

Q1:FlexAlign.Center 和 alignItems 的 Center 有什么区别?

A:justifyContent(FlexAlign.Center) 控制子组件在 主轴(垂直) 方向整体居中。alignItems(HorizontalAlign.Center) 控制子组件在 交叉轴(水平) 方向对齐。两者一个是主轴排列,一个是交叉轴对齐,互不冲突。

如果希望子组件在容器正中央,需要同时设置两者:

Column() {
  Text('正中央')
}
.alignItems(HorizontalAlign.Center)  // 水平居中
.justifyContent(FlexAlign.Center)    // 垂直居中
.width('100%')
.height(300)

Q2:为什么设置了 Center 但子组件没有居中?

A:最常见的原因是容器高度不够。排查步骤:

  1. 确认容器有明确的高度(layoutWeightheight('100%') 或固定数值)
  2. 确认子组件总高度小于容器高度
  3. 检查是否有 paddingmargin 影响了可用空间

快速调试方法:给容器设置一个明显的背景色(如 backgroundColor('#ff000022')),直观查看容器的实际范围。

Q3:Center 和 SpaceEvenly 效果一样吗?

A:不一样。Center 保持子组件之间的固定间距(由 spacemargin 控制),整体居中。SpaceEvenly 会重新分配间距,使所有间距(包括首尾)相等。详细对比见第 5.2 节。

Q4:Column 和 Flex 有什么区别?

A:Column 是 Flex 的特化版本,主轴固定为垂直方向。Flex 可以通过 direction 属性自定义主轴方向:

// Column 等价于
Flex() {
  // 子组件
}
.direction(FlexDirection.Column)

// 两者的 justifAlignItems 等属性和行为完全一致

使用 Column 或 Flex 的选择标准:如果主轴就是垂直方向且不需要切换,用 Column 语义更清晰;如果需要在水平和垂直之间切换(响应式布局),用 Flex。

Q5:可以在 Column 中混用不同宽度的子组件吗?

A:可以。Column 对子组件的宽度没有限制。如果设置了 .alignItems(HorizontalAlign.Center),不同宽度的子组件会以各自宽度在水平方向居中。如果使用默认的 .alignItems(HorizontalAlign.Stretch),所有子组件会被拉伸到与 Column 同宽。

Q6:ForEach 的 key 生成器有什么作用?

A:key 生成器帮助 ArkUI 框架的 Diff 算法追踪列表元素。当列表数据发生变化(增、删、改)时,框架通过 key 判断哪些元素是新增的、哪些是删除的、哪些是移动的。没有 key 或使用索引作为 key,框架可能需要重建整个列表,影响性能和动画效果。

好的 key 应该基于数据本身:ID、唯一名称、时间戳等。

Q7:如何给 Column 内的子组件设置背景色不同的交替行效果?

A:结合 ForEach 和索引可以实现:

ForEach(this.items, (item: string, index: number) => {
  Text(item)
    .backgroundColor(index % 2 === 0 ? '#ffffff' : '#f5f5f5')
    .padding(12)
    .width('100%')
}, (item: string) => item)

12. 总结

12.1 本文要点回顾

本文围绕鸿蒙原生 ArkTS 布局方式「Column + justifyContent(FlexAlign.Center) 主轴居中分布」,从以下维度进行了深入剖析:

  1. 理论基础:Column 容器的定义、主轴与交叉轴的概念、FlexAlign 六种枚举值的含义
  2. 核心机制:FlexAlign.Center 的三步计算原理(测量→计算剩余空间→平分留白)
  3. 完整示例:从路由注册到子组件,逐行解析了 FeatureIcon 组件和 ColumnCenterPage 页面的每一行代码
  4. 对比选型:六种 FlexAlign 模式的视觉对比和选型决策树
  5. 实践场景:加载状态、弹窗、空状态三个完整可复用示例
  6. 性能优化:嵌套建议、layoutWeight 使用规范、动态数据管理

12.2 核心代码模板

最精简的 Column + FlexAlign.Center 布局模板:

@Entry
@Component
struct CenterTemplate {
  build() {
    Column() {
      // 你的子组件列表
      Text('内容')
      Button('操作')
    }
    .alignItems(HorizontalAlign.Center)  // 交叉轴水平居中
    .justifyContent(FlexAlign.Center)    // ★ 主轴垂直居中
    .width('100%')
    .height('100%')                      // 确保有足够空间展示居中
  }
}

12.3 进一步学习路径

掌握了 Column + FlexAlign.Center 之后,可以沿着以下路径继续深入:

  1. Row + FlexAlign 系列:理解水平方向的排列,与 Column 原理互通
  2. Flex 弹性布局:自定义主轴方向,实现更灵活的布局
  3. Grid 网格布局:二维布局能力,适合宫格、相册等场景
  4. Stack 层叠布局:子组件在 Z 轴层叠,适合浮动按钮、标签等
  5. List + ForEach:虚拟滚动列表,适合长列表场景
  6. 响应式布局:使用 @MediaQuerybreakpoint 实现不同屏幕尺寸的适配

12.4 结语

鸿蒙 ArkUI 的声明式布局体系虽然借鉴了 Flexbox 的概念,但在 API 设计和行为细节上有自己的特色。掌握 ColumnFlexAlign 等核心布局属性,是迈入鸿蒙原生开发的重要一步。

Logo

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

更多推荐