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

鸿蒙原生 ArkTS 布局深度解析:ColumnStart 顶部起始分布模式(API 24)

作者:AtomCode
版本:HarmonyOS NEXT API 24(6.1.1 Release)
源码:本仓库 ColumnStartDemo.ets
字数:约 10000 字


一、引言

1.1 从"对齐"到"排列"——布局维度的跃迁

在鸿蒙原生 ArkTS 布局体系中,Column 是最基础的纵向布局容器。此前我们分别探讨了 ColumnCenterColumnEndColumnBaselineColumnStretch 四种模式——它们都聚焦于 alignItems 属性,即交叉轴(水平方向)的对齐方式

本文将视角从"交叉轴"转向"主轴",深入剖析 justifyContent(FlexAlign.Start)——即 ColumnStart 纵向顶部起始分布模式。这是 Column 的默认排列行为,也是所有纵向布局中最自然、最直观、最基础的模式。

这里有一个关键的思想转变:

对比维度 前四篇文章(ColumnCenter 等) 本文(ColumnStart)
核心属性 alignItems justifyContent
控制方向 交叉轴(水平) 主轴(垂直)
控制效果 子组件在水平方向的对齐 子组件在垂直方向的排列
类比 摆放在货架上的左右位置 货架层间距的分布策略

简单来说,alignItems 管"左中右",justifyContent 管"上中下"。理解这一区分,是掌握鸿蒙 ArkTS 布局体系的基石。

1.2 为什么 ColumnStart 值得深入理解

justifyContent(FlexAlign.Start) 是 Column 的默认值,这意味着即使不写这行代码,Column 的子组件也会从顶部开始排列。但"默认"不等于"简单",恰恰相反,Start 模式背后涉及了 Flexbox 布局中最核心的几个概念:

  • 主轴剩余空间的分配策略:当子组件总高度小于容器高度时,多余空间如何处理?
  • 内容压缩与溢出:当子组件总高度超过容器高度时,是溢出还是裁剪?
  • 与 alignItems 的协同:当主轴排列和交叉轴对齐同时设置时,如何协调工作?
  • 嵌套容器的布局传递:在多层嵌套的 Column 中,Start 模式如何影响子布局?

这些技术细节在日常开发中很容易被忽略,但一旦遇到复杂的布局需求(如自适应屏幕、动态内容、多端适配),这些底层原理就会成为解决问题的关键。

1.3 本文的示例应用

为了更好地阐释 ColumnStart 的工作原理,我们构建了一个待办事项(Todo List) 演示应用:

ColumnStartDemo.ets
├── 顶部状态栏          ← 导航 + 标题
├── 进度概览卡片        ← 已完成/总数/完成率 + 进度条
├── 新增待办输入区      ← 输入框 + 添加按钮
├── 待办事项列表        ← 可选中、可删除的列表项
├── 底部操作栏          ← 标记完成 / 重置 / 全部完成
└── 布局说明区          ← 六大 justifyContent 对比表格

这个应用虽然简单,却涵盖了日常开发中 90% 的纵向布局场景。通过它,我们将一步步剖析 ColumnStart 的每一个细节。


二、justifyContent 深度原理

2.1 主轴与交叉轴的再思考

在深入 justifyContent 之前,我们有必要重新审视一下 主轴(Main Axis)交叉轴(Cross Axis) 这两个概念。

对于 Column 组件:

┌─────────────────────────────────────┐
│          交叉轴(水平方向)           │
│     ←─────── 对齐 ────────→         │
│                                      │
│  ┌──────────────────────────────┐   │
│  │        子组件 A              │   │  ↑
│  └──────────────────────────────┘   │  │
│          ↑  margin 间距              │  主
│  ┌──────────────────────────────┐   │  轴
│  │        子组件 B              │   │  (
│  └──────────────────────────────┘   │  垂
│          ↑  margin 间距              │  直
│  ┌──────────────────────────────┐   │  方
│  │        子组件 C              │   │  向
│  └──────────────────────────────┘   │  )
│                                      │  ↓
│       ←── alignItems 控制 ──→       │
└─────────────────────────────────────┘

关键理解:

  • 主轴(Main Axis):垂直方向,控制的是子组件的排列顺序和间距,由 justifyContent 管理。
  • 交叉轴(Cross Axis):水平方向,控制的是子组件的对齐位置,由 alignItems 管理。
  • 两个轴是正交的:修改主轴属性不会影响交叉轴的行为,反之亦然。

这一正交性意味着:ColumnStart 模式(主轴 Start)可以与任意 alignItems 组合,形成不同的布局效果:

组合模式 justifyContent alignItems 视觉效果
Start + Start FlexAlign.Start HorizontalAlign.Start 左上角起始,自然阅读流
Start + Center FlexAlign.Start HorizontalAlign.Center 顶部居中,表单常用
Start + End FlexAlign.Start HorizontalAlign.End 顶部靠右,操作面板

本文演示的正是 Start + Start 组合,即"左上角起始"的阅读布局。

2.2 FlexAlign 六大枚举值详解

FlexAlign 是 ArkTS 中控制主轴排列策略的枚举,包含六个值。我们将逐一分析其行为:

Column() {
  ChildA()
  ChildB()
  ChildC()
}

为了便于理解,我们假设容器高度为 600vp,三个子组件各占 100vp,总高度为 300vp,剩余空间为 300vp。

FlexAlign.Start(默认)
┌──────────────────────────────┐
│  ┌─────── Child A ────────┐  │
│  ├─────────────────────────┤  │
│  ├─────── Child B ────────┤  │
│  ├─────────────────────────┤  │
│  ├─────── Child C ────────┤  │
│  │                        │  │
│  │     剩余空间(300vp)    │  │
│  │     全部留在底部        │  │
│  │                        │  │
│  │                        │  │
│  │                        │  │
└──────────────────────────────┘

行为:子组件从容器顶部开始紧密排列,所有剩余空间保留在容器底部。

特点:这是最自然的排列方式——内容从上到下依次展示,阅读顺序与代码顺序一致。

FlexAlign.Center
┌──────────────────────────────┐
│  │                        │  │
│  │     剩余空间(150vp)    │  │
│  │                        │  │
│  ├─────── Child A ────────┤  │
│  ├─────────────────────────┤  │
│  ├─────── Child B ────────┤  │
│  ├─────────────────────────┤  │
│  ├─────── Child C ────────┤  │
│  │                        │  │
│  │     剩余空间(150vp)    │  │
│  │                        │  │
└──────────────────────────────┘

