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

1. 引言:鸿蒙原生布局体系概览

1.1 为什么选择原生 ArkTS 布局

HarmonyOS NEXT 作为华为全场景操作系统的核心迭代,从底层彻底剥离了 Android AOSP 代码,带来了完全原生的声明式 UI 框架——ArkUI。ArkUI 使用 ArkTS 语言(基于 TypeScript 的扩展),提供了一套从布局、动画到交互的完整声明式开发范式。

在 ArkUI 中,布局是一切 UI 的基石。不同于传统命令式 UI 需要手动计算每个控件的位置和尺寸,ArkUI 采用声明式布局理念:开发者只需要描述"组件之间是什么关系",框架自动完成布局计算。这套布局体系的核心组件包括:

组件 主轴方向 对齐方式 适用场景
Column 垂直(从上到下) HorizontalAlign 纵向列表、表单、信息流
Row 水平(从左到右) VerticalAlign 横向排列、工具条、标签栏
Flex 可配置(水平或垂直) ItemAlign 弹性布局、自适应排列
Stack 层叠(Z 轴) Alignment 叠加效果、悬浮按钮
Grid 网格排列 宫格展示、瀑布流
RelativeContainer 相对定位 复杂精确定位
List 虚拟滚动列表 长列表、高性能滚动

1.2 本系列文章的目标

本系列文章将以"单种布局方式 + 完整示例应用"的形式,逐一深入解析 ArkUI 的每一种布局模式。本文作为第一篇,聚焦在日常开发中出现频率最高的布局——ColumnCenter 垂直居中布局

所谓 ColumnCenter,就是使用 Column 容器并设置 alignItems(HorizontalAlign.Center),让所有子组件在水平方向上居中对齐,同时利用 justifyContent 控制它们在垂直方向上的分布。这种布局在移动端应用中无处不在:登录页面、个人资料编辑、文章详情、商品列表……几乎所有需要纵向排列内容的场景都会用到它。


2. Column 组件深度解析

2.1 基本概念

Column 是 ArkUI 中最基础的布局容器之一,它的核心职责是将其子组件沿垂直方向依次排列。可以把它想象成一个垂直方向的"收纳盒":你把组件逐个放进去,它们会从上到下排成一列。

在 HarmonyOS NEXT 6.1.1(API 24)中,Column 的使用非常简单:

Column() {
  // 子组件按顺序从上到下排列
  Text('第一个')
  Text('第二个')
  Text('第三个')
}

2.2 Column 的坐标系

理解 Column 的布局坐标系对正确使用它至关重要:

  • 主轴(Main Axis):垂直方向,从顶部指向底部。子组件沿主轴依次排列。
  • 交叉轴(Cross Axis):水平方向,从左到右或从右到左(取决于 direction 属性)。交叉轴决定了子组件在水平方向上的对齐方式。
       ┌─────────────────────────────┐
       │         交叉轴 (Cross Axis)   │
       │  ←───────────────────────→  │
       │                             │
       │  ┌─────────────────────┐    │    ↑
       │  │     子组件 1         │    │    │
       │  └─────────────────────┘    │    │
       │                             │  主轴
       │  ┌─────────────────────┐    │  (Main Axis)
       │  │     子组件 2         │    │    │
       │  └─────────────────────┘    │    │
       │                             │    ↓
       │  ┌─────────────────────┐    │
       │  │     子组件 3         │    │
       │  └─────────────────────┘    │
       └─────────────────────────────┘

2.3 Column 的核心属性

2.3.1 alignItems — 交叉轴对齐

alignItems 控制子组件在交叉轴(水平方向)上的对齐方式。这是实现 ColumnCenter 布局的关键属性。

在 HarmonyOS NEXT 6.1.1(API 24)中,ColumnalignItems 方法接收 HorizontalAlign 枚举值:

enum HorizontalAlign {
  Start,    // 左对齐
  Center,   // 居中对齐 —— 这正是 ColumnCenter 的核心
  End       // 右对齐
}

重要提醒:在早期的 HarmonyOS SDK 版本(API 12 之前)中,Column.alignItems() 接受的是 ItemAlign 枚举。但在 API 24 中,ColumnRow 的对齐枚举已经分拆:

  • ColumnalignItems(HorizontalAlign)
  • RowalignItems(VerticalAlign)
  • FlexalignItems(ItemAlign)

这是为了提供更强的类型安全,避免将垂直对齐用在 Column 上或反之。如果你在迁移旧代码时遇到类型错误,请检查是否是这里的原因。

2.3.2 justifyContent — 主轴对齐

justifyContent 控制子组件在主轴(垂直方向)上的分布方式。它接收 FlexAlign 枚举:

enum FlexAlign {
  Start,        // 从顶部开始排列(默认值)
  Center,       // 垂直居中排列
  End,          // 从底部开始排列
  SpaceBetween, // 均匀分布,首尾不留空白
  SpaceAround,  // 均匀分布,首尾留一半空白
  SpaceEvenly   // 均匀分布,所有间距相等
}
2.3.3 其他重要属性
属性 类型 说明
width Length 容器宽度,常用 '100%' 或具体数值
height Length 容器高度,常用 '100%' 或具体数值
padding Padding | Length 内边距
margin Margin | Length 外边距
constraintSize ConstraintSizeOptions 尺寸约束(最小/最大宽高)
clip boolean 是否裁剪超出内容
direction Direction 排列方向(LTR/RTL)
space string | number 子组件之间的间距

