鸿蒙原生 ArkTS 布局精讲(一):ColumnCenter 垂直居中布局全面解析


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)中,Column 的 alignItems 方法接收 HorizontalAlign 枚举值:
enum HorizontalAlign {
Start, // 左对齐
Center, // 居中对齐 —— 这正是 ColumnCenter 的核心
End // 右对齐
}
重要提醒:在早期的 HarmonyOS SDK 版本(API 12 之前)中,Column.alignItems() 接受的是 ItemAlign 枚举。但在 API 24 中,Column 和 Row 的对齐枚举已经分拆:
Column→alignItems(HorizontalAlign)Row→alignItems(VerticalAlign)Flex→alignItems(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
每一层嵌套的容器都有自己独立的 alignItems 和 justifyContent 设置,互不影响。这是构建复杂布局的关键灵活性。
3. ColumnCenter 布局模式详解
3.1 什么是 ColumnCenter
ColumnCenter 是 Column 布局中最常用的一种模式,其核心配置为:
Column() {
// 子组件...
}
.width('100%')
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Start)
这种配置的效果是:
- 所有子组件在水平方向居中对齐(alignItems 的作用)
- 子组件从顶部开始依次排列(justifyContent 的默认行为)
- 容器撑满父容器宽度(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 容器分别使用了 Start、Center 和 End 三种对齐方式,内部的文字会分别出现在顶部、中间和底部,非常直观地展示了 justifyContent 的效果。
4. alignItems 与 justifyContent 的协作机制
4.1 核心协作关系
alignItems 和 justifyContent 是 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 中输入内容时,userName 和 userEmail 会实时更新;当用户勾选复选框时,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 来实现滚动效果。需要注意几点:
Scroll必须设置width('100%')和height('100%')以撑满屏幕。Column作为Scroll的唯一子组件,不需要设置height(它会根据内容自动扩展)。- 背景色可以设置在
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%')
这里的两个设计要点:
-
子组件宽度设为
90%:如果不设宽度,TextInput 会默认填满 Column 的宽度(即 100%),此时alignItems(Center)仍然生效,但视觉上看不出居中效果。设置90%宽度后,输入框左右各有 5% 的间距,居中效果一目了然。 -
提交按钮宽度设为
80%:故意设置不同的宽度,让居中效果在视觉上更加突出。按钮居中于 Column,输入框也居中于 Column,所有组件共用同一个中线。
5.3.5 信息流列表
信息流列表使用 InfoListItem 自定义组件来展示每条信息:
InfoListItem({
icon: '📌',
title: 'ColumnCenter 布局特点',
summary: '所有子组件在水平方向自动居中,适合表单、列表、信息卡片等场景。'
})
每个列表项内部其实是一个 Row 布局:图标在左,文字在右。但列表项本身在 Column 中居中排列,这就是 ColumnCenter 布局的灵活之处——外层 Column 负责整体的垂直排列和水平居中,内部的每个子组件(Row)保持自己的内部布局。
6. 自定义组件拆分与复用
6.1 组件拆分原则
在实际开发中,良好的组件拆分是保证代码可维护性的关键。在本文的示例中,我们按照以下原则进行了组件拆分:
- 单一职责:每个组件只负责一个明确的功能。
- 可复用性:判断一个 UI 片段是否可能在多处使用,如果是,就封装为独立组件。
- 可测试性:独立组件可以单独编译、测试和调试。
- 命名清晰性:组件名直观地反映其用途。
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 中 align 是 CustomComponent 基类的保留属性名,直接使用会导致命名冲突编译错误。
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() 接收 HorizontalAlign,Row.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; // 编译错误!
}
原因:align 是 CustomComponent 基类已经定义的属性,子组件不能重复声明。
正确做法:
@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 项)时,应该使用 ForEach 或 LazyForEach 来生成列表项。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 布局的核心要点:
- Column 是垂直布局容器,子组件沿主轴(垂直方向)排列。
alignItems(HorizontalAlign.Center)实现水平居中——这是 ColumnCenter 的"Center"。justifyContent控制垂直方向的排列方式,有 6 种可选值。width('100%')必须显式设置,否则居中效果不可见。- 配合
Scroll实现可滚动的 ColumnCenter 布局。 - 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 在大屏设备上(如平板、折叠屏)自动限制最大宽度,保持内容在合理范围内居中,避免文字行过长影响阅读体验。
更多推荐



所有评论(0)