请添加图片描述
请添加图片描述

鸿蒙原生 ArkTS 布局深度解析:ColumnSpaceAround 主轴均匀环绕分布

SDK 版本:HarmonyOS NEXT 6.1.1(API 24)
开发语言:ArkTS(方舟统一编程语言)
UI 框架:ArkUI(方舟 UI 框架)


目录

01 布局基础:认识鸿蒙 ArkUI 的 Flex 弹性布局体系
02 核心概念:主轴、交叉轴与 justifyContent
03 SpaceAround 详解:均匀环绕分布的数学本质
04 四大 FlexAlign 值横向对比
05 完整项目结构与文件清单
06 逐段代码精读:从 import 到 build 构建函数
07 核心演示区:5 张卡片如何实现均匀环绕
08 子组件设计:RoundedCard 圆角卡片的封装思想
09 辅助组件详解:标题、说明、图例与对比
10 间距对照图例:可视化 1x 与 2x 的比例关系
11 交互细节:点击选中高亮与响应式状态管理
12 注册页面:main_pages.json 与路由配置
13 多页面跳转:从 Index 到 ColumnSpaceAroundDemo
14 响应式数据流:@State @Prop 装饰器的工作原理
15 与 SpaceBetween 的差异:首尾边距的微妙区别
16 与 SpaceEvenly 的差异:完全均匀与半环绕
17 与 Center 的差异:无间距居中排列
18 实战技巧:容器高度对 SpaceAround 效果的影响
19 常见陷阱:为什么必须设置固定高度
20 嵌套布局:Scroll + Column 的组合使用场景
21 视觉设计:颜色、圆角与阴影的搭配原则
22 组件化思想:@Component 拆分与复用策略
23 代码风格:ArkTS 装饰器语法的书写规范
24 调优建议:性能与可维护性的平衡
25 总结与展望:鸿蒙原生布局的未来方向


01 布局基础:认识鸿蒙 ArkUI 的 Flex 弹性布局体系

鸿蒙操作系统自诞生之初就秉持「全场景、分布式」的设计理念,而 HarmonyOS NEXT 作为彻底剥离 Android 代码、全面拥抱鸿蒙原生生态的里程碑版本,其 UI 框架 ArkUI 也迎来了质的飞跃。在 ArkUI 中,布局系统是整个用户界面构建的基石,而 Flex 弹性布局 则是其中最重要、使用频率最高的布局模型之一。

1.1 什么是弹性布局

弹性布局(Flexbox)源自 CSS 的 Flexible Box Layout 规范,其核心思想是让容器能够根据可用空间动态调整子元素的大小和位置。鸿蒙 ArkUI 将这一成熟的布局模型原生引入,通过 Row(水平弹性容器)和 Column(垂直弹性容器)两个基础组件,为开发者提供了简洁而强大的布局能力。

1.2 Column 与 Row 的本质

在 ArkUI 中:

  • Row:水平弹性容器,主轴为水平方向(从左到右),子组件沿水平轴线排列。
  • Column:垂直弹性容器,主轴为垂直方向(从上到下),子组件沿垂直轴线排列。

两者都继承自弹性布局模型,共享 justifyContent(主轴对齐)、alignItems(交叉轴对齐)等核心属性。这种设计让开发者只需要理解一套布局模型,就能应对绝大部分的场景需求。

1.3 弹性布局在鸿蒙中的定位

相比于鸿蒙提供的其他布局方式——如绝对定位(Stack)、栅格布局(GridRow/GridCol)、列表布局(List)——弹性布局更适合以下场景:

  • 子组件数量确定或可枚举
  • 需要沿单一方向排列子组件
  • 希望由框架自动计算间距和排列方式
  • 需要响应式地适应不同屏幕尺寸

SpaceAround
是弹性布局中用于处理主轴方向上子组件间距的四种经典策略之一。


02 核心概念:主轴、交叉轴与 justifyContent

要深入理解 SpaceAround,首先需要掌握弹性布局的两个核心轴线概念。

2.1 主轴与交叉轴

任何一个弹性容器都有两条轴:

容器类型 主轴方向 交叉轴方向
Row 水平(→) 垂直(↓)
Column 垂直(↓) 水平(→)

主轴 决定了子组件的排列方向,而 交叉轴 决定了子组件在垂直于排列方向上的对齐方式。对于我们的场景 —— Column 布局 —— 主轴是垂直方向,交叉轴是水平方向。

2.2 justifyContent 的作用

justifyContent 是一个用于控制主轴方向上子组件分布方式的属性。它接收一个 FlexAlign 枚举值,决定子组件在主轴上的排列策略。

FlexAlign 枚举包含以下六个值:

枚举值 含义 适用场景
Start 子组件在主轴起点对齐 默认行为,从上到下紧密排列
Center 子组件在主轴居中,无间距 需要整体居中且无间距时
End 子组件在主轴终点对齐 从底部开始排列
SpaceBetween 首尾贴边,中间间距相等 子组件占据容器两端
SpaceAround 首尾间距 = 中间间距 × 0.5 均匀环绕分布
SpaceEvenly 所有间距完全相等 完全均匀分布

2.3 为什么叫「主轴对齐」

justifyContent 的名称源自 CSS 的 justify-content 属性,直译为「证明内容」略显晦涩,更准确的理解是 「调整主轴方向的内容分布」。它不负责单个子组件在交叉轴上的对齐(那是 alignItems 的任务),而是专注于所有子组件作为一个整体在主轴上如何排列。


03 SpaceAround 详解:均匀环绕分布的数学本质

3.1 SpaceAround 的定义

FlexAlign.SpaceAround 的中文含义是 「均匀环绕分布」。该策略会让容器在主轴方向上,将剩余可用空间均匀地分配到每个子组件的两侧,形成一个「环绕」效果。

用数学表达式来描述:

设容器在主轴上的总长度为 L
子组件有 N 个,每个子组件在主轴上的尺寸为 S₁, S₂, ..., Sₙ
所有子组件的总尺寸之和为 ΣS = S₁ + S₂ + ... + Sₙ
则剩余可用空间 R = L - ΣS

SpaceAround 将 R 均匀分成 2N 份:
  相邻子组件之间的间距 = 2 × (R / 2N) = R / N
  首部间距(第一个子组件之前)= R / 2N = R / (2N)
  尾部间距(最后一个子组件之后)= R / 2N = R / (2N)

即:
  首尾间距 = 相邻间距 / 2

3.2 直观理解

以 5 张卡片在 500px 高的容器中排列为例:

容器高度:500px
5 张卡片各高 60px,总计 300px
剩余空间:200px

按照 SpaceAround:
  将 200px 分成 2 × 5 = 10 份
  每份 = 20px
  
  首部间距(卡片1上方)= 20px
  卡片1 → 卡片2 间距 = 40px
  卡片2 → 卡片3 间距 = 40px
  卡片3 → 卡片4 间距 = 40px
  卡片4 → 卡片5 间距 = 40px
  尾部间距(卡片5下方)= 20px