行为:子组件整体在容器垂直方向居中,剩余空间均匀分配在顶部和底部。

特点:适合内容较少、需要视觉聚焦的场景(如启动页、弹窗内容)。

FlexAlign.End
┌──────────────────────────────┐
│  │                        │  │
│  │                        │  │
│  │                        │  │
│  │     剩余空间(300vp)    │  │
│  │                        │  │
│  ├─────── Child A ────────┤  │
│  ├─────────────────────────┤  │
│  ├─────── Child B ────────┤  │
│  ├─────────────────────────┤  │
│  ├─────── Child C ────────┤  │
└──────────────────────────────┘

行为:子组件整体在容器底部排列,所有剩余空间保留在顶部。

特点:适合底部操作栏、聊天输入框等场景。

FlexAlign.SpaceBetween
┌──────────────────────────────┐
│  ├─────── Child A ────────┤  │
│  │                        │  │
│  │     剩余空间(150vp)    │  │
│  │                        │  │
│  ├─────── Child B ────────┤  │
│  │                        │  │
│  │     剩余空间(150vp)    │  │
│  │                        │  │
│  ├─────── Child C ────────┤  │
└──────────────────────────────┘

行为:子组件均匀分布,两端无间距,中间间距相等。

特点:适合需要分散排列、但不需要两端留白的长列表。

FlexAlign.SpaceAround
┌──────────────────────────────┐
│  │    间距(75vp)          │  │
│  ├─────── Child A ────────┤  │
│  │    间距(150vp)         │  │
│  ├─────── Child B ────────┤  │
│  │    间距(150vp)         │  │
│  ├─────── Child C ────────┤  │
│  │    间距(75vp)          │  │
└──────────────────────────────┘

行为:子组件均匀分布,两端间距为中间间距的一半

特点:适合需要两端留白但又不想留白太多的场景。

FlexAlign.SpaceEvenly
┌──────────────────────────────┐
│  │    间距(100vp)         │  │
│  ├─────── Child A ────────┤  │
│  │    间距(100vp)         │  │
│  ├─────── Child B ────────┤  │
│  │    间距(100vp)         │  │
│  ├─────── Child C ────────┤  │
│  │    间距(100vp)         │  │
└──────────────────────────────┘

行为:子组件均匀分布,所有间距(包括两端)相等

特点:间距最均匀,适合需要精确等距排列的场景。

2.3 一个关键前提:容器高度约束

justifyContent 的效果有一个重要前提:容器必须有明确的高度约束

如果 Column 没有设置高度(即默认由内容撑开),那么子组件的总高度刚好等于容器高度,剩余空间为 0,所有 justifyContent 分布策略的效果都一样:

// ❌ 错误示范:容器高度由内容撑开,justifyContent 无效
Column() {
  Text('A').height(100)
  Text('B').height(100)
  Text('C').height(100)
}
// 容器高度 = 300(内容撑开),剩余空间 = 0
// justifyContent 无论设置什么值,视觉上都是 Start 效果

// ✅ 正确示范:容器高度明确,justifyContent 生效
Column() {
  Text('A').height(100)
  Text('B').height(100)
  Text('C').height(100)
}
.height('100%')  // 或 .height(600)
// 容器高度 = 屏幕高度(假设 800vp),剩余空间 = 500vp
// justifyContent 的效果才能体现

这就是为什么在 ColumnStart 的根容器中,我们必须设置 .height('100%')——不设置,justifyContent 就失去了发挥的空间。

2.4 justifyContent 与 alignItems 的协同工作

justifyContentalignItems 同时设置时,它们在两个正交的维度上协同工作:

Column() {
  ChildA()
  ChildB()
  ChildC()
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Start)   // 主轴:垂直方向,从顶部排列
.alignItems(HorizontalAlign.Start)  // 交叉轴:水平方向,靠左对齐

可以将其想象为在二维坐标系中分配位置:

  • Y 轴(垂直):由 justifyContent 决定每个子组件的 Y 坐标
  • X 轴(水平):由 alignItems 决定每个子组件的 X 坐标

两者互不干扰,但共同决定了子组件的最终位置。这种正交性使得开发者可以独立地调整两个方向的对齐策略,而无需担心相互影响。


三、ColumnStart 实战:待办事项应用

3.1 应用架构总览

演示应用包含两个关键文件:

entry/src/main/ets/pages/
├── Index.ets              # 首页 — 导航入口
└── ColumnStartDemo.ets    # 演示页 — ColumnStart 布局示例(809 行)

以及路由注册文件:

entry/src/main/resources/base/profile/
└── main_pages.json        # 添加 "pages/ColumnStartDemo" 路由

3.2 状态管理设计

ColumnStartDemoPage 结构体中,我们定义了五个 @State 状态变量:

// ---------- 状态变量 ----------
/* 待办事项输入框文本 */
@State todoText: string = '';
/* 待办事项列表 */
@State todoList: string[] = [
  '学习 ColumnStart 布局',
  '编写 ArkTS 示例代码',
  '运行测试 — 验证效果',
  '撰写技术博客',
];
/* 当前选中的待办项索引,-1 表示未选中 */
@State selectedIndex: number = -1;
/* 已完成事项数量 */
@State completedCount: number = 0;
/* 模拟加载状态 */
@State isLoading: boolean = false;

状态粒度的设计原则

这里将 todoText(字符串)、todoList(数组)、selectedIndex(数字)、completedCount(数字)和 isLoading(布尔值)拆分为五个独立的 @State 变量,而非合并为一个对象。这样做的好处是:

  1. 精准重渲染:修改 selectedIndex 时,只有依赖选中状态的 UI 片段会重新渲染,其他部分保持不变。
  2. 类型安全:每个变量都有明确的类型,TypeScript 编译器可以在编译期捕获类型错误。
  3. 代码可读性:变量名本身即文档,读者一眼就能看出每个状态的用途。

数组状态的不可变性

值得注意的是,todoList 的修改方式遵循了不可变更新原则:

// 添加待办:使用展开运算符创建新数组
this.todoList = [text, ...this.todoList];

// 删除待办:使用 filter 创建新数组
this.todoList = this.todoList.filter(
  (_: string, i: number) => i !== index
);