2.4 Column 的嵌套规则

Column 可以嵌套任何合法组件,包括其他布局容器。常见的嵌套模式有:

Column (外层容器)
├── Column (内层分组)
│   ├── Text
│   └── Image
├── Row (横向布局)
│   ├── Button
│   └── Button
└── Text

每一层嵌套的容器都有自己独立的 alignItemsjustifyContent 设置,互不影响。这是构建复杂布局的关键灵活性。


3. ColumnCenter 布局模式详解

3.1 什么是 ColumnCenter

ColumnCenter 是 Column 布局中最常用的一种模式,其核心配置为:

Column() {
  // 子组件...
}
.width('100%')
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Start)

这种配置的效果是:

  1. 所有子组件在水平方向居中对齐(alignItems 的作用)
  2. 子组件从顶部开始依次排列(justifyContent 的默认行为)
  3. 容器撑满父容器宽度(width(‘100%’) 的作用),使得居中对齐的效果在视觉上更加明显

3.2 ColumnCenter 的变体

ColumnCenter 可以结合不同的 justifyContent 值产生不同的变体:

变体一:顶部居中(默认模式)
Column() {
  // ...
}
.alignItems(HorizontalAlign.Center)  // 水平居中
.justifyContent(FlexAlign.Start)     // 垂直从顶部开始

适用于:表单页、设置页、信息流列表——内容从顶部依次排列,每个组件居中。

变体二:垂直居中
Column() {
  // ...
}
.alignItems(HorizontalAlign.Center)  // 水平居中
.justifyContent(FlexAlign.Center)    // 垂直居中

适用于:启动页、加载状态、空态页面、弹窗内容——需要在屏幕中央展示的内容。

变体三:底部居中
Column() {
  // ...
}
.alignItems(HorizontalAlign.Center)  // 水平居中
.justifyContent(FlexAlign.End)       // 垂直从底部开始

适用于:底部悬浮按钮、底部操作栏、确认对话框。

变体四:均匀分布居中
Column() {
  // ...
}
.alignItems(HorizontalAlign.Center)  // 水平居中
.justifyContent(FlexAlign.SpaceEvenly)  // 均匀分布

适用于:底部导航按钮组、首页功能图标区——需要均匀占据整个空间。

3.3 各变体效果对比

为了直观地理解这四种变体的区别,我们可以编写一个简单的演示代码:

@Component
struct ColumnCenterVariants {
  build() {
    Row() {
      // 变体一:顶部居中
      Column() {
        Text('Top').fontSize(12).fontColor(Color.White)
      }
      .width(60)
      .height(120)
      .backgroundColor('#FF3A86FF')
      .borderRadius(8)
      .alignItems(HorizontalAlign.Center)
      .justifyContent(FlexAlign.Start)

      // 变体二:垂直居中
      Column() {
        Text('Center').fontSize(12).fontColor(Color.White)
      }
      .width(60)
      .height(120)
      .backgroundColor('#FF6C9FFF')
      .borderRadius(8)
      .alignItems(HorizontalAlign.Center)
      .justifyContent(FlexAlign.Center)

      // 变体三:底部居中
      Column() {
        Text('End').fontSize(12).fontColor(Color.White)
      }
      .width(60)
      .height(120)
      .backgroundColor('#FFA3C4FF')
      .borderRadius(8)
      .alignItems(HorizontalAlign.Center)
      .justifyContent(FlexAlign.End)
    }
    .width('100%')
    .justifyContent(FlexAlign.SpaceEvenly)
    .padding(20)
  }
}

在这个示例中,三个高度相同的 Column 容器分别使用了 StartCenterEnd 三种对齐方式,内部的文字会分别出现在顶部、中间和底部,非常直观地展示了 justifyContent 的效果。


4. alignItems 与 justifyContent 的协作机制

4.1 核心协作关系

alignItemsjustifyContent 是 Column 布局的两大核心控制参数。它们相互独立又协同工作:

  • alignItems 控制交叉轴(水平方向)上的对齐——决定每个子组件在其所在行中的左右位置。
  • justifyContent 控制主轴(垂直方向)上的排列——决定子组件整体在 Column 容器中的上下分布。

alignItems(HorizontalAlign.Center)justifyContent(FlexAlign.Start) 结合时,效果是:所有组件从左到右居中,同时从上到下依次排列。

4.2 不同布局模式的协作效果

下表展示了不同组合的视觉表现(假设 Column 内有三个不同宽度的子组件):

alignItems justifyContent 效果描述
Start Start 左上角开始排列,左对齐,顶部对齐
Center Start 顶部排列,水平居中 ← ColumnCenter 标准模式
Center Center 整个容器垂直居中,水平居中
Center SpaceBetween 两端分布,水平居中
End Start 顶部排列,右对齐
Center SpaceEvenly 均匀分布,水平居中