这种分布方式在视觉上形成了「环绕」的感觉——每个子组件周围都有均匀的空白,但两端的空白比中间的要少一半,视觉重心自然落在中间区域。

3.3 SpaceAround 的视觉特征

SpaceAround 最显著的视觉特征是对称的「松紧带」效果

  • 中间区域的空间感最强(间距最大)
  • 两端区域的空间感适中(间距居中)
  • 整体呈现出从中心向两端逐渐收紧的节奏感

这种节奏感特别适合以下场景:

  • 垂直排列的功能卡片
  • 设置页面中的选项菜单
  • 展示面板中的信息区块
  • 需要视觉层次感的列表式界面

04 四大 FlexAlign 值横向对比

为帮助读者快速理解差异,我们用同一个容器(高度 500px,5 个高度 60px 的卡片)来对比四种排列策略的实际效果。

排列策略 顶部间距 相邻间距 底部间距 适用场景
Center 均匀分配 0 均匀分配 整体居中,无需间距
SpaceBetween 0 50px 0 两侧贴边,中间均匀
SpaceAround 10px 20px 10px 环绕分布,中心松散
SpaceEvenly 14.28px 14.28px 14.28px 完全均匀分布

4.1 关键差异总结

SpaceBetween:  [卡] ═════ [卡] ═════ [卡]
SpaceAround:    ↕[卡]══[卡]══[卡]↕
SpaceEvenly:   ↕[卡]↕[卡]↕[卡]↕
Center:        [ 卡 卡 卡 ]
  • SpaceBetween:首尾贴边,适合导航栏、工具栏等需要最大化利用空间的场景。
  • SpaceAround:首尾半间距,适合信息卡片、选项面板等需要留白但又不希望太松散的场景。
  • SpaceEverly:所有间距相等,适合等分空间、对称要求极高的场景。
  • Center:零间距居中,适合按钮组、标签栏等需要紧凑排列的场景。

05 完整项目结构与文件清单

在进行代码分析之前,我们先了解一下整个鸿蒙项目的完整目录结构。一个标准 HarmonyOS NEXT 项目的骨架如下:

s222222/
├── AppScope/
│   ├── app.json5                     # 应用全局配置
│   └── resources/                    # 应用级资源
│
├── entry/                            # Entry 模块(HAP 包)
│   ├── src/main/
│   │   ├── ets/
│   │   │   ├── entryability/
│   │   │   │   └── EntryAbility.ets  # Ability 生命周期
│   │   │   ├── pages/
│   │   │   │   ├── Index.ets         # 默认首页(TTS 示例)
│   │   │   │   └── ColumnSpaceAroundDemo.ets  # ★ 本文主角
│   │   │   └── TTSManager.ets        # TTS 工具类
│   │   ├── module.json5              # 模块配置
│   │   └── resources/                # 模块资源文件
│   │
│   ├── build-profile.json5           # 构建配置
│   ├── hvigorfile.ts                 # Hvigor 构建脚本
│   └── oh-package.json5              # OHPM 包管理
│
├── hvigor/                           # Hvigor 构建工具配置
├── build-profile.json5               # 项目级构建配置
├── hvigorfile.ts                     # 项目级构建脚本
└── oh-package.json5                  # 项目级包管理

5.1 我们的目标文件

本文主要关注 entry/src/main/ets/pages/ColumnSpaceAroundDemo.ets 这一单个文件。

这个文件是一个独立的、自包含的页面,采用了鸿蒙 ArkTS 的标准页面结构,包含 @Entry(页面入口标记)和 @Component(组件标记)两个核心装饰器,以及多个自定义子组件。


06 逐段代码精读:从 import 到 build 构建函数

我们从文件头部开始,逐段解读每一部分代码的设计意图。

6.1 文件头部注释

/*
 * HarmonyOS NEXT 鸿蒙原生ArkTS布局示例
 * ==============================================================
 * 布局名称:ColumnSpaceAround —— 纵向主轴均匀环绕分布
 * 核心 API:Column + justifyContent(FlexAlign.SpaceAround)
 * ==============================================================
 * 布局效果(纵向):
 *          ┌─────────────┐
 *          │   卡片 1     │   ← ╲
 *          ├─────────────┤      ╲ 上半间距(主轴边距的一半)
 *          │   卡片 2     │   ← ╱
 *          ├─────────────┤   ← 间距 = 2 × 边距
 *          │   卡片 3     │   ← ╲
 *          ├─────────────┤      ╱ 下半间距(主轴边距的一半)
 *          │   卡片 4     │   ← ╱
 *          └─────────────┘
 *
 * SpaceAround 含义:
 *   每个子组件在主轴(纵向)方向上均匀排列,
 *   每两个相邻子组件之间的间距相等,
 *   但第一个子组件距容器顶部、最后一个子组件距容器底部的距离
 *   等于相邻间距的一半(即「环绕」效果)。
 * ==============================================================
 */

这段注释不是可有可无的「装饰」,而是整个文件的 「布局说明书」

设计上的考量有三点:

第一,文档即代码。在团队协作中,其他开发者在打开文件的瞬间就能理解这个页面的布局意图,无需逐行阅读代码。

第二,视觉化说明。通过 ASCII 艺术图直观呈现 SpaceAround 的分布效果,比文字描述更加直观。

第三,关键公式明确。注释中明确写出了「首尾间距 = 相邻间距 ÷ 2」这一核心规则,帮助读者绕过复杂的数学推导,直接理解布局效果。

6.2 关于 import 语句

// ======================== 1. 必要 import 语句 ========================

/**
 * 鸿蒙 ArkUI(方舟UI框架)核心装饰器与组件:
 *   @Entry        —— 表示该页面是应用的入口页面之一
 *   @Component    —— 表示该结构体是一个可复用的自定义组件
 *   @State        —— 声明响应式状态变量,数据变化时自动刷新UI
 *   @Builder      —— 声明一个构建函数,用于复用 UI 片段
 */
// ======================== 2. 页面主组件 ========================

细心的读者可能会发现:这里 import 部分只有注释,没有实际的 import 语句。这是因为 HarmonyOS NEXT 将所有 ArkUI 的基础组件和 API 都作为全局内置符号注册,开发者无需显式导入 @Entry@ComponentColumnRowText 等核心组件。

这与传统的 Web 开发(需要 import React from 'react')或 Flutter 开发(需要 import 'package:flutter/material.dart')有显著不同。鸿蒙的全局注册机制减少了样板代码,让文件结构更清晰。

如果需要导入自定义模块(如工具类、常量定义),则需要使用标准的 import 语法:

import { CustomUtil } from '../utils/CustomUtil'

但在本文的示例中,所有子组件都在同一个文件中定义,因此无需任何导入。

6.3 页面主组件结构

@Entry
@Component
struct ColumnSpaceAroundDemo {

  @State selectedIndex: number = -1