// 重置:直接赋新数组
this.todoList = [
  '学习 ColumnStart 布局',
  '编写 ArkTS 示例代码',
  '运行测试 — 验证效果',
  '撰写技术博客',
];

这是 ArkTS 响应式系统的要求——@State 装饰的数组通过引用比较来判断是否变化,只有赋新数组才能触发 UI 更新。直接调用 pushsplice 等方法修改原数组不会触发重渲染。

3.3 根级 Column 容器的配置

整个演示页面的根容器是 ColumnStart 布局的核心:

build() {
  Column() {
    this.buildTopBar()              // 区域一:顶部状态栏
    this.buildProgressCard()        // 区域二:进度概览卡片
    this.buildInputSection()        // 区域三:新增待办输入区
    this.buildTodoListSection()     // 区域四:待办事项列表
    this.buildBottomActions()       // 区域五:底部操作栏
    this.buildLayoutNote()          // 区域六:布局说明文字
  }
  .width('100%')
  .height('100%')
  .justifyContent(FlexAlign.Start)   // ⬅️ ColumnStart 核心
  .alignItems(HorizontalAlign.Start)  // 交叉轴左对齐
  .padding({
    left: 16,
    right: 16,
    top: 0,
    bottom: 0,
  })
  .backgroundColor('#F8F9FA')
}

关键配置解读

1. justifyContent(FlexAlign.Start) — 本模式的核心

这行代码设置了主轴(垂直方向)的排列策略为"从顶部起始"。这意味着:

  • 六个 buildXxx 区域按代码顺序从顶部依次向下排列
  • 区域之间只有通过 margin 手动设置的间距
  • 没有任何自动分配的额外间距
  • 如果内容总高度不足一屏,底部会留有空白

2. alignItems(HorizontalAlign.Start) — 辅助配置

这行代码设置了交叉轴(水平方向)的对齐方式为"左对齐":

  • 所有子组件在水平方向上靠左对齐
  • 这与主轴 Start 配合,形成了"左上角起始"的阅读布局
  • 可以改为 CenterEnd 观察水平对齐的变化,不影响垂直排列

3. height('100%') — 必要前提

如前所述,justifyContent 需要容器有明确的高度约束才能生效。height('100%') 确保容器填满父容器高度,为 justifyContent 的分布策略提供可分配的空间。

3.4 @Builder 拆分 UI 片段

与之前几篇文章一致,我们使用 @Builder 装饰器将 UI 拆分为独立的构建方法。每个 @Builder 方法只负责一个视觉区域:

@Builder
buildTopBar(): void {
  // 顶部状态栏:返回按钮 + 标题
}

@Builder
buildProgressCard(): void {
  // 进度概览卡片:已完成/总数/完成率
}

@Builder
buildInputSection(): void {
  // 新增待办输入区:输入框 + 添加按钮
}

@Builder
buildTodoListSection(): void {
  // 待办事项列表:可选中、可删除
}

@Builder
buildBottomActions(): void {
  // 底部操作栏:标记完成 / 重置 / 全部完成
}

@Builder
buildLayoutNote(): void {
  // 布局说明区:justifyContent 对比表格
}

每个区域的构建方法内部可能还有自己的 Column / Row 布局,但作为根 Column 的子节点时,它们都受 justifyContent(FlexAlign.Start)alignItems(HorizontalAlign.Start) 的约束。

3.5 辅助 Builder 的抽象

对于重复出现的 UI 模式,我们进一步抽象为 @Builder 方法:

/**
 * 构建说明列表项(内部辅助 Builder)
 * @param title   要点标题
 * @param desc    要点描述
 */
@Builder
buildNoteItem(title: string, desc: string): void {
  Column() {
    Text(title)
      .fontSize(12)
      .fontColor('#6C5CE7')
      .fontWeight(FontWeight.Medium)
    Text(desc)
      .fontSize(11)
      .fontColor('#636E72')
      .margin({ top: 2 })
      .lineHeight(15)
  }
  .alignItems(HorizontalAlign.Start)
  .width('100%')
  .margin({ top: 6, bottom: 2 })
}

/**
 * 构建对比表格数据行
 * @param mode    排列模式名称
 * @param desc    效果描述
 */
@Builder
buildTableRow(mode: string, desc: string): void {
  Row() {
    Text(mode)
      .fontSize(11)
      .fontColor('#2D3436')
      .fontWeight(FontWeight.Medium)
      .layoutWeight(1)
    Text(desc)
      .fontSize(11)
      .fontColor('#636E72')
      .layoutWeight(2)
  }
  .width('100%')
  .padding({ top: 5, bottom: 5, left: 8, right: 8 })
  .border({
    width: { bottom: 1 },
    color: '#F0F0F0',
  })
}

@Builder 方法支持参数这一特性(API 24 增强)显著提升了 UI 代码的复用能力。一次定义,多处调用,且保持类型安全。


四、六大区域的设计与实现

4.1 区域一:顶部状态栏

状态栏采用 Row 水平布局,实现"返回按钮靠左 + 标题靠右"的经典导航模式:

@Builder
buildTopBar(): void {
  Row() {
    // 返回按钮
    Row() {
      Text('←').fontSize(18).fontColor('#2D3436')
      Text(' 返回').fontSize(14).fontColor('#2D3436')
               .margin({ left: 4 })
    }
    .alignItems(VerticalAlign.Center)
    .onClick(() => { router.back() })

    Blank()  // 弹性空白

    // 页面标题
    Text('ColumnStart 布局')
      .fontSize(17).fontWeight(FontWeight.Bold)
      .fontColor('#2D3436')
  }
  .width('100%').height(48)
  .alignItems(VerticalAlign.Center)
  .padding({ left: 0, right: 4 })
  .margin({ top: 8, bottom: 4 })
}

布局要点

  • Row 作为子组件,在父 Column(Start) 中从顶部排列
  • Row 内部的 Blank() 组件将左右两部分推开(Blank 是弹性空间填充器)
  • router.back() 调用路由返回功能,需要导入 import router from '@ohos.router'

4.2 区域二:进度概览卡片

进度卡片展示了三个统计维度(已完成、总事项、完成率),通过 layoutWeight 实现三等分:

@Builder
buildProgressCard(): void {
  Column() {
    // 卡片标题行
    Row() {
      Text('📋').fontSize(18)
      Text('今日待办').fontSize(16)
        .fontWeight(FontWeight.Bold).fontColor('#2D3436')
        .margin({ left: 8 })
    }
    .alignItems(VerticalAlign.Center).width('100%')

    // 进度信息行(三等分)
    Row() {
      // 已完成
      Column() {
        Text(`${this.completedCount}`)
          .fontSize(28).fontWeight(FontWeight.Bold)
          .fontColor('#6C5CE7')
        Text('已完成').fontSize(11)
          .fontColor('#B2BEC3').margin({ top: 2 })
      }
      .alignItems(HorizontalAlign.Center).layoutWeight(1)

      // 总事项
      Column() {
        Text(`${this.todoList.length}`)
          .fontSize(28).fontWeight(FontWeight.Bold)
          .fontColor('#2D3436')
        Text('总事项').fontSize(11)
          .fontColor('#B2BEC3').margin({ top: 2 })
      }
      .alignItems(HorizontalAlign.Center).layoutWeight(1)

      // 完成率
      Column() {
        Text(
          this.todoList.length > 0
            ? `${Math.round((this.completedCount / this.todoList.length) * 100)}%`
            : '0%'
        )
          .fontSize(28).fontWeight(FontWeight.Bold)
          .fontColor('#00B894')
        Text('完成率').fontSize(11)
          .fontColor('#B2BEC3').margin({ top: 2 })
      }
      .alignItems(HorizontalAlign.Center).layoutWeight(1)
    }
    .alignItems(VerticalAlign.Center).width('100%')
    .margin({ top: 16 })

    // 进度条
    Progress({
      value: this.todoList.length > 0
        ? (this.completedCount / this.todoList.length) * 100 : 0,
      total: 100, type: ProgressType.Linear,
    })
      .width('100%').height(6).color('#6C5CE7')
      .backgroundColor('#E8E8E8').borderRadius(3)
      .margin({ top: 12 })
  }
  .width('100%').padding(16)
  .backgroundColor('#FFFFFF').borderRadius(12)
  .margin({ top: 8 })
  .shadow({ radius: 6, color: 'rgba(0, 0, 0, 0.06)', offsetX: 0, offsetY: 2 })
}

布局要点

  • layoutWeight(1)Row 中实现等宽分布,三个 Column 各占三分之一宽度
  • 卡片使用 shadow 属性添加阴影,增加视觉层次感
  • Progress 组件的 value 动态绑定计算后的完成率百分比
  • 卡片在 Column(Start) 中作为第二子组件,紧跟在状态栏下方

4.3 区域三:新增待办输入区

输入区采用 Row 水平布局,输入框和按钮并排显示:

@Builder
buildInputSection(): void {
  Column() {
    Text('✏️ 新增待办')
      .fontSize(15).fontWeight(FontWeight.Medium)
      .fontColor('#2D3436').margin({ bottom: 8 })

    Row() {
      TextInput({ placeholder: '输入待办事项...', text: this.todoText })
        .layoutWeight(1).height(44)
        .backgroundColor('#FFFFFF').borderRadius(8)
        .padding({ left: 12 }).fontSize(14)
        .placeholderColor('#B2BEC3')
        .onChange((value: string) => { this.todoText = value })

      Button('添加')
        .fontSize(14).fontColor('#FFFFFF')
        .backgroundColor('#6C5CE7').borderRadius(8)
        .height(44).padding({ left: 20, right: 20 })
        .margin({ left: 10 })
        .onClick(() => { this.addTodoItem() })
    }
    .alignItems(VerticalAlign.Center).width('100%')
  }
  .alignItems(HorizontalAlign.Start).width('100%')
  .margin({ top: 16 })
}

布局要点

  • TextInput 使用 layoutWeight(1) 占据剩余宽度,Button 固定宽度
  • 输入框的 text 属性双向绑定到 @State todoText
  • onChange 回调更新状态,触发 UI 自动重渲染
  • 点击"添加"按钮调用 addTodoItem() 方法,将新项添加到列表顶部

添加逻辑

addTodoItem(): void {
  const text: string = this.todoText.trim()
  if (text.length > 0) {
    // 使用展开运算符将新项添加到数组开头
    this.todoList = [text, ...this.todoList]
    this.todoText = ''
  }
}

这里特意使用 [text, ...this.todoList] 将新项添加到数组开头,而非 push 到末尾。这体现了 Start 的"顶部"语义——新添加的事项出现在列表顶部,视觉上"从顶部开始"。

4.4 区域四:待办事项列表

列表区域是 ColumnStart 中最复杂的部分,展示了 List + ForEach 的组合使用:

@Builder
buildTodoListSection(): void {
  Column() {
    // 区域标题(含事项数量统计)
    Row() {
      Text('📌 待办列表')
        .fontSize(15).fontWeight(FontWeight.Medium)
        .fontColor('#2D3436')
      Blank()
      Text(`${this.todoList.length}`)
        .fontSize(11).fontColor('#B2BEC3').margin({ right: 4 })
    }
    .alignItems(VerticalAlign.Center).width('100%')
    .margin({ bottom: 10 })

    // 待办事项列表
    List({ space: 8 }) {
      ForEach(this.todoList, (item: string, index: number) => {
        ListItem() {
          Row() {
            // 选中状态圆点
            Circle()
              .width(20).height(20)
              .fill(this.selectedIndex === index ? '#6C5CE7' : '#FFFFFF')
              .stroke(this.selectedIndex === index ? '#6C5CE7' : '#CCCCCC')
              .strokeWidth(2).margin({ right: 12 })
              .onClick(() => { this.toggleSelect(index) })

            // 待办文字
            Text(item).fontSize(14)
              .fontColor(this.selectedIndex === index ? '#6C5CE7' : '#2D3436')
              .fontWeight(this.selectedIndex === index
                ? FontWeight.Medium : FontWeight.Normal)
              .layoutWeight(1)

            // 删除按钮
            Text('✕').fontSize(14).fontColor('#E17055')
              .padding({ left: 10, right: 6 })
              .onClick(() => { this.removeTodoItem(index) })
          }
          .width('100%').height(48)
          .padding({ left: 14, right: 6 })
          .backgroundColor(this.selectedIndex === index
            ? 'rgba(108, 92, 231, 0.06)' : '#FFFFFF')
          .borderRadius(10)
          .alignItems(VerticalAlign.Center)
          .onClick(() => { this.toggleSelect(index) })
        }
      }, (item: string) => item)
    }
    .width('100%').height(280).borderRadius(8)
    .padding({ top: 4, bottom: 4 })
    .divider({
      strokeWidth: 1, color: '#F0F0F0',
      startMargin: 46, endMargin: 10,
    })

    // 空状态提示
    if (this.todoList.length === 0) {
      Text('🎉 所有待办事项已完成!')
        .fontSize(13).fontColor('#B2BEC3')
        .margin({ top: 20, bottom: 10 })
        .width('100%').textAlign(TextAlign.Center)
    }
  }
  .alignItems(HorizontalAlign.Start).width('100%')
  .margin({ top: 16 })
}