4.3 子组件自身对齐的优先级

需要注意的是,如果子组件自身设置了 alignSelf 属性,它会覆盖父容器 alignItems 的设置:

Column() {
  Text('item1')
    .alignSelf(ItemAlign.Start)  // 此子组件左对齐,覆盖父容器的 Center

  Text('item2')
    // 此子组件遵循父容器的 HorizontalAlign.Center

  Text('item3')
    .alignSelf(ItemAlign.End)    // 此子组件右对齐,覆盖父容器的 Center
}
.width('100%')
.alignItems(HorizontalAlign.Center)

这在需要大部分子组件居中、但特定组件需要左对齐或右对齐的场景中非常有用,例如表单中的标签和输入框组合。

4.4 width 的作用

在 ColumnCenter 布局中,有一个容易忽略的关键点:父容器必须设置明确的宽度alignItems(HorizontalAlign.Center) 的效果才能正确显现。

思考下面两种情况:

情况 A — 未设置 width:

Column() {
  Text('居中的文字')
    .fontSize(20)
}
.alignItems(HorizontalAlign.Center)

此时 Column 的宽度由最宽的子组件决定(等于 Text 的宽度),Text 本身已经在 Column 中居中,但由于 Column 宽度等于 Text 宽度,居中的效果不可见。

情况 B — 设置 width(‘100%’):

Column() {
  Text('居中的文字')
    .fontSize(20)
}
.width('100%')                    // Column 撑满父容器
.alignItems(HorizontalAlign.Center)  // 文字在容器中居中

此时 Column 撑满父容器宽度,Text 在 Column 中居中,效果肉眼可见。

结论:使用 ColumnCenter 布局时,务必为 Column 设置 width('100%') 或一个足够大的宽度值,否则居中效果不可见。这是初学者最容易踩的坑之一。


5. 示例应用完整实现

接下来,我们将构建一个完整的 ColumnCenter 布局演示应用。这个应用包含五个展示区域:标题区、布局原理说明、表单示例、信息流列表、justifyContent 效果演示。

5.1 项目结构

entry/src/main/ets/pages/Index.ets   ← 唯一页面文件

5.2 完整代码(含详细注释)

以下是与 HarmonyOS NEXT 6.1.1(API 24)完全兼容的完整实现代码,已在 DevEco Studio 中通过编译验证:

/*
 * ColumnCenter 垂直居中布局示例
 * =============================================================
 * 【布局要点】
 *   1) Column 容器:子组件沿垂直方向排列(从上到下)
 *   2) alignItems(HorizontalAlign.Center):所有子组件在水平方向居中对齐
 *   3) justifyContent(FlexAlign.Start / Center / SpaceBetween):
 *       控制子组件在垂直方向上的分布方式
 *   4) width('100%'):容器撑满父容器宽度,使居中效果可见
 * =============================================================
 * SDK: HarmonyOS NEXT 6.1.1 (API 24)
 */

// =============================================================
// 入口页面:ColumnCenter 布局概念展示页
// =============================================================
@Entry
@Component
struct ColumnCenterDemo {
  // ---------- 状态变量 ----------
  @State userName: string = '';         // 用户名输入
  @State userEmail: string = '';        // 邮箱输入
  @State agreed: boolean = false;       // 是否同意协议
  @State selectedCity: string = '北京'; // 选中的城市
  @State showDetail: boolean = false;   // 是否展开详情

  // 城市列表数据
  private cities: string[] = ['北京', '上海', '广州', '深圳', '杭州'];