  build() {
    Scroll() {
      Column() {
        TitleSection()
        DescriptionCard()
        // —— 核心演示区 ——
        Column() {
          // 5 张卡片
        }
        .justifyContent(FlexAlign.SpaceAround)
        .width('100%')
        .height(500)
        .padding(12)
        .borderRadius(16)
        .backgroundColor('#F0F0F0')
        .margin({ top: 16, bottom: 16 })

        SpacingLegend()
        ComparisonSection()
        BlankSpace()
      }
      .width('100%')
      .padding(16)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F8F9FA')
  }
}

整个页面的 UI 树结构如下:

Scroll(根容器,可滚动)
  └── Column(顶级纵向容器)
       ├── TitleSection(标题区域)
       ├── DescriptionCard(说明区域)
       ├── Column(核心演示容器)← ★ SpaceAround 在此
       │    ├── 卡片 1(首)
       │    ├── 卡片 2
       │    ├── 卡片 3
       │    ├── 卡片 4
       │    └── 卡片 5(末)
       ├── SpacingLegend(图例区域)
       ├── ComparisonSection(对比区域)
       └── BlankSpace(底部留白)

这种嵌套结构是鸿蒙 ArkTS 布局的典型写法:

  • 外层 Scroll 保证页面内容超出屏幕高度时可以滚动查看
  • 顶层的 Column 负责将各个功能区域垂直排列
  • 中间的 Column 是核心演示容器,应用了 SpaceAround
  • 每个功能区域都被封装成了独立的子组件

07 核心演示区:5 张卡片如何实现均匀环绕

这是整个页面的核心,也是 SpaceAround 布局的实际展示区域。

// ★★★★★ 以下 Column 是本示例的核心 ★★★★★
// justifyContent(FlexAlign.SpaceAround) 的作用:
//   在纵向(主轴)方向,让所有子组件均匀排列,
//   首尾边距 = 相邻间距 / 2
// ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
Column() {

  // 卡片 1 —— 右上角带小圆点标记「首」
  Stack() {
    RoundedCard({
      text: '卡片 1(首)',
      desc: '距容器顶部距离 = 间距的一半',
      color: '#FF6B81',
      index: 0,
      isSelected: this.selectedIndex === 0
    })
    Text('首')
      .fontSize(10)
      .fontColor(Color.White)
      .backgroundColor(Color.Red)
      .borderRadius(8)
      .padding({ left: 6, right: 6, top: 2, bottom: 2 })
      .align(Alignment.TopEnd)
      .offset({ x: 0, y: -8 })
  }
  .width('100%')
  .height(80)
  .onClick(() => { this.selectedIndex = 0 })

  // 卡片 2-4(中间位置)
  RoundedCard({
    text: '卡片 2(中)',
    desc: '↕ 上下间距相等',
    // ...
  })
  .onClick(() => { this.selectedIndex = 1 })

  // 卡片 5 —— 带小圆点标记「末」
  // ...
}
// ========== SpaceAround 核心代码,仅此一行 ==========
.justifyContent(FlexAlign.SpaceAround)
// =====================================================
.width('100%')
.height(500)                    // 固定高度,使 SpaceAround 效果可被明显观察
.padding(12)
.borderRadius(16)
.backgroundColor('#F0F0F0')
.margin({ top: 16, bottom: 16 })

7.1 关键设计决策

为什么使用 5 张卡片?

5 是一个奇数,可以让中间位置的卡片 3 成为视觉中心,更容易观察「中间间距最大」的效果。如果只用 2~3 张卡片,SpaceAround 的效果不太明显。

为什么卡片 1 和卡片 5 用 Stack 包裹?

Stack(层叠布局)允许将「首」「末」标记小圆点叠加到卡片的右上角,且不影响卡片本身的布局尺寸。这是非侵入式添加视觉标记的经典手法。

为什么 Container 要设置 height(500)?

SpaceAround 的底层是「将剩余空间均匀分配」。如果容器不设置固定高度,容器高度将由子组件撑满(即 wrap_content),此时剩余空间为 0,SpaceAround 与 Start 的效果完全一致。因此,固定高度是 SpaceAround 生效的必要条件

7.2 5 张卡片的高度

核心容器设置了 height(500),而每张卡片的高度是 80。5 张卡片的总高度为 5 × 80 = 400,剩余空间为 100

根据 SpaceAround 的分配公式:

  • 首尾间距 = 100 / (2 × 5) = 10px
  • 相邻间距 = 100 / 5 = 20px

验证了「首尾间距 = 相邻间距 / 2」的规律。


08 子组件设计:RoundedCard 圆角卡片的封装思想

RoundedCard 是一个可复用的自定义组件,展示了鸿蒙 ArkTS 中组件化设计的最佳实践。

8.1 完整的组件代码

@Component
struct RoundedCard {
  @Prop text: string = ''
  @Prop desc: string = ''
  @Prop color: string = '#007AFF'
  @Prop index: number = 0
  @Prop isSelected: boolean = false

  build() {
    Row() {
      // 序号徽标
      Text(`${this.index + 1}`)
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
        .fontColor(Color.White)
        .textAlign(TextAlign.Center)
        .width(36)
        .height(36)
        .borderRadius(18)
        .backgroundColor(this.color)

      // 文字信息
      Column() {
        Text(this.text)
          .fontSize(16)
          .fontWeight(FontWeight.Medium)
          .fontColor('#333333')

        Text(this.desc)
          .fontSize(12)
          .fontColor('#999999')
          .margin({ top: 4 })
      }
      .alignItems(HorizontalAlign.Start)
      .margin({ left: 12 })

      // 选中指示器
      if (this.isSelected) {
        Text('✓')
          .fontSize(20)
          .fontColor('#34C759')
          .fontWeight(FontWeight.Bold)
      }
    }
    .width('100%')
    .height('100%')
    .padding({ left: 16, right: 16 })
    .alignItems(VerticalAlign.Center)
    .backgroundColor(Color.White)
    .borderRadius(12)
    .borderWidth(this.isSelected ? 2 : 0)
    .borderColor(this.isSelected ? '#34C759' : Color.Transparent)
    .shadow({
      radius: this.isSelected ? 8 : 4,
      color: this.isSelected ? 'rgba(52,199,89,0.3)' : 'rgba(0,0,0,0.1)',
      offsetX: 0,
      offsetY: 2
    })
  }
}

8.2 组件设计要点

第一:属性通过 @Prop 接收

@Prop 装饰器允许父组件向子组件传递数据。与 @State 不同,@Prop 是单向数据流——子组件不能修改 @Prop 的值,只能读取。

@Prop text: string = ''        // 卡片标题,默认空字符串
@Prop desc: string = ''        // 卡片描述,默认空字符串
@Prop color: string = '#007AFF' // 卡片主色调,默认蓝色
@Prop index: number = 0        // 卡片序号,默认 0
@Prop isSelected: boolean = false  // 选中状态,默认未选中

第二:Row 作为内部容器

卡片内部使用 Row(水平容器)来排列三个元素:左侧序号圆标、中间文字区域、右侧选中打勾标记。这种水平三栏布局是 ArkUI 中最常见的信息展示结构。

第三:选中态视觉反馈

通过条件渲染 if (this.isSelected) 控制打勾标记的出现,同时配合边框、阴影的变化,让选中态有丰富的视觉反馈:

.borderWidth(this.isSelected ? 2 : 0)
.borderColor(this.isSelected ? '#34C759' : Color.Transparent)
.shadow({
  radius: this.isSelected ? 8 : 4,
  color: this.isSelected ? 'rgba(52,199,89,0.3)' : 'rgba(0,0,0,0.1)',
})

第四:百分比撑满

Rowwidth('100%')height('100%') 确保卡片填满父容器(Stack 或 Column)分配的空间。这是 ArkUI 中常用的「自适应填充」写法。


09 辅助组件详解:标题、说明、图例与对比

一个好页面不仅要有核心演示,还需要辅助组件来提供上下文信息和交互引导。本示例精心设计了五个辅助组件,每一个都有明确的功能定位。

9.1 TitleSection——页面标题

@Component
struct TitleSection {
  build() {
    Column() {
      Text('纵向 SpaceAround 均匀环绕分布')
        .fontSize(22)
        .fontWeight(FontWeight.Bold)
        .fontColor('#1A1A2E')
        .textAlign(TextAlign.Center)

      Text('Column + justifyContent(FlexAlign.SpaceAround)')
        .fontSize(13)
        .fontColor('#6C757D')
        .fontFamily('Courier New')
        .margin({ top: 6 })
        .textAlign(TextAlign.Center)

      Text('子组件在主轴(纵向)上均匀排列,首尾间距 = 相邻间距 / 2')
        .fontSize(13)
        .fontColor('#6C757D')
        .margin({ top: 4 })
        .textAlign(TextAlign.Center)
    }
    // ...
  }
}

标题组件由三行文字组成:

  • 第一行:中文描述,使用 fontSize(22)fontWeight(FontWeight.Bold) 突出显示,让用户一眼知道这个页面的主题。
  • 第二行:API 签名,使用 fontFamily('Courier New') 等宽字体,模拟代码风格,方便有开发经验的读者快速识别关键技术点。
  • 第三行:核心规则,用简洁的文字总结 SpaceAround 的本质,降低理解成本。

9.2 DescriptionCard——布局说明

@Component
struct DescriptionCard {
  build() {
    Column() {
      // 公式区域
      Row() {
        Text('公式:').fontSize(14).fontWeight(FontWeight.Bold)
        Text('  首尾间距 = 相邻间距 ÷ 2')
          .fontSize(14).fontColor('#E74C3C')
          .fontFamily('Courier New')
          .fontWeight(FontWeight.Bold)
      }
      .width('100%')
      .margin({ bottom: 12 })

      KeyPoint({ icon: '①', text: '容器必须设置固定高度……' })
      KeyPoint({ icon: '②', text: '所有子组件在纵向上均匀排列……' })
      KeyPoint({ icon: '③', text: '第一个子组件上方、最后一个子组件下方的空白 = 中间间隙 / 2' })
      KeyPoint({ icon: '④', text: '点击卡片可高亮选中,辅助观察间距分布' })
    }
    // ...
  }
}

说明卡片使用蓝色背景(#E8F4FD)和浅蓝色边框(#BBDEFB),视觉上与其他区域形成区分。

公式区域使用红色等宽字体凸显关键信息,这是开发者在阅读时需要重点关注的内容。

四个要点通过 KeyPoint 子组件逐一呈现,每个要点都有编号圆标(①~④),形成清晰的阅读路径。

9.3 KeyPoint——要点行

@Component
struct KeyPoint {
  @Prop icon: string = ''
  @Prop text: string = ''

  build() {
    Row() {
      Text(this.icon)
        .fontSize(14)
        .fontColor('#1976D2')
        .margin({ right: 8 })
      Text(this.text)
        .fontSize(13)
        .fontColor('#495057')
    }
    // ...
  }
}

这个组件虽然只有两行文字,但它体现了细粒度组件化的思想。如果不用 KeyPoint 组件,DescriptionCard 中就需要重复编写四次 Row + 两个 Text 的结构,代码冗余且不易维护。将其抽离为独立组件后,不仅代码更简洁,后续如需修改要点样式(如字号、颜色),只需修改一处。

9.4 ComparisonSection——对比可选项

@Component
struct ComparisonSection {
  build() {
    Column() {
      Text('与其他 FlexAlign 对比')
        .fontSize(16).fontWeight(FontWeight.Bold)

      AlignRow({ alignName: 'FlexAlign.SpaceAround',  desc: '首尾间距 = 中间间距 / 2', isActive: true })
      AlignRow({ alignName: 'FlexAlign.SpaceBetween', desc: '首尾贴边',              isActive: false })
      AlignRow({ alignName: 'FlexAlign.SpaceEvenly',  desc: '首尾间距 = 中间间距',   isActive: false })
      AlignRow({ alignName: 'FlexAlign.Center',       desc: '所有子组件居中排列',    isActive: false })
    }
  }
}

对比区域对本示例(SpaceAround)进行了高亮标记,同时列出其他三种常见排列策略。这种设计帮助读者建立知识框架——不仅知道 SpaceAround 是什么,还知道它与其他策略的异同。

isActive: trueAlignRow 使用蓝色背景(#E3F2FD)和「✓ 本示例」标签,让读者清楚当前的焦点。

9.5 BlankSpace——底部留白

@Component
struct BlankSpace {
  build() {
    Column() {
      Text('点击上方卡片可选中高亮,便于观察间距分布')
        .fontSize(12).fontColor('#ADB5BD')
      Text('— Column SpaceAround 布局演示 —')
        .fontSize(12).fontColor('#ADB5BD')
        .margin({ top: 8 })
    }
    // ...
  }
}

底部留白看似简单,却是用户体验设计的重要一环。它提供了:

  • 操作提示:提醒用户可以点击卡片查看选中效果
  • 页面结尾标识:用「— …… —」的分割线标记页面结束
  • 视觉缓冲:防止页面内容紧贴屏幕底部

10 间距对照图例:可视化 1x 与 2x 的比例关系

SpaceAround 的核心规律是「首尾间距与中间间距的比例为 1:2」。为了让读者直观理解这一概念,我们设计了一个间距对照图例组件。

@Component
struct SpacingLegend {
  build() {
    Column() {
      Text('间距对照图例')
        .fontSize(16).fontWeight(FontWeight.Bold)
        .margin({ bottom: 12 })

      Column() {
        // 上边距(短条)
        Row() {
          ColorBlock({ widthVal: '30%', color: '#FF6B81', label: '边距(1x)' })
        }
        .width('100%')
        .justifyContent(FlexAlign.Center)

        // 卡片占位
        Text('卡片').fontSize(12)
          .backgroundColor('#FF6B81')
          .width(60).height(30).borderRadius(6)
          .textAlign(TextAlign.Center).lineHeight(30)
          .fontColor(Color.White)

        // 中间间距(长条)
        Row() {
          ColorBlock({ widthVal: '60%', color: '#5352ED', label: '相邻间距(2x)' })
        }
        .width('100%')
        .justifyContent(FlexAlign.Center)

        // 卡片占位
        // ...重复三张卡片+中间间距...

        // 下边距(短条)
        Row() {
          ColorBlock({ widthVal: '30%', color: '#FFA502', label: '边距(1x)' })
        }
        .width('100%')
        .justifyContent(FlexAlign.Center)
      }
      .justifyContent(FlexAlign.SpaceAround)  // ← 图例本身也使用 SpaceAround
      .width('100%')
      .height(280)
      // ...
    }
  }
}

10.1 自指涉设计

这个图例组件本身也使用了 SpaceAround 布局!这种 「自指涉」 的设计手法让图例如同一个小型演示器——它展示的内容与它自身的布局方式完全一致。

图例中的元素排列如下:

┌─────────────────────────┐
│      边距(1x)30%        │  ← 短色块
│         [卡片]           │
│     相邻间距(2x)60%      │  ← 长色块
│         [卡片]           │
│     相邻间距(2x)60%      │  ← 长色块
│         [卡片]           │
│      边距(1x)30%        │  ← 短色块
└─────────────────────────┘

10.2 ColorBlock——彩色条块

@Component
struct ColorBlock {
  @Prop widthVal: string = '50%'
  @Prop color: string = '#007AFF'
  @Prop label: string = ''

  build() {
    Row() {
      Text(this.label)
        .fontSize(11).fontColor('#6C757D')
        .textAlign(TextAlign.Center)
    }
    .width(this.widthVal)
    .height(20)
    .backgroundColor(this.color)
    .borderRadius(4)
    .justifyContent(FlexAlign.Center)
    .opacity(0.6)
  }
}

ColorBlock 接收三个属性:

  • widthVal:宽度百分比,边距用 30%、间距用 60%
  • color:颜色值,五种颜色分别对应不同位置
  • label:显示文本,标注「边距(1x)」或「相邻间距(2x)」

通过宽度 30% 与 60% 的视觉对比,1:2 的比例关系一目了然。


11 交互细节:点击选中高亮与响应式状态管理

鸿蒙 ArkTS 的声明式 UI 框架依赖响应式状态管理来驱动界面更新。本示例通过 @State 和条件渲染机制,实现了卡片点击高亮的功能。

11.1 状态定义

@State selectedIndex: number = -1

selectedIndex 是一个 @State 装饰的响应式变量,初始值为 -1(表示没有选中任何卡片)。当它的值发生变化时,所有依赖于它的 UI 部分都会自动重新渲染。

11.2 点击事件绑定

在核心演示区中,每张卡片都绑定了一个 onClick 事件:

// 卡片 0(首)
Stack() {
  RoundedCard({
    // ...
    isSelected: this.selectedIndex === 0
  })
  // ...
}
.onClick(() => { this.selectedIndex = 0 })

// 卡片 1
RoundedCard({
  // ...
  isSelected: this.selectedIndex === 1
})
.onClick(() => { this.selectedIndex = 1 })

11.3 数据流路径

完整的数据流如下:

  1. 用户点击卡片 → onClick 回调触发
  2. 回调修改 this.selectedIndex = N
  3. ArkUI 框架检测到 @State 变量变化
  4. 框架重新调用 build() 方法构建 UI 树
  5. RoundedCardisSelected 属性获得新值
  6. 卡片内部条件渲染:显示打勾标记、改变边框、改变阴影
  7. 选中的卡片边框变绿、阴影增强,其他卡片恢复默认样式

11.4 条件渲染的两种形式

本示例使用了两种条件渲染方式:

第一种:三元表达式

.borderWidth(this.isSelected ? 2 : 0)

用于简单的二选一属性值选择。

第二种:if 语句

if (this.isSelected) {
  Text('✓')
    .fontSize(20)
    .fontColor('#34C759')
    // ...
}

用于条件性地添加或移除整个组件节点。


12 注册页面:main_pages.json 与路由配置

一个页面创建完成后,必须在模块的 main_pages.json 中注册,否则 HarmonyOS 运行时无法找到该页面。

12.1 main_pages.json 文件位置

entry/
  src/
    main/
      resources/
        base/
          profile/
            main_pages.json    ← 页面路由注册文件

12.2 注册内容

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

src 数组中的每一项对应 pages/ 目录下的一个 .ets 文件。注意:

  • 不需要写文件扩展名 .ets
  • 路径相对于 ets/ 目录
  • 第一个页面是默认首页
  • 每新增一个页面,都要在这里添加对应路径

12.3 页面路由地址

注册完成后,页面可通过两种方式访问:

方式一:启动入口

ColumnSpaceAroundDemo 移到 src 数组的第一个位置,即可设为应用启动时的首页。

方式二:路由跳转

通过 router.pushUrlNavigator 组件进行页面跳转:

import { router } from '@kit.ArkUI'

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

13 多页面跳转:从 Index 到 ColumnSpaceAroundDemo

虽然在本文中我们没有直接在 Index.ets 中添加跳转按钮,但在实际项目中,通常会从首页通过按钮或列表项跳转到演示页面。

下面是一个完整的跳转示例(可以作为扩展练习):

13.1 在 Index 中添加跳转按钮

// Index.ets 中增加跳转按钮
Button('查看 Column SpaceAround 布局演示')
  .onClick(() => {
    router.pushUrl({
      url: 'pages/ColumnSpaceAroundDemo'
    })
  })
  .backgroundColor('#007AFF')
  .fontColor(Color.White)
  .borderRadius(8)
  .width(200)
  .height(40)

13.2 在 ColumnSpaceAroundDemo 中添加返回按钮

// 在 TitleSection 下方增加返回按钮
Button('← 返回首页')
  .onClick(() => {
    router.back()
  })
  .backgroundColor('#6C757D')
  .fontColor(Color.White)
  .borderRadius(8)
  .fontSize(12)
  .height(32)
  .width(100)

注意:使用 router API 需要在文件头部导入:

import { router } from '@kit.ArkUI'

14 响应式数据流:@State @Prop 装饰器的工作原理

理解 @State@Prop 的底层机制,是掌握鸿蒙 ArkTS 开发的关键。这两个装饰器构成了组件间数据通信的基础。

14.1 @State——局部响应式状态

@State 装饰的变量具有以下特性:

特性一:响应式

当变量值发生变化时,ArkUI 框架会自动重新执行组件的 build() 方法,更新所有依赖该变量的 UI 部分。这个过程是声明式的——开发者只需关心「数据是什么」,无需关心「如何更新 UI」。

特性二:局部性

@State 变量的作用域仅限于当前组件及其子组件。父组件无法直接访问子组件的 @State 变量,除非子组件通过事件回调(如 @Event 或函数参数)暴露出来。

特性三:可变性

组件可以自由修改自己的 @State 变量。在本示例中,onClick 回调直接修改了 selectedIndex

.onClick(() => { this.selectedIndex = 0 })

14.2 @Prop——父传子的单向数据通道

@Prop 装饰的变量用于从父组件接收数据。其特性包括:

特性一:只读

子组件不能修改 @Prop 的值。这是单向数据流原则的体现。

特性二:默认值

@Prop 变量必须有默认值,当父组件未传递对应属性时使用默认值:

@Prop text: string = ''         // 默认空字符串
@Prop color: string = '#007AFF'  // 默认蓝色

特性三:同步更新

当父组件传递给 @Prop 的值发生变化时,子组件会自动更新。但这种更新是有条件的:

  • 如果 @Prop 接收的是基本类型(string、number、boolean),更新是即时的。
  • 如果 @Prop 接收的是对象类型,需要创建新对象才能触发更新(遵循不变性原则)。

14.3 数据流图示

父组件(ColumnSpaceAroundDemo)
  │
  │ @State selectedIndex: number
  │
  ├──→ RoundedCard { index: 0, isSelected: selectedIndex === 0 }
  │       ↑ @Prop index: number      (只读接收)
  │       ↑ @Prop isSelected: boolean(只读接收)
  │
  ├──→ RoundedCard { index: 1, isSelected: selectedIndex === 1 }
  │
  └──→ RoundedCard { index: 2, isSelected: selectedIndex === 2 }

当用户点击卡片时:

用户点击
   │
   ▼
onClick() → this.selectedIndex = N
   │
   ▼
@State 触发响应式更新
   │
   ▼
框架重新执行 build()
   │
   ▼
为每个 RoundedCard 重新计算 isSelected
   │
   ▼
选中卡片显示绿色边框 + ✓ 标记
未选中卡片恢复默认样式

14.4 与 @Link 的区别

ArkTS 还提供了 @Link 装饰器,用于双向同步

  • @State:局部可变,单向向下传
  • @Prop:只读接收,单向
  • @Link:双向同步,父子组件共享同一份数据的引用

在多数场景中,@Prop 的数据流更可控、更容易调试,因此是推荐的首选方案。只有在需要子组件主动修改数据并反映到父组件时,才考虑使用 @Link 或事件回调。


15 与 SpaceBetween 的差异:首尾边距的微妙区别

SpaceBetween 是最容易被混淆的排列策略之一,它与 SpaceAround 只有一个区别:首尾无边距

15.1 SpaceBetween 的数学描述

首部间距 = 0
相邻间距 = R / (N - 1)     // R 为剩余空间
尾部间距 = 0

注意:当 N = 1 时,SpaceBetween 无法分配间距(分母为 0)。因此在仅有一个子组件时,SpaceBetween 等同于 Start。

15.2 视觉对比

SpaceAround:   ↕[卡]══[卡]══[卡]↕     (首尾有半间距)
SpaceBetween:  [卡]════[卡]════[卡]    (首尾贴边)

15.3 代码差异

将 SpaceAround 改为 SpaceBetween 只需修改一个单词:

// SpaceAround
.justifyContent(FlexAlign.SpaceAround)

// SpaceBetween
.justifyContent(FlexAlign.SpaceBetween)

15.4 适用场景

场景 推荐策略
底部分页指示器(圆点) SpaceBetween,让圆点均匀分布在整行
顶部导航栏的菜单项 SpaceBetween,左右贴边最大化利用空间
信息卡片列表 SpaceAround,保留呼吸感
设置页面的选项组 SpaceAround,每组之间有适中的间距

16 与 SpaceEvenly 的差异:完全均匀与半环绕

SpaceEvenly 是最直观的排列策略——所有间距完全相等。

16.1 SpaceEvenly 的数学描述

首部间距 = R / (N + 1)
相邻间距 = R / (N + 1)
尾部间距 = R / (N + 1)

也就是将剩余空间等分成 N + 1 份,每一份都相等。

16.2 三者对比

以高度 500px、5 个各高 80px 的卡片为例(总子组件高度 400px,剩余 100px):

SpaceBetween:  首=0px    中间=25px   尾=0px
SpaceAround:   首=10px   中间=20px   尾=10px   (首尾 = 中间/2)
SpaceEvenly:   首=16.67px 中间=16.67px 尾=16.67px  (全部相等)

注意观察:虽然 SpaceEvenly 是「完全均匀」,但它两端留白最多(16.67px),而 SpaceAround 的中间间距最大(20px),两端适中(10px)。哪一种更美观?这取决于设计和审美偏好。

在实际的 UI 设计中:

  • SpaceAround 更适合内容区块的排列,视觉重心更突出中间区域。
  • SpaceEvenly 更适合需要绝对对称的场景,如设置页面中的开关项。

17 与 Center 的差异:无间距居中排列

Center 是最简单的排列方式——所有子组件作为一个整体居中,子组件之间没有间距。

17.1 Center 的数学描述

将 N 个子组件视为一个整体
整体的总尺寸 = ΣS
整体居中放置,距首尾的距离相等 = (L - ΣS) / 2
子组件之间没有间距

17.2 代码对比

// 本示例 —— SpaceAround
Column() {
  Card1()
  Card2()
  Card3()
}
.justifyContent(FlexAlign.SpaceAround)

// 如果改为 Center
Column() {
  Card1()
  Card2()
  Card3()
}
.justifyContent(FlexAlign.Center)

17.3 何时使用 Center

Center 适用于以下场景:

  • 按钮组(如"确定/取消"按钮并排时用 Row + Center)
  • 居中的图标
  • 需要整体居中但内部靠左排列的内容(Center + alignItems(ItemAlign.Start) 组合技)

18 实战技巧:容器高度对 SpaceAround 效果的影响

18.1 高度越大,间距越明显

// 不同高度下的效果
Column() {
  // 5 张卡片
}
.justifyContent(FlexAlign.SpaceAround)
.height(400)  // 间距较小,SpaceAround 效果不够明显
.height(500)  // 间距适中,推荐
.height(700)  // 间距很大,视觉上更松散

18.2 子组件高度的影响

子组件的绝对高度会影响剩余空间的计算。如果子组件高度过大,剩余空间变小,SpaceAround 的效果就会减弱。

例如,如果将 RoundedCard 的高度从 80px 改为 90px:

5 × 90 = 450
容器高度 500
剩余空间 50
首尾间距 = 50 / 10 = 5px      ← 几乎看不出来了
相邻间距 = 50 / 5 = 10px

18.3 自适应高度的处理

在实际开发中,有时子组件的高度是动态的(如文本内容行数不定)。此时可以使用 .layoutWeight(1).constraintSize() 来控制子组件的最小/最大尺寸,确保 SpaceAround 有稳定的表现。


19 常见陷阱:为什么必须设置固定高度

这是 SpaceAround 布局中最容易踩的坑,值得单独用一整节来讲解。

19.1 陷阱重现

// ❌ 错误写法 —— SpaceAround 不生效
Column() {
  Card1()
  Card2()
  Card3()
}
.justifyContent(FlexAlign.SpaceAround)
// 没有设置 height!

运行效果:所有卡片紧密排列在顶部,与 justifyContent(Start) 的效果完全一致。

19.2 原因分析

justifyContent 的作用是分配剩余空间。当 Column 没有设置固定高度时,Column 的高度由子组件撑满(wrap_content 模式)。此时:

Column 高度 = 子组件总高度
剩余空间 = Column 高度 - 子组件总高度 = 0

剩余空间为 0,无论是 SpaceAround、SpaceEvenly 还是 SpaceBetween,分配结果都是 0 间距,等同于 Start。

19.3 解决方案

方案一:设置固定高度(推荐)

Column() {
  // 子组件
}
.height(500)   // ← 为 Column 设置固定高度
.justifyContent(FlexAlign.SpaceAround)

方案二:使用 layoutWeight 弹性分配

Column() {
  Card1().layoutWeight(1)
  Card2().layoutWeight(1)
  Card3().layoutWeight(1)
}
.justifyContent(FlexAlign.SpaceAround)
.height('100%')  // 父容器需要提供确定的空间

方案三:父容器约束

让父容器(如 Scroll 或 Row)为 Column 提供尺寸约束:

Scroll() {
  Column() {
    // ...
  }
  .height('100%')       // Scroll 提供了确定的高度范围
  .justifyContent(FlexAlign.SpaceAround)
}

19.4 调试技巧

如果 SpaceAround 没有生效,依次检查:

  1. 容器是否有 height 属性?
  2. height 是否为一个确定值(Number 或百分比 '100%')?
  3. 父容器是否提供了可计算的尺寸上下文?
  4. 子组件的总高度是否小于容器高度?

20 嵌套布局:Scroll + Column 的组合使用场景

本示例中最外层的结构是 Scroll 包裹 Column,这是一种非常常见的布局模式。

20.1 为什么要用 Scroll

当页面内容(标题 + 说明 + 核心演示 + 图例 + 对比 + 底部留白)的总高度超过屏幕高度时,Scroll 允许用户上下滚动查看隐藏的内容,保证了所有信息都能被访问。

20.2 Scroll + Column 的结构

Scroll() {
  Column() {
    TitleSection()
    DescriptionCard()
    // 核心演示区域
    Column() {
      // 5 张卡片
    }
    .height(500)           // ← 核心演示区自身固定高度
    .justifyContent(FlexAlign.SpaceAround)

    SpacingLegend()
    ComparisonSection()
    BlankSpace()
  }
  .width('100%')
  .padding(16)
}
.width('100%')
.height('100%')

20.3 注意:Scroll 不影响内部 Column 的 SpaceAround

外层的 Scroll 只提供滚动能力,不会干涉内部 Column 的布局计算。核心演示区的 Column 设置了 height(500),这个高度是相对于 Scroll 内容的布局尺寸而言的,SpaceAround 在其中正常工作。

20.4 性能考虑

Scroll 在渲染大量子组件时,会一次性构建所有子组件。如果子组件数量极大(数百个),建议使用 List 组件代替 Scroll + Column,因为 List 支持列表项复用懒加载,性能更优。

但对于不到 10 个子组件的演示页面,Scroll + Column 是轻量且高效的方案。


21 视觉设计:颜色、圆角与阴影的搭配原则

一个好的布局示例不仅要演示功能,还应展示良好的视觉设计。本示例在配色、圆角和阴影方面遵循了简洁、清晰的视觉原则。

21.1 色彩方案

本示例使用了五张不同颜色的卡片,形成鲜明对比:

卡片 主色 效果
卡片 1 #FF6B81 暖红色 醒目,作为开头
卡片 2 #5352ED 靛蓝色 稳重,作为过渡
卡片 3 #2ED573 翠绿色 清新,作为视觉中心
卡片 4 #FFA502 橙色 温暖,承上启下
卡片 5 #A855F7 紫色 高贵,作为结尾

五种颜色按照 红→蓝→绿→橙→紫 的色谱排列,视觉上形成渐变效果。

21.2 圆角设计

  • 卡片圆角:borderRadius(12),柔和的圆角让卡片显得友好
  • 序号圆标:borderRadius(18),即 width/height 的一半,形成完美圆形
  • 容器圆角:borderRadius(16),最外层容器的圆角略大于内部卡片

圆角的大小遵循一致性原则:容器圆角 > 卡片圆角 > 内部元素圆角,形成层级感。

21.3 阴影使用

默认卡片阴影:

.shadow({
  radius: 4,
  color: 'rgba(0,0,0,0.1)',
  offsetX: 0,
  offsetY: 2
})

选中卡片阴影:

.shadow({
  radius: 8,
  color: 'rgba(52,199,89,0.3)',
  offsetX: 0,
  offsetY: 2
})

阴影的参数含义:

  • radius:模糊半径,值越大阴影越扩散。选中态从 4 增加到 8,产生「浮起」的视觉效果。
  • color:阴影颜色。选中态使用绿色半透明(rgba(52,199,89,0.3)),与边框颜色呼应。
  • offsetX/offsetY:阴影偏移。保持 y:2 向下偏移 2px,模拟自然光照。

22 组件化思想:@Component 拆分与复用策略

本示例将页面拆分为 7 个自定义组件,体现了「组件化优先」的设计思想。

22.1 组件清单

组件名 文件内位置 职责 复用潜力
RoundedCard struct 圆角信息卡片 ★★★★★ 高复用
TitleSection struct 页面标题 ★★★ 中等复用
DescriptionCard struct 功能说明 ★★ 低复用
KeyPoint struct 要点行 ★★★★ 高复用
SpacingLegend struct 间距图例 ★ 专用于本示例
ColorBlock struct 彩色条块 ★★★★ 高复用
AlignRow struct 对齐方式对比行 ★★ 低复用
ComparisonSection struct 对比区域 ★ 专用于本示例
BlankSpace struct 底部留白 ★★ 低复用

22.2 组件拆分原则

本示例遵循了以下几个组件拆分原则:

原则一:单一职责

每个组件只做一件事。TitleSection 只负责显示标题,DescriptionCard 只负责布局说明,不相互耦合。

原则二:可配置性

通过 @Prop 接收配置参数,让组件适用不同场景。例如 RoundedCard 可自定义标题、描述、颜色、序号和选中状态。

原则三:一致的接口

所有子组件都使用 @Prop 接收参数,不使用 @Link 或全局变量,保证了接口的清晰和可控。

22.3 组件的优化潜力

如果这个文件被用于更大型的项目,可以考虑将高频复用的组件(如 RoundedCardKeyPointColorBlock)抽取到独立的文件中:

// components/RoundedCard.ets
@Component
export struct RoundedCard {
  // ...
}

// components/KeyPoint.ets
@Component
export struct KeyPoint {
  // ...
}

然后通过 import 导入使用:

import { RoundedCard } from '../components/RoundedCard'
import { KeyPoint } from '../components/KeyPoint'

23 代码风格:ArkTS 装饰器语法的书写规范

鸿蒙 ArkTS 作为 TypeScript 的超集,在装饰器语法上有一些特定的书写规范。本示例严格遵守了以下最佳实践。

23.1 装饰器顺序

// 正确写法
@Entry
@Component
struct ColumnSpaceAroundDemo {
  // ...
}

@Entry 必须在 @Component 之前。如果写反,编译器会报错。

23.2 @Prop 默认值

所有 @Prop 变量都必须有默认值。这是 ArkTS 的语法要求:

// ✅ 正确
@Prop text: string = ''
@Prop count: number = 0
@Prop isActive: boolean = false

// ❌ 错误 - 没有默认值
@Prop text: string

23.3 链式调用规范

ArkUI 的属性设置采用链式调用风格,每个属性修饰符返回组件本身。建议按以下顺序排列属性修饰符:

1. 尺寸属性:width, height
2. 布局属性:padding, margin, align
3. 视觉属性:backgroundColor, borderRadius, borderWidth
4. 效果属性:shadow, opacity
5. 事件绑定:onClick

例如:

Row()
  .width('100%')              // 1. 尺寸
  .height(80)                  // 1. 尺寸
  .padding({ left: 16 })       // 2. 布局
  .backgroundColor(Color.White) // 3. 视觉
  .borderRadius(12)            // 3. 视觉
  .shadow({ radius: 4, ... })  // 4. 效果
  .onClick(() => { ... })      // 5. 事件

23.4 条件渲染括号

条件渲染的 if 语句后必须使用花括号 {}

// ✅ 正确
if (this.isSelected) {
  Text('✓')
}

// ❌ 错误 - 缺少花括号
if (this.isSelected)
  Text('✓')

23.5 Build 函数结构

build() 函数必须返回一个且仅一个根组件。不能返回多个并列的组件:

build() {
  // ✅ 正确 —— 只有一个根组件 Scroll
  Scroll() {
    Column() { /* ... */ }
  }
}

// ❌ 错误 —— 返回了两个并列组件
build() {
  Text('Hello')
  Text('World')
}

24 调优建议:性能与可维护性的平衡

虽然本示例规模较小,但依然可以讨论一些通用的性能优化和代码维护策略。

24.1 性能优化

建议一:减少不必要的条件渲染

RoundedCard 中,if (this.isSelected) 的条件渲染是合理的使用场景。但如果条件渲染的逻辑非常复杂(如多个分支),建议替换为 visibility 属性:

// 方式一:if 条件渲染(完全移除/添加节点)
if (this.showHint) {
  Text('提示信息')
}

// 方式二:visibility 控制(保留节点,仅隐藏)
Text('提示信息')
  .visibility(this.showHint ? Visibility.Visible : Visibility.None)

if 的方式性能更低(每次条件变化要创建/销毁节点),但更节省内存(不显示的节点不占内存)。
visibility 的方式性能更高(节点始终存在),但始终占用内存。

建议二:避免 build 函数中的复杂计算

所有需要计算的值都应该在 build() 外部提前计算好,或者在 @State 的 getter 中计算:

// ❌ 不推荐 —— 每次 build 都重复计算
Text(`${this.selectedIndex * 2 + this.cardCount * 3}`)

// ✅ 推荐 —— 提前计算
@State computedValue: number = 0
// 在适当的时候更新 computedValue

24.2 可维护性优化

建议三:抽取常量

将颜色、字号等常量抽取为模块级常量,避免魔术数字:

// 页面底部
const COLORS = {
  primary: '#007AFF',
  danger: '#FF6B81',
  success: '#2ED573',
  warning: '#FFA502',
  info: '#5352ED'
} as const

const FONT_SIZES = {
  title: 22,
  body: 16,
  small: 13,
  tiny: 11
} as const

建议四:使用 @Builder 复用局部 UI

对于不需要独立组件的复用 UI 片段,可以使用 @Builder 装饰器:

@Builder
CardLabel(text: string) {
  Text(text)
    .fontSize(10)
    .fontColor(Color.White)
    .backgroundColor(Color.Red)
    .borderRadius(8)
    .padding({ left: 6, right: 6, top: 2, bottom: 2 })
}

然后在 build() 中调用:

Stack() {
  RoundedCard({ /* ... */ })
  this.CardLabel('首')
    .align(Alignment.TopEnd)
    .offset({ x: 0, y: -8 })
}

25 总结与展望:鸿蒙原生布局的未来方向

25.1 本文回顾

在本文中,我们通过一个完整的 ArkTS 示例,深入剖析了鸿蒙原生布局中 Column + FlexAlign.SpaceAround 的用法、原理和最佳实践。

我们从核心概念讲起,逐步展开了以下内容:

  • Flex 弹性布局的主轴与交叉轴模型
  • SpaceAround 的数学本质:首尾间距 = 相邻间距 ÷ 2
  • 四大 FlexAlign 值的对比:SpaceAround vs SpaceBetween vs SpaceEvenly vs Center
  • 完整的代码实现:从 @Entry @Component 到各个子组件的详细解读
  • 组件化设计思想:单一职责、可配置性、一致接口
  • 响应式数据流@State@Prop 装饰器的工作原理
  • 间距图例的自指涉设计:用 SpaceAround 展示 SpaceAround
  • 常见陷阱与解决方案:为什么必须设置固定高度

25.2 关键记忆

对于希望掌握鸿蒙布局的开发者,下面三句话值得记住:

  1. 「SpaceAround 的核心规律是首尾间距 = 相邻间距 ÷ 2」 —— 这个公式比任何代码示例都更能帮助理解。
  2. 「无高度,不 SpaceAround」 —— 容器必须设置固定高度,否则剩余空间为 0,SpaceAround 不生效。
  3. 「组件化是 ArkTS 的灵魂」 —— 学会用 @Component 拆分 UI,用 @Prop 传递数据,是写好鸿蒙应用的关键。

25.3 展望

HarmonyOS NEXT 作为华为新一代操作系统,其 UI 框架 ArkUI 仍在快速演进中。从 API 24(HarmonyOS NEXT 6.1.1)开始,ArkUI 在布局能力、动画系统、跨设备协同方面都有了显著的提升。

未来值得关注的布局相关特性包括:

  • 自适应布局:基于 GridRowGridCol 的响应式栅格系统
  • 高级动画:基于属性动画(.animation())和转场动画(.transition())的平滑布局过渡
  • 跨设备布局:通过 @LocalStorageProp@LocalStorageLink 实现跨设备状态同步
  • 布局性能优化LazyForEach 懒加载列表的布局性能提升

掌握 SpaceAround 只是鸿蒙原生布局之路的第一步。当你能灵活运用 ColumnRowStackGridRow 等基础容器,并理解 justifyContentalignItemsalignSelf 等布局属性的组合变化时,你就能构建出几乎任何复杂度的 UI 界面。


本文通过 HarmoneyOS NEXT SDK 6.1.1(API 24)环境验证,所有代码均可在 DevEco Studio 中编译运行。

Logo

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

更多推荐