列表布局要点

1. List 组件的 space 属性

List({ space: 8 }) 设置了列表项之间的间距为 8vp。与 margin 不同,space 是 List 的内置属性,只能控制列表项之间的间距,不能控制首尾间距。

2. divider 属性

.divider({ strokeWidth: 1, color: '#F0F0F0', startMargin: 46, endMargin: 10 }) 为列表项之间添加了分隔线,startMarginendMargin 控制分隔线的左右缩进,避免分隔线从列表项的最左侧开始。

3. ForEach 的 key 生成器

ForEach 的第三个参数是 key 生成器(item: string) => item,使用每个待办事项的文本内容作为唯一标识。这有助于 List 在增删操作时正确复用组件实例,避免 UI 闪烁或状态丢失。

4. 空状态处理

todoList.length === 0 时,显示提示文字。这个条件渲染使用 ArkTS 的 if 语句直接在组件树中编写,清晰直观。

4.5 区域五:底部操作栏

操作栏包含三个按钮,演示了 ColumnStart 中"按钮从顶部依次排列"的效果:

@Builder
buildBottomActions(): void {
  Column() {
    // 按钮一:标记完成
    Button() {
      Row() {
        Text('✅').fontSize(14)
        Text(' 标记完成').fontSize(14)
          .fontColor('#FFFFFF').fontWeight(FontWeight.Medium)
          .margin({ left: 4 })
      }
      .alignItems(VerticalAlign.Center)
    }
    .width('100%').height(44).backgroundColor('#00B894')
    .borderRadius(10)
    .onClick(() => {
      if (this.selectedIndex >= 0) {
        this.completedCount++
        this.removeTodoItem(this.selectedIndex)
        this.selectedIndex = -1
      }
    })

    // 按钮二:重置样例
    Button() {
      Row() {
        Text('🔄').fontSize(14)
        Text(' 重置样例').fontSize(14)
          .fontColor('#FFFFFF').fontWeight(FontWeight.Medium)
          .margin({ left: 4 })
      }
      .alignItems(VerticalAlign.Center)
    }
    .width('100%').height(44).backgroundColor('#6C5CE7')
    .borderRadius(10).margin({ top: 10 })
    .onClick(() => { this.resetSampleData() })

    // 按钮三:全部完成
    Button() {
      Row() {
        Text('⚡').fontSize(14)
        Text(' 全部完成').fontSize(14)
          .fontColor('#FFFFFF').fontWeight(FontWeight.Medium)
          .margin({ left: 4 })
      }
      .alignItems(VerticalAlign.Center)
    }
    .width('100%').height(44).backgroundColor('#E17055')
    .borderRadius(10).margin({ top: 10 })
    .onClick(() => {
      this.completedCount += this.todoList.length
      this.todoList = []
      this.selectedIndex = -1
    })
  }
  .alignItems(HorizontalAlign.Start).width('100%')
  .margin({ top: 16 })
}

布局要点

  • 三个按钮在 Column(Start) 中从顶部依次排列
  • 每个按钮之间通过 margin({ top: 10 }) 保持 10vp 间距
  • 按钮使用 width('100%') 撑满父容器宽度,在 alignItems(Start) 下靠左对齐
  • 每个按钮内部使用 Row 实现图标和文字的水平排列

4.6 区域六:布局说明区

最后一个区域包含完整的 justifyContent 对比表格,作为学习参考:

@Builder
buildLayoutNote(): void {
  Column() {
    Divider().width('100%').height(1).color('#E0E0E0')

    Text('ColumnStart 布局核心')
      .fontSize(13).fontWeight(FontWeight.Medium)
      .fontColor('#2D3436').margin({ top: 12 }).width('100%')

    // 关键点列表
    Column() {
      this.buildNoteItem(
        '① justifyContent(FlexAlign.Start)',
        '子组件从容器顶部紧密排列,是 Column 的默认行为'
      )
      this.buildNoteItem(
        '② 与 Center 的区别',
        'Center 将所有子组件整体垂直居中,上下空白均匀;Start 则顶部无空白'
      )
      this.buildNoteItem(
        '③ 与 End 的区别',
        'End 将所有子组件整体靠底部排列,底部无空白;Start 则顶部无空白'
      )
      this.buildNoteItem(
        '④ 组合 alignItems',
        '配合 Start / Center / End 控制水平对齐,灵活适应不同场景'
      )
    }
    .alignItems(HorizontalAlign.Start).width('100%')
    .margin({ top: 8 })

    // 对比表格
    Text('justifyContent 对比一览')
      .fontSize(12).fontWeight(FontWeight.Medium)
      .fontColor('#636E72').margin({ top: 14, bottom: 8 }).width('100%')

    // 表格标题行
    Row() {
      Text('排列模式').fontSize(11).fontWeight(FontWeight.Bold)
        .fontColor('#2D3436').layoutWeight(1)
      Text('效果').fontSize(11).fontWeight(FontWeight.Bold)
        .fontColor('#2D3436').layoutWeight(2)
    }
    .width('100%').padding({ top: 6, bottom: 6, left: 8, right: 8 })
    .backgroundColor('rgba(108, 92, 231, 0.06)').borderRadius(6)

    // 表格数据行
    this.buildTableRow('Start', '顶部开始排列,无额外间距 ← 当前')
    this.buildTableRow('Center', '整体垂直居中,上下空白均匀')
    this.buildTableRow('End', '整体靠底部,底部无空白')
    this.buildTableRow('SpaceAround', '均匀分布,两端间距为中间的一半')
    this.buildTableRow('SpaceBetween', '均匀分布,两端无间距')
    this.buildTableRow('SpaceEvenly', '均匀分布,所有间距相等')

    Text('提示:修改根容器 justifyContent 的值即可切换排列模式')
      .fontSize(10).fontColor('#B2BEC3')
      .margin({ top: 12 }).width('100%').textAlign(TextAlign.Center)

    Text('HarmonyOS NEXT · ArkTS ColumnStart 布局示例')
      .fontSize(10).fontColor('#CCCCCC')
      .margin({ top: 10, bottom: 16 })
      .width('100%').textAlign(TextAlign.Center)
  }
  .alignItems(HorizontalAlign.Start).width('100%')
  .margin({ top: 20 })
  .backgroundColor('#FFFFFF').borderRadius(12).padding(16)
  .shadow({ radius: 6, color: 'rgba(0, 0, 0, 0.04)', offsetX: 0, offsetY: 1 })
}