  // ---------- 构建 UI ----------
  build() {
    // 【核心】Scroll 容器包裹 Column,实现内容可滚动
    Scroll() {
      // 【核心】Column 容器:垂直排列 + 水平居中
      Column() {
        // ① 顶部标题区
        Column() {
          Text('📐 ColumnCenter 布局')
            .fontSize(28)
            .fontWeight(FontWeight.Bold)
            .fontColor('#FF3A86FF')

          Text('Column + alignItems(Center) + justifyContent')
            .fontSize(14)
            .fontColor('#FF888888')
            .margin({ top: 6 })
        }
        .width('100%')
        .alignItems(HorizontalAlign.Center)
        .padding({ top: 24, bottom: 16 })

        // ② 布局说明卡片
        CardContainer() {
          Column() {
            Text('🎯 布局原理')
              .fontSize(16)
              .fontWeight(FontWeight.Bold)

            Row() {
              Text('•').fontColor('#FF3A86FF').fontSize(18).fontWeight(FontWeight.Bold)
              Text(' Column —— 所有子组件沿垂直方向排列')
                .fontSize(13)
                .fontColor('#FF666666')
                .margin({ left: 6 })
            }
            .width('100%')
            .margin({ top: 8 })

            Row() {
              Text('•').fontColor('#FF3A86FF').fontSize(18).fontWeight(FontWeight.Bold)
              Text(' alignItems(HorizontalAlign.Center) —— 水平居中')
                .fontSize(13)
                .fontColor('#FF666666')
                .margin({ left: 6 })
            }
            .width('100%')
            .margin({ top: 4 })

            Row() {
              Text('•').fontColor('#FF3A86FF').fontSize(18).fontWeight(FontWeight.Bold)
              Text(' justifyContent —— 垂直方向分布控制')
                .fontSize(13)
                .fontColor('#FF666666')
                .margin({ left: 6 })
            }
            .width('100%')
            .margin({ top: 4 })
          }
          .alignItems(HorizontalAlign.Start)
          .width('100%')
        }

        // ③ 表单场景
        CardContainer() {
          Column() {
            Text('📝 表单示例(纵向列表 + 居中)')
              .fontSize(16)
              .fontWeight(FontWeight.Bold)

            TextInput({ placeholder: '请输入用户名', text: this.userName })
              .onChange((val: string) => { this.userName = val; })
              .height(44)
              .borderRadius(8)
              .backgroundColor('#FFF5F5F5')
              .padding({ left: 12 })
              .margin({ top: 12 })
              .width('90%')

            TextInput({ placeholder: '请输入邮箱地址', text: this.userEmail })
              .onChange((val: string) => { this.userEmail = val; })
              .height(44)
              .borderRadius(8)
              .backgroundColor('#FFF5F5F5')
              .padding({ left: 12 })
              .margin({ top: 10 })
              .width('90%')

            Row() {
              Text('📍 所在城市:')
                .fontSize(15)
                .fontColor('#FF333333')
              Text(this.selectedCity)
                .fontSize(15)
                .fontColor('#FF3A86FF')
                .fontWeight(FontWeight.Medium)
            }
            .width('90%')
            .height(44)
            .backgroundColor('#FFF5F5F5')
            .borderRadius(8)
            .padding({ left: 12 })
            .margin({ top: 10 })
            .onClick(() => {
              const idx = this.cities.indexOf(this.selectedCity);
              this.selectedCity = this.cities[(idx + 1) % this.cities.length];
            })

            Row() {
              Checkbox()
                .size({ width: 20, height: 20 })
                .select(this.agreed)
                .onChange((val: boolean) => { this.agreed = val; })
              Text('  我已阅读并同意服务协议')
                .fontSize(14)
                .fontColor(this.agreed ? '#FF3A86FF' : '#FF999999')
            }
            .margin({ top: 12 })
            .width('90%')

            Button('提 交')
              .width('80%')
              .height(44)
              .backgroundColor(this.agreed ? '#FF3A86FF' : '#FFCCCCCC')
              .borderRadius(22)
              .fontColor(Color.White)
              .fontSize(16)
              .fontWeight(FontWeight.Medium)
              .margin({ top: 16, bottom: 4 })
              .enabled(this.agreed)
              .onClick(() => {
                this.getUIContext()?.getPromptAction().showDialog({
                  title: '提交成功',
                  message: `用户名:${this.userName}\n邮箱:${this.userEmail}\n城市:${this.selectedCity}`,
                  buttons: [{ text: '确定', color: '#FF3A86FF' }]
                })
              })
          }
          .alignItems(HorizontalAlign.Center)
          .width('100%')
        }

        // ④ 信息流列表
        CardContainer() {
          Column() {
            Text('📰 信息流列表(纵向排布 + 居中)')
              .fontSize(16)
              .fontWeight(FontWeight.Bold)
              .width('100%')
              .textAlign(TextAlign.Start)

            InfoListItem({
              icon: '📌',
              title: 'ColumnCenter 布局特点',
              summary: '所有子组件在水平方向自动居中,适合表单、列表、信息卡片等场景。'
            })

            InfoListItem({
              icon: '⚡',
              title: '核心 API',
              summary: 'Column + alignItems(HorizontalAlign.Center) + justifyContent(FlexAlign.Start)'
            })

            // 可展开详情区域
            Column() {
              Row() {
                Text('🔍')
                  .fontSize(22)
                  .margin({ right: 10 })
                Column() {
                  Text('justifyContent 控制垂直分布')
                    .fontSize(15)
                    .fontWeight(FontWeight.Medium)
                    .fontColor('#FF333333')
                  Text('点击展开查看不同排列效果')
                    .fontSize(12)
                    .fontColor('#FF999999')
                    .margin({ top: 2 })
                }
                .alignItems(HorizontalAlign.Start)
              }
              .width('90%')
              .padding(12)
              .onClick(() => {
                this.showDetail = !this.showDetail;
              })

              if (this.showDetail) {
                Column() {
                  JustifyDemoRow({ label: 'Start(顶部排列)', flexAlign: FlexAlign.Start })
                  Divider().height(1).color('#FFEEEEEE').margin({ top: 8, bottom: 8 })

                  JustifyDemoRow({ label: 'Center(垂直居中)', flexAlign: FlexAlign.Center })
                  Divider().height(1).color('#FFEEEEEE').margin({ top: 8, bottom: 8 })

                  JustifyDemoRow({ label: 'SpaceBetween(两端分布)', flexAlign: FlexAlign.SpaceBetween })
                  Divider().height(1).color('#FFEEEEEE').margin({ top: 8, bottom: 8 })

                  JustifyDemoRow({ label: 'SpaceAround(均匀分布)', flexAlign: FlexAlign.SpaceAround })
                }
                .width('90%')
                .backgroundColor('#FFF9F9FF')
                .borderRadius(8)
                .padding(12)
                .margin({ top: 4, bottom: 8 })
              }
            }
            .alignItems(HorizontalAlign.Center)
            .width('100%')
          }
          .alignItems(HorizontalAlign.Center)
          .width('100%')
        }

        // ⑤ 底部标签区
        CardContainer() {
          Row() {
            Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
              Text('📐 Column').fontSize(12).fontColor('#FF3A86FF').margin({ right: 4 })
              Text('+').fontSize(12).fontColor('#FF999999')
              Text(' alignItems(Center)').fontSize(12).fontColor('#FF3A86FF').margin({ left: 4, right: 4 })
              Text('+').fontSize(12).fontColor('#FF999999')
              Text(' justifyContent').fontSize(12).fontColor('#FF3A86FF').margin({ left: 4 })
            }
          }
          .width('100%')
          .justifyContent(FlexAlign.Center)
          .padding(12)
        }
      }
      .width('100%')
      .alignItems(HorizontalAlign.Center)       // 【关键】Column 子组件水平居中
      .justifyContent(FlexAlign.Start)           // 【关键】垂直方向从顶部开始排列
      .backgroundColor('#FFF0F4FF')
      .padding({ left: 16, right: 16 })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#FFF0F4FF')
  }
}

