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

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

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


一、引言

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

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

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

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

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

简单来说,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 组合,形成不同的布局效果:

组合模式justifyContentalignItems视觉效果
Start + StartFlexAlign.StartHorizontalAlign.Start左上角起始,自然阅读流
Start + CenterFlexAlign.StartHorizontalAlign.Center顶部居中,表单常用
Start + EndFlexAlign.StartHorizontalAlign.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 的协同工作

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

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 更新。直接调用 push、splice 等方法修改原数组不会触发重渲染。

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 配合,形成了"左上角起始"的阅读布局
  • 可以改为 Center 或 End 观察水平对齐的变化,不影响垂直排列

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 }) 为列表项之间添加了分隔线,startMargin 和 endMargin 控制分隔线的左右缩进,避免分隔线从列表项的最左侧开始。

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)  // 生效

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

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

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

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

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

5.2 alignItems 与 justifyContent 的混淆

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

// ❌ 混淆:想要子组件靠右,却设置了 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 嵌套时,justifyContent 和 alignItems 不会自动从父容器传递到子容器:

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 设置为了 Center 或 End改为 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

对比维度ColumnStartColumnCenter
核心属性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 控制的是完全不同的维度:

  • ColumnStart(justifyContent)→ 控制垂直排列
  • ColumnStretch(alignItems)→ 控制水平拉伸

两者可以组合使用:

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. HorizontalAlign 和 VerticalAlign 的严格区分

在 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 的默认值,这本身就是一种设计选择——在绝大多数场景下,开发者需要的正是"从顶部开始排列"的行为。不需要额外的配置,布局就能正常工作。

哲学二:正交的属性设计

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

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

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

哲学四:渐进式复杂度

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


九、总结

本文通过一个完整的待办事项(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、测试、元服务和应用上架分发等。

更多推荐