布局要点

  • 这个区域本身也是一个 Column,在根 Column(Start) 中作为最后一个子组件
  • 对比表格使用 Row + layoutWeight 模拟两列布局
  • 表格标题行使用紫色背景高亮,与数据行形成视觉区分
  • 当前使用的 Start 模式在数据行中标注了"← 当前",方便读者对照

五、布局调试与常见问题

5.1 justifyContent 不生效的排查

如果在开发中遇到 justifyContent 设置后没有效果的情况,可以按以下步骤排查:

第一步:检查容器高度

// ❌ 高度由内容撑开,justifyContent 无效
Column() { /* ... */ }
// 等价于 .height(Content)

// ✅ 显式设置高度,justifyContent 生效
Column() { /* ... */ }
.height('100%')  // 或固定高度

诊断方法:在 Column 的父容器上设置 backgroundColor,观察 Column 的实际高度范围。如果 Column 的高度刚好等于子组件总高度,说明没有剩余空间可供分配。

第二步:检查是否有其他布局约束

// ❌ 子组件使用了 layoutWeight 占据了所有空间
Column() {
  Text('A').layoutWeight(1)
  Text('B').layoutWeight(1)
}
.justifyContent(FlexAlign.SpaceBetween)  // 无效,因为没有剩余空间

// ✅ 子组件使用固定高度,留下剩余空间
Column() {
  Text('A').height(100)
  Text('B').height(100)
}
.height(500)
.justifyContent(FlexAlign.SpaceBetween)  // 生效

当子组件使用了 layoutWeightflexGrow 等弹性属性时,它们会占据所有可用空间,导致 justifyContent 没有剩余空间可分配。

第三步:检查是否使用了 Scroll

// ❌ Scroll 包裹下,Column 的高度可能无限
Scroll() {
  Column() {
    // 子组件
  }
  .height('100%')  // 在 Scroll 中,这个高度相对于 Scroll 的内容高度
}

// ✅ 需要给 Scroll 设置固定高度
Scroll() {
  Column() { /* ... */ }
}
.height('100%')

Scroll 会扩展其内容区域的高度,导致 Column 的 height('100%') 可能超出预期。在这种情况下,需要给 Scroll 本身设置高度约束。

5.2 alignItems 与 justifyContent 的混淆

最常見的布局错误是混淆 alignItemsjustifyContent 的作用方向:

// ❌ 混淆:想要子组件靠右,却设置了 justifyContent
Column() {
  Text('靠右').width(100)
}
.height('100%')
.justifyContent(FlexAlign.End)  // 这是垂直方向靠下,不是水平方向靠右

// ✅ 正确:水平靠右使用 alignItems
Column() {
  Text('靠右').width(100)
}
.height('100%')
.alignItems(HorizontalAlign.End)  // 水平方向靠右

记忆技巧:“justify” 与 “align” 的区别

  • justifyContent:管"排列"(沿着主轴的方向)
  • alignItems:管"对齐"(在交叉轴的方向上)

5.3 嵌套 Column 的布局传递

当 Column 嵌套时,justifyContentalignItems 不会自动从父容器传递到子容器:

Column() {
  // 父 Column 的 justifyContent(Start) 控制这个 Column 子组件的位置
  Column() {
    // 子 Column 有自己的 justifyContent,默认也是 Start
    // 这个子 Column 内部的子组件不受父 Column 的影响
    Text('内部')
  }
  .alignItems(HorizontalAlign.Center)  // 子 Column 自己的对齐方式
}
.justifyContent(FlexAlign.Start)
.alignItems(HorizontalAlign.Start)

每个 Column 独立控制其直接子组件的布局。父 Column 的 alignItems 控制的是"子 Column 这个整体"的水平对齐,而不是子 Column 内部的子组件。

5.4 常见错误对照表

症状 可能原因 解决方案
justifyContent 没有效果 容器高度由内容撑开,无剩余空间 设置 .height('100%') 或固定高度
子组件不靠左 alignItems 设置为了 CenterEnd 改为 HorizontalAlign.Start
子组件间距不均匀 误用了 SpaceBetween 但期望 SpaceEvenly 确认每种 FlexAlign 的间距分布规则
列表项状态不对齐 ForEach 的 key 生成器返回了重复值 确保 key 唯一,如使用 item.id.toString()
状态变更不刷新 直接修改了数组原内容,未赋新数组 使用展开运算符或 filter/map 创建新数组
页面跳转 404 路由未在 main_pages.json 注册 在 “src” 数组中添加页面路径
@Builder 方法不更新 方法内部使用了非 @State 变量 依赖的变量必须使用 @State 装饰
阴影不显示 阴影颜色 alpha 值过低 使用 RGBa 格式,alpha 至少 0.04

六、性能优化与最佳实践

6.1 列表性能优化

在待办事项应用中,列表是性能优化的重点。以下是几个关键优化点:

1. 为 ForEach 提供稳定的 key 生成器

// 推荐:使用唯一标识
ForEach(items, item => ListItem() { /* UI */ },
  (item: ItemType) => item.id.toString())

// 避免:使用下标索引(默认行为)
ForEach(items, (item, index) => ListItem() { /* UI */ })

稳定的 key 帮助框架在列表项增删时复用已有的组件实例,而不是全部销毁重建。在我们的示例中,使用待办文本作为 key((item: string) => item),在文本唯一的情况下也是稳定的。

2. 控制 List 的高度

List 的高度设置需要权衡:固定的高度(如 height(280))可以让 List 内部滚动,但外部 Column 的 justifyContent 会将 List 作为一个整体对待。如果 List 的高度不固定,它会随内容扩展,影响 Column 的排列。

3. 避免在 ListItem 中嵌套过深

// ❌ 嵌套过深,影响渲染性能
ListItem() {
  Column() {
    Row() {
      Column() { /* ... */ }
    }
  }
}