5.3 代码分段解读

5.3.1 状态管理

示例中使用了 @State 装饰器来管理组件的响应式状态:

@State userName: string = '';
@State userEmail: string = '';
@State agreed: boolean = false;
@State selectedCity: string = '北京';
@State showDetail: boolean = false;

@State 是 ArkTS 中最核心的状态装饰器之一。被 @State 修饰的变量具有以下特性:

  • 响应式:当变量的值发生变化时,依赖该变量的 UI 片段会自动重新渲染。
  • 私有性@State 变量只能被所属组件访问,不能从父组件传递进来。
  • 生命周期@State 变量的生命周期与组件实例绑定,组件销毁时变量也销毁。

在本示例中,每当用户在 TextInput 中输入内容时,userNameuserEmail 会实时更新;当用户勾选复选框时,agreed 改变并影响按钮的可点击状态和颜色。

5.3.2 Scroll 与 Column 的组合
Scroll() {
  Column() {
    // ...内容...
  }
  .width('100%')
  .alignItems(HorizontalAlign.Center)
  .justifyContent(FlexAlign.Start)
  .backgroundColor('#FFF0F4FF')
  .padding({ left: 16, right: 16 })
}
.width('100%')
.height('100%')

当 Column 中的内容超过屏幕高度时,需要外层包裹 Scroll 来实现滚动效果。需要注意几点:

  1. Scroll 必须设置 width('100%')height('100%') 以撑满屏幕。
  2. Column 作为 Scroll 的唯一子组件,不需要设置 height(它会根据内容自动扩展)。
  3. 背景色可以设置在 Column 上(内容区背景)或 Scroll 上(固定背景),根据设计需求决定。

在 HarmonyOS NEXT 6.1.1 中,Column 组件本身不再支持 overflow(Overflow.Scroll) 属性,因此必须使用 Scroll 容器来实现滚动,这是 API 24 相对于旧版本的一个重要变化。

5.3.3 CardContainer 卡片容器

每一个区域模块都包裹在 CardContainer 中,这是一个自定义的可复用组件,提供了统一的圆角白底卡片样式:

CardContainer() {
  // 卡片内容
}

CardContainer 的实现依赖 @BuilderParam 装饰器,这是一种组件内容插槽机制,类似于 Vue 的 slot 或 React 的 children prop:

@Component
struct CardContainer {
  @BuilderParam content: () => void = this.emptyBuilder;

  @Builder
  emptyBuilder() {}

  build() {
    Column() {
      this.content()
    }
    .width('100%')
    .backgroundColor(Color.White)
    .borderRadius(16)
    .padding(16)
    .margin({ top: 12 })
    .shadow({
      radius: 8,
      color: 'rgba(0, 0, 0, 0.06)',
      offsetX: 0,
      offsetY: 2
    })
    .alignItems(HorizontalAlign.Center)
  }
}

这里的 @BuilderParam content 定义了一个可接收自定义内容的占位符,在父组件中使用 CardContainer() { ... } 大括号中的内容会自动替换 this.content() 的位置。

5.3.4 表单中的 ColumnCenter 应用

表单部分是最典型的 ColumnCenter 应用场景:一组输入控件垂直排列,每个控件在水平方向居中:

Column() {
  Text('📝 表单示例(纵向列表 + 居中)')
    .fontSize(16)
    .fontWeight(FontWeight.Bold)

  TextInput({ placeholder: '请输入用户名', text: this.userName })
    .width('90%')         // 宽度 90%,左右留白

  TextInput({ placeholder: '请输入邮箱地址', text: this.userEmail })
    .width('90%')

  // ...更多表单项...

  Button('提 交')
    .width('80%')         // 按钮宽度 80%,比输入框窄一些
}
.alignItems(HorizontalAlign.Center)
.width('100%')