// ✅ 尽量扁平化
ListItem() {
  Row() {
    // 减少不必要的包裹容器
  }
}

6.2 @State 粒度的最佳实践

如前所述,细粒度的 @State 有助于减少不必要的重渲染。但粒度过细也会带来代码维护成本的增加。以下是推荐的粒度策略:

// 太粗(一个对象包含所有状态)
@State pageState: { text: string, list: string[], index: number } = {
  text: '', list: [], index: -1
}
// 修改 text 会触发所有依赖 pageState 的 UI 重渲染

// 适中(推荐)
@State todoText: string = ''
@State todoList: string[] = []
@State selectedIndex: number = -1

// 太细(不必要的拆分)
@State selectedIndex: number = -1
@State isItemSelected: boolean = false  // 可以从 selectedIndex 推导
// 增加维护成本,且没有带来性能收益

原则:当状态变量之间存在推导关系时,不应同时将它们作为独立的状态。例如,isItemSelected 完全可以从 selectedIndex >= 0 推导出来,不需要额外的状态变量。

6.3 @Builder 与 @Component 的选择

在 ArkTS 中,@Builder@Component 都可以用于封装 UI 片段,但两者有不同的适用场景:

对比维度 @Builder @Component
创建开销 低(轻量方法) 高(独立组件实例)
状态访问 直接访问结构体成员 通过 @Prop / @Link 传递
复用范围 当前结构体内部 全局可复用
性能影响 无额外开销 有组件实例化开销
适用场景 页面内 UI 拆分 跨页面复用的组件

选择建议

  • 在单个页面内部拆分 UI → 使用 @Builder
  • 需要在多个页面复用的 UI → 使用 @Component
  • 需要独立状态管理的 UI → 使用 @Component

在我们的 ColumnStart 示例中,所有 UI 拆分都使用 @Builder,因为这些片段只在当前页面中使用,无需跨页面复用。

6.4 百分比宽度与 layoutWeight 的选择

在 Column 布局中,子组件的宽度控制有多种方式:

// 方式一:百分比宽度(推荐用于卡片、按钮)
ChildComponent().width('90%')

// 方式二:layoutWeight 等分(推荐用于 Tab、等分布局)
Row() {
  Column().layoutWeight(1)
  Column().layoutWeight(1)
  Column().layoutWeight(1)
}

// 方式三:固定宽度(推荐用于图标、小按钮)
ChildComponent().width(48)

// 方式四:不设宽度(默认拉伸,Stretch 模式)
ChildComponent()  // 自动拉伸填满

选择原则

  • 需要适应屏幕宽度 → 使用百分比宽度或 layoutWeight
  • 需要固定尺寸 → 使用固定宽度
  • 需要通栏效果 → 不设宽度(Stretch 默认行为)或 width('100%')

6.5 颜色与样式的一致性管理

在 ArkTS 中,建议将颜色值定义为常量,避免在多个地方使用魔法字符串:

// 不推荐:多处重复使用魔法字符串
.backgroundColor('#6C5CE7')
.fontColor('#6C5CE7')

// 推荐:使用常量(或定义在资源文件中)
const COLORS = {
  primary: '#6C5CE7',
  success: '#00B894',
  danger: '#E17055',
  textPrimary: '#2D3436',
  textSecondary: '#636E72',
  textTertiary: '#B2BEC3',
  background: '#F8F9FA',
  cardBackground: '#FFFFFF',
} as const

// 使用
.backgroundColor(COLORS.primary)
.fontColor(COLORS.primary)

对于更大型的项目,建议将颜色定义在 resources/base/element/color.json 中,通过资源引用访问。


七、ColumnStart 与其他布局模式的对比

7.1 ColumnStart vs ColumnCenter

对比维度 ColumnStart ColumnCenter
核心属性 justifyContent(FlexAlign.Start) alignItems(HorizontalAlign.Center)
控制方向 主轴(垂直)排列 交叉轴(水平)对齐
视觉特征 内容从顶部开始,底部留白 内容水平居中,垂直方向自定义
典型场景 列表、信息流、Feed 表单、登录页、卡片
阅读习惯 自上而下,自然阅读流 对称、聚焦注意力
底部留白 有(内容不足时) 取决于 justifyContent

结论:两者不冲突,可以组合使用。ColumnStart 控制垂直排列,ColumnCenter 控制水平对齐。实际开发中经常同时使用:

Column() {
  // 子组件
}
.justifyContent(FlexAlign.Start)   // 垂直从顶部排列
.alignItems(HorizontalAlign.Center)  // 水平居中对齐

7.2 ColumnStart vs ColumnEnd

ColumnStart 和 ColumnEnd 在垂直方向上是对称的:

ColumnStart              ColumnEnd
┌────────────────────┐  ┌────────────────────┐
│  ┌────── A ──────┐  │  │                    │
│  ├────────────────┤  │  │                    │
│  ├────── B ──────┤  │  │                    │
│  ├────────────────┤  │  │                    │
│  ├────── C ──────┤  │  │  ┌────── A ──────┐  │
│  │    (空白)      │  │  │  ├────────────────┤  │
│  │                │  │  │  ├────── B ──────┤  │
│  │                │  │  │  ├────────────────┤  │
│  │                │  │  │  ├────── C ──────┤  │
└────────────────────┘  └────────────────────┘

选择建议

  • 内容需要从上到下阅读 → 使用 ColumnStart(绝大多数场景)
  • 内容需要从底部开始(如聊天输入框、浮窗操作)→ 使用 ColumnEnd

7.3 ColumnStart vs ColumnStretch

ColumnStart 和 ColumnStretch 控制的是完全不同的维度:

  • ColumnStartjustifyContent)→ 控制垂直排列
  • ColumnStretchalignItems)→ 控制水平拉伸

两者可以组合使用:

Column() {
  // 子组件从顶部排列,且自动拉伸至全宽
}
.justifyContent(FlexAlign.Start)  // 垂直从顶部排列
.alignItems(ItemAlign.Stretch)     // 水平拉伸填满

这种组合非常常见,因为大多数列表页面既需要内容从顶部排列,又希望子组件通栏显示。

7.4 实际开发中的组合模式

在实际的鸿蒙应用开发中,很少单独使用某一种模式。以下是一些常见的组合模式:

// 模式一:列表页面(Start + Stretch)
Column() {
  ListItem()  // 通栏,从顶部排列
  ListItem()  // 通栏,从顶部排列
  ListItem()  // 通栏,从顶部排列
}
.justifyContent(FlexAlign.Start)
.alignItems(ItemAlign.Stretch)

// 模式二:居中表单(Center + Center)
Column() {
  TextInput()  // 居中,宽度 90%
  Button()     // 居中,宽度 90%
}
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)

// 模式三:底部操作栏(End + Stretch)
Column() {
  Blank()      // 弹性填充
  Button()     // 通栏,在底部
}
.justifyContent(FlexAlign.End)
.alignItems(ItemAlign.Stretch)

八、API 24 布局特性总结

8.1 API 24 的重要变化

从 API 23 到 API 24,与 Column 布局相关的关键变化包括:

1. HorizontalAlignVerticalAlign 的严格区分

在 API 24 中,Column.alignItems() 明确接受 HorizontalAlign 枚举,Row.alignItems() 接受 VerticalAlign 枚举。这一变化在编译期就阻止了混淆:

// API 24 编译错误
Column() { /* ... */ }
  .alignItems(VerticalAlign.Center)  // ❌ 类型错误!
// Argument of type 'VerticalAlign' is not assignable to
// parameter of type 'HorizontalAlign'.

2. @Builder 参数支持增强

@Builder 方法现在支持更灵活的参数传递,包括可选参数和默认值:

@Builder
buildNoteItem(title: string, desc: string = ''): void {
  // desc 现在有默认值,调用时可以省略第二个参数
}

3. shadow 属性全面取代 elevation

新的阴影系统支持精确的 RGBa 颜色控制和偏移量设置,不再依赖系统默认的阴影颜色。

4. 更严格的类型检查

ForEach 的 key 生成器返回值类型由 string 扩展为 string | number,但要求返回的值必须稳定。

8.2 从 ColumnStart 看鸿蒙布局设计哲学

ColumnStart 模式虽然简单,但它的设计体现了鸿蒙原生布局体系的几个核心哲学:

哲学一:默认行为即最佳实践

justifyContent(FlexAlign.Start) 是 Column 的默认值,这本身就是一种设计选择——在绝大多数场景下,开发者需要的正是"从顶部开始排列"的行为。不需要额外的配置,布局就能正常工作。

哲学二:正交的属性设计

justifyContentalignItems 控制两个正交的维度,互不干扰。这种设计让开发者可以独立地思考和调整每个方向上的布局策略,降低了认知负担。

哲学三:声明式优于命令式

在 ArkTS 中,布局是"声明"出来的,而非"操作"出来的。开发者描述"最终状态",框架负责"如何达到"。这种范式让 UI 代码更接近设计师的视觉稿,减少了"翻译"过程中的心智损耗。

哲学四:渐进式复杂度

ColumnStart 是最简单的模式,当需要更复杂的布局时,可以渐进地引入 SpaceBetweenSpaceAround 等分布策略,而无需重写整个布局。这种渐进式复杂度设计让开发者可以从简单开始,按需深入。


九、总结

本文通过一个完整的待办事项(Todo List)示例应用,深入剖析了 HarmonyOS NEXT API 24 中 ColumnStart 顶部起始分布布局的原理与实践。核心要点可以概括为以下七点:

1. justifyContent 控制主轴排列,alignItems 控制交叉轴对齐

这是 Column 布局的底层逻辑。justifyContent(FlexAlign.Start) 将子组件从容器顶部开始排列,是所有 FlexAlign 值中最自然、最符合阅读习惯的模式。

2. 容器高度是 justifyContent 生效的前提

没有明确的高度约束,justifyContent 就没有剩余空间可供分配。务必在 Column 上设置 height('100%') 或固定高度。

3. FlexAlign 六大枚举值各有适用场景

最佳场景
Start 列表、信息流、Feed(默认)
Center 启动页、弹窗内容
End 底部操作栏、聊天输入框
SpaceAround 需要两端留白的列表
SpaceBetween 需要均匀分布、无两端留白的列表
SpaceEvenly 需要精确等距排列的场景

4. @Builder 是 UI 拆分的高效手段

在单个页面内部,使用 @Builder 拆分 UI 片段比创建 @Component 更轻量、更高效。@Builder 方法可以直接访问结构体成员,无需参数传递。

5. @State 的粒度影响渲染性能

细粒度的 @State 变量可以减少不必要的重渲染。但粒度过细会增加维护成本,需要在性能和可维护性之间找到平衡。

6. 列表性能优化需要关注 key 生成器

ForEach 的 key 生成器直接影响列表的更新性能。稳定的 key 帮助框架复用组件实例,避免 UI 闪烁。

7. ColumnStart 是纵向布局的基石

理解 ColumnStart 是掌握鸿蒙 ArkTS 布局体系的第一步。从 Start 开始,到 Center、End、SpaceAround、SpaceBetween、SpaceEvenly,逐步深入,就能应对绝大多数纵向布局需求。


附录:完整代码清单

文件一:Index.ets(首页)

@Entry
@Component
struct Index {
  build() {
    Column() {
      Text('🏠 布局示例首页')
        .fontSize(24).fontWeight(FontWeight.Bold)
        .fontColor('#1A1A2E')
      Text('选择一个布局模式进行查看')
        .fontSize(14).fontColor('#888888').margin({ top: 8 })
      Button('▶ ColumnStart 顶部起始布局')
        .width('85%').height(50)
        .backgroundColor('#6C5CE7').fontColor('#FFFFFF')
        .fontSize(15).fontWeight(FontWeight.Medium)
        .borderRadius(12).margin({ top: 32 })
        .onClick(() => {
          router.pushUrl({ url: 'pages/ColumnStartDemo' })
        })
      Text('Column + justifyContent(FlexAlign.Start)')
        .fontSize(12).fontColor('#AAAAAA').margin({ top: 12 })
    }
    .width('100%').height('100%')
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Center)
    .backgroundColor('#F5F5F5').padding(16)
  }
}

文件二:ColumnStartDemo.ets(演示页)

完整代码见本仓库 entry/src/main/ets/pages/ColumnStartDemo.ets,共 809 行。

文件三:main_pages.json(路由注册)

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

本文是"鸿蒙原生 ArkTS 布局深度解析"系列文章之一。该系列覆盖 Column 的五大布局模式(Center / End / Start / Stretch / Baseline),旨在帮助开发者系统性地掌握鸿蒙 NEXT 的原生布局能力。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