这里的两个设计要点:

  1. 子组件宽度设为 90%:如果不设宽度,TextInput 会默认填满 Column 的宽度(即 100%),此时 alignItems(Center) 仍然生效,但视觉上看不出居中效果。设置 90% 宽度后,输入框左右各有 5% 的间距,居中效果一目了然。

  2. 提交按钮宽度设为 80%:故意设置不同的宽度,让居中效果在视觉上更加突出。按钮居中于 Column,输入框也居中于 Column,所有组件共用同一个中线。

5.3.5 信息流列表

信息流列表使用 InfoListItem 自定义组件来展示每条信息:

InfoListItem({
  icon: '📌',
  title: 'ColumnCenter 布局特点',
  summary: '所有子组件在水平方向自动居中,适合表单、列表、信息卡片等场景。'
})

每个列表项内部其实是一个 Row 布局:图标在左,文字在右。但列表项本身在 Column 中居中排列,这就是 ColumnCenter 布局的灵活之处——外层 Column 负责整体的垂直排列和水平居中,内部的每个子组件(Row)保持自己的内部布局。


6. 自定义组件拆分与复用

6.1 组件拆分原则

在实际开发中,良好的组件拆分是保证代码可维护性的关键。在本文的示例中,我们按照以下原则进行了组件拆分:

  1. 单一职责:每个组件只负责一个明确的功能。
  2. 可复用性:判断一个 UI 片段是否可能在多处使用,如果是,就封装为独立组件。
  3. 可测试性:独立组件可以单独编译、测试和调试。
  4. 命名清晰性:组件名直观地反映其用途。

6.2 CardContainer — 卡片式容器

CardContainer 是一个通用容器组件,为包裹的内容提供卡片样式:

@Component
struct CardContainer {
  @BuilderParam content: () => void = this.emptyBuilder;

  @Builder
  emptyBuilder() {}

  build() {
    Column() {
      this.content()
    }
    .width('100%')
    .backgroundColor(Color.White)
    .borderRadius(16)
    .padding(16)
    .margin({ top: 12 })
    .shadow({
      radius: 8,
      color: 'rgba(0, 0, 0, 0.06)',
      offsetX: 0,
      offsetY: 2
    })
    .alignItems(HorizontalAlign.Center)
  }
}

设计要点

  • 使用 @BuilderParam content 接收子组件内容,这是 ArkTS 实现组件插槽的标准方式。
  • 卡片默认设置了 alignItems(HorizontalAlign.Center),包裹的内容自动居中。
  • .shadow() 方法为卡片添加阴影效果,参数依次为:阴影半径、颜色、偏移 X、偏移 Y。
  • 所有卡片共享统一的 borderRadius(16)(大圆角),形成一致的视觉风格。

6.3 InfoListItem — 信息列表项

InfoListItem 封装了"图标 + 标题 + 摘要 + 箭头"的列表项样式:

@Component
struct InfoListItem {
  @Prop icon: string = '📄';
  @Prop title: string = '';
  @Prop summary: string = '';

  build() {
    Row() {
      Text(this.icon).fontSize(24).margin({ right: 12 })

      Column() {
        Text(this.title)
          .fontSize(15)
          .fontWeight(FontWeight.Medium)
          .fontColor('#FF333333')
        Text(this.summary)
          .fontSize(12)
          .fontColor('#FF999999')
          .margin({ top: 4 })
          .lineHeight(18)
      }
      .alignItems(HorizontalAlign.Start)

      Text('›').fontSize(22).fontColor('#FFCCCCCC').margin({ left: 8 })
    }
    .width('90%')
    .padding(12)
    .backgroundColor('#FFFAFAFA')
    .borderRadius(10)
    .margin({ top: 8 })
  }
}

设计要点

  • 使用 @Prop 而不是 @State,表示数据从父组件传入,组件自身不修改数据。
  • 内部采用 Row + Column 的组合布局:Row 横向排布图标、文字区域和箭头;Column 纵向排列标题和摘要。
  • 文字区域的 .alignItems(HorizontalAlign.Start) 确保标题和摘要在垂直方向上都左对齐。
  • 外层的 width('90%') 使列表项在父容器中居中,左右各留 5% 间距。

6.4 JustifyDemoRow — justifyContent 效果演示

JustifyDemoRow 是本文示例中最具教学意义的组件,它通过三个彩色方块直观展示不同 justifyContent 的效果:

@Component
struct JustifyDemoRow {
  @Prop label: string = '';
  @Prop flexAlign: FlexAlign = FlexAlign.Start;

  build() {
    Column() {
      Text(this.label)
        .fontSize(13)
        .fontColor('#FF666666')
        .fontWeight(FontWeight.Medium)
        .margin({ bottom: 6 })

      Column() {
        SquareBlock({ color: '#FF3A86FF' })
        SquareBlock({ color: '#FF6C9FFF' })
        SquareBlock({ color: '#FFA3C4FF' })
      }
      .width('100%')
      .height(80)
      .backgroundColor('#FFF5F5FF')
      .borderRadius(8)
      .padding(8)
      .alignItems(HorizontalAlign.Center)
      .justifyContent(this.flexAlign)          // 由参数动态控制
    }
    .alignItems(HorizontalAlign.Center)
    .width('100%')
  }
}

关键点:属性名 flexAlign 不能命名为 align,因为在 ArkTS 中 alignCustomComponent 基类的保留属性名,直接使用会导致命名冲突编译错误。

6.5 SquareBlock — 装饰性色块

@Component
struct SquareBlock {
  @Prop color: string = '#FF3A86FF';

  build() {
    Column() {
      Text('■')
        .fontSize(14)
        .fontColor(Color.White)
    }
    .width(24)
    .height(24)
    .backgroundColor(this.color)
    .borderRadius(6)
    .alignItems(HorizontalAlign.Center)
    .justifyContent(FlexAlign.Center)
  }
}

这个组件很简单,但体现了组件复用的思想:同样的方块在不同位置使用时只需要传入不同的 color 值。

6.6 组件间数据流

整个页面组件的数据流可以概括为:

ColumnCenterDemo (主组件)
  │
  ├── @State → userName, userEmail, agreed, selectedCity, showDetail
  │
  ├── CardContainer (容器,无数据传递)
  │     └── 使用 @BuilderParam 接收内容插槽
  │
  ├── InfoListItem (接收 @Prop 数据)
  │     └── icon, title, summary 从父组件传入
  │
  └── JustifyDemoRow (接收 @Prop 数据)
        └── label, flexAlign 从父组件传入

数据流向是单向的:父组件通过 @Prop 将数据传递给子组件,子组件不能修改 @Prop 变量的值。需要修改数据的操作(如切换城市、勾选协议等)都在父组件中完成。


7. 常见布局误区与调试技巧

7.1 误区一:Column 未设置 width,居中效果不可见

错误示例

Column() {
  Text('Hello')
}
.alignItems(HorizontalAlign.Center)  // 效果不可见!

原因:Column 的默认宽度由最宽的子组件决定。当只有 Text 时,Column 宽度等于 Text 宽度,居中效果在视觉上无法体现。

正确做法

Column() {
  Text('Hello')
}
.width('100%')                         // 必须显式设置宽度
.alignItems(HorizontalAlign.Center)    // 效果正确显现

7.2 误区二:混淆 Column 和 Flex 的 alignItems 类型

错误示例(API 24 中):

Column() {
  // ...
}
.alignItems(ItemAlign.Center)  // 编译错误:类型不兼容

原因:在 HarmonyOS NEXT 6.1.1(API 24)中,Column.alignItems() 接收 HorizontalAlignRow.alignItems() 接收 VerticalAlign,只有 Flex.alignItems() 接收 ItemAlign

正确做法

Column().alignItems(HorizontalAlign.Center)   // Column → HorizontalAlign
Row().alignItems(VerticalAlign.Center)         // Row → VerticalAlign
Flex().alignItems(ItemAlign.Center)            // Flex → ItemAlign

7.3 误区三:在 Column 上使用 overflow

错误示例(API 24 中):

Column() {
  // 大量内容...
}
.overflow(Overflow.Scroll)  // 编译错误:Column 不支持 overflow

原因:API 24 中 Column 组件移除了 overflow 属性。这是 API 设计与安全层面的考量:Column 是布局容器而非滚动容器,滚动行为应该由专门的 Scroll 组件负责。

正确做法

Scroll() {
  Column() {
    // 大量内容...
  }
}
.height('100%')
.scrollable(ScrollDirection.Vertical)  // 或默认垂直

7.4 误区四:属性名与基类冲突

错误示例

@Component
struct MyComponent {
  @Prop align: FlexAlign = FlexAlign.Start;  // 编译错误!
}

原因alignCustomComponent 基类已经定义的属性,子组件不能重复声明。

正确做法

@Component
struct MyComponent {
  @Prop flexAlign: FlexAlign = FlexAlign.Start;  // 使用不冲突的名称
}

7.5 调试技巧

7.5.1 使用背景色辅助调试

在布局调试阶段,为容器设置不同的背景色是观察布局边界的最直接方式:

Column() {
  // ...
}
.backgroundColor('#FFE0E0E0')  // 浅灰色背景,方便观察容器范围

如果启用了 DevEco Studio 的 Inspector 工具,还可以查看组件的布局边界框、内边距和外边距的实时可视化。

7.5.2 使用 hilog 输出布局信息
import { hilog } from '@kit.PerformanceAnalysisKit';

// 在组件的方法中输出状态信息
aboutToAppear() {
  hilog.info(0x0000, 'LayoutDemo', 'Column width: %{public}s', JSON.stringify(this.width));
}
7.5.3 使用 build() 中的条件渲染
build() {
  Column() {
    if (this.debugMode) {
      // 只在调试模式下显示的辅助组件
      Text(`容器宽度: ${this.containerWidth}`)
        .fontSize(10)
        .fontColor(Color.Gray)
    }
    // 正式内容...
  }
}

这种方式可以在不修改布局逻辑的前提下,临时添加调试信息。


8. 性能优化建议

8.1 减少不必要的嵌套

虽然 Column 可以无限嵌套,但每一层嵌套都会增加布局计算的开销。在设计 UI 结构时,应尽量扁平化:

不推荐

Column() {
  Column() {
    Column() {
      Text('信息')
    }
  }
}

推荐(减少不必要的嵌套):

Column() {
  Text('信息')
}

8.2 使用 @State 最小化更新范围

@State 变量的变化会触发其所在组件的 build() 方法重新执行。如果 @State 定义在顶层组件,任何状态变化都会导致整个页面重绘。

优化策略:将频繁变化的状态下放到负责该区域的子组件中。

// 不推荐:频繁变化的状态在顶层
@Component
struct ParentPage {
  @State inputValue: string = '';
  build() {
    Column() {
      Text(this.inputValue)     // 每次输入都重绘
      BigStaticSection()        // 也会被不必要地重绘
    }
  }
}
// 推荐:状态下放到子组件
@Component
struct BigStaticSection {
  build() {
    // 静态内容,不会因为输入而重绘
  }
}

8.3 合理使用 LazyForEach

当 Column 中的列表项数量较大(超过 50 项)时,应该使用 ForEachLazyForEach 来生成列表项。LazyForEach 还支持懒加载,只渲染可视区域内的项目:

Column() {
  LazyForEach(this.dataSource, (item: Item, index: number) => {
    InfoListItem({
      icon: item.icon,
      title: item.title,
      summary: item.summary
    })
  }, (item: Item) => item.id)
}

不过需要注意,在 Column 中使用 LazyForEach 时,必须确保 Column 在 Scroll 中,否则所有项目会一次性渲染,失去懒加载的意义。

8.4 避免在 build() 中执行复杂计算

build() 方法可能会被频繁调用,因此应避免在其中执行耗时操作:

// 不推荐
build() {
  Column() {
    Text(this.computeExpensiveString())  // 每次 build 都重新计算
  }
}

// 推荐:提前计算好
@State computedString: string = '';

aboutToAppear() {
  this.computedString = this.computeExpensiveString();
}

build() {
  Column() {
    Text(this.computedString)
  }
}

8.5 使用 VStack 的替代方案

在某些极简场景下,如果不需要 Column 的完整功能,可以考虑使用更轻量的 VStack 布局(如果 API 24 版本可用),它占用的内存和布局计算开销更小。


9. 总结与扩展

9.1 核心要点回顾

通过本文的示例和讲解,我们深入理解了 ColumnCenter 布局的核心要点:

  1. Column 是垂直布局容器,子组件沿主轴(垂直方向)排列。
  2. alignItems(HorizontalAlign.Center) 实现水平居中——这是 ColumnCenter 的"Center"。
  3. justifyContent 控制垂直方向的排列方式,有 6 种可选值。
  4. width('100%') 必须显式设置,否则居中效果不可见。
  5. 配合 Scroll 实现可滚动的 ColumnCenter 布局。
  6. API 24 的枚举类型变化:Column 使用 HorizontalAlign,Row 使用 VerticalAlign,Flex 使用 ItemAlign

9.2 ColumnCenter 的实际应用场景

ColumnCenter 布局在实际开发中随处可见。以下是几个标准企业级应用页面的布局拆解:

登录页面

Column(Center)
├── Logo (居中)
├── TextInput (账号,90%宽度)
├── TextInput (密码,90%宽度)
├── Button (登录,80%宽度)
└── Text (忘记密码,居中)

个人中心页面

Column(Center)
├── 头像区域 (居中)
├── 用户名 (居中)
├── 个人信息卡片 (90%宽度,居中)
├── 设置列表 (90%宽度,居中)
└── 退出按钮 (80%宽度,居中)

商品详情页

Scroll
  └── Column(Center)
        ├── 轮播图 (100%宽度)
        ├── 商品信息卡片 (居中)
        ├── 规格选项 (居中)
        ├── 评价列表 (居中)
        └── 底部操作栏 (居中)

9.3 与其他布局模式的组合

ColumnCenter 极少单独使用,更多时候会与其他布局模式组合:

Scroll
  └── Column(Center, Start)
        ├── Row(Start)     ← 左侧标签,右侧内容
        │   ├── Text('标题')
        │   └── Text('内容')
        ├── Row(SpaceBetween)  ← 左右分布
        │   ├── Text('取消')
        │   └── Text('确定')
        └── Flex(Center)   ← 弹性布局
              ├── Icon × 3
              └── Icon × 2

9.4 扩展思考:自适应与响应式

在 ColumnCenter 布局的基础上,结合 constraintSize 和媒体查询,可以实现自适应和响应式布局:

Column() {
  // ...
}
.constraintSize({
  minWidth: 300,
  maxWidth: 600
})
.alignItems(HorizontalAlign.Center)

这种方式可以让 Column 在大屏设备上(如平板、折叠屏)自动限制最大宽度,保持内容在合理范围内居中,避免文字行过长影响阅读体验。

Logo

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

更多推荐