鸿蒙原生 ArkTS 布局精讲:RelativeContainer 实现居中加载动画

基于 HarmonyOS NEXT API 24(SDK 7.0+),使用 ArkTS + RelativeContainer + LoadingProgress,构建一个优雅的全屏居中加载动画页面。


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

一、前言

HarmonyOS NEXT 彻底剥离 Android 内核走向全栈自研,开发者必须掌握纯正的鸿蒙原生开发技术——ArkTS 语言和 ArkUI 声明式 UI 框架。本文将围绕 RelativeContainer + LoadingProgress,深入讲解如何在鸿蒙 NEXT 下实现全屏居中加载动画


二、环境准备与 API 版本说明

项目 说明
IDE DevEco Studio 5.0+
SDK HarmonyOS SDK 7.0.0 (API 24)
语言 ArkTS
框架 ArkUI(声明式 UI)

API 24 是鸿蒙生态的重要里程碑,相比 API 23 在类型安全、组件库和运行时性能上均有提升。


三、核心技术概览

3.1 RelativeContainer —— 相对定位容器

RelativeContainer 是 ArkUI 提供的相对定位布局容器,子组件通过 alignRules 指定自身相对于父容器或其他兄弟组件的摆放位置。

Row / Column 线性布局不同,RelativeContainer 不依赖代码书写顺序,通过显式声明锚点实现更自由精确的定位。

核心定位语法:

alignRules({
  top:    { anchor: '锚点组件ID', align: VerticalAlign.Top },
  center: { anchor: '锚点组件ID', align: VerticalAlign.Center },
  bottom: { anchor: '锚点组件ID', align: VerticalAlign.Bottom },
  left:   { anchor: '锚点组件ID', align: HorizontalAlign.Left },
  middle: { anchor: '锚点组件ID', align: HorizontalAlign.Center },
  right:  { anchor: '锚点组件ID', align: HorizontalAlign.Right },
})

每个条目包含两个关键字段:

字段 类型 说明
anchor string 锚点组件的 id__container__ 表示父容器
align enum 对齐方式,使用 VerticalAlign / HorizontalAlign

3.2 __container__ —— 特殊锚点名

__container__ 是保留锚点名,代表父容器本身。当组件使用 __container__ 作为锚点,将相对于 RelativeContainer 的边界进行定位。例如:

center: { anchor: '__container__', align: VerticalAlign.Center }   // 垂直居中
middle: { anchor: '__container__', align: HorizontalAlign.Center } // 水平居中

两者组合即实现正中央居中效果。

3.3 LoadingProgress —— 原生加载动画

LoadingProgress 是 ArkUI 内置的轻量加载动画组件:

  • 开箱即用:无需额外引入动画资源,自带旋转动效
  • 可定制颜色:通过 .color() 改变图标颜色
  • 轻量高效:GPU 渲染,性能开销极小
  • 自动动画:挂载后自动旋转,无需手动触发

基本用法:

LoadingProgress()
  .width(60)
  .height(60)
  .color(Color.White)

四、需求分析与设计思路

4.1 需求

  1. 全屏正中央显示 LoadingProgress 旋转加载图标
  2. 加载图标下方显示动态提示文字
  3. 提示文字下方显示实时进度百分比(0%→100% 循环)
  4. 页面底部显示版本版权信息
  5. 渐变色背景,视觉效果优雅

4.2 布局设计

┌─────────────────────────────────────────┐
│  RelativeContainer (100%×100%)          │
│  ┌─ linearGradient 背景 ───────────────┐│
│                                         ││
│          ┌──────────┐                   ││
│          │Loading   │  ← 组件①          ││
│          │Progress  │    anchor:        ││
│          │ (60×60)  │    __container__  ││
│          └────┬─────┘    正中央         ││
│               │                        ││
│       ┌───────┴────────┐               ││
│       │正在加载...       │ ← 组件②      ││
│       │ anchor: loadingSpinner         ││
│       │ top → bottom    │               ││
│       └───────┬────────┘               ││
│               │                        ││
│       ┌───────┴────────┐               ││
│       │     56%        │ ← 组件③      ││
│       └────────────────┘               ││
│                                        ││
│ ┌──────────────────────────────────┐   ││
│ │ RelativeContainer + LoadingProgress│  ││
│ │ 示例 ← 组件④ bottom → __container__│  ││
│ └──────────────────────────────────┘   ││
└─────────────────────────────────────────┘

4.3 组件关系

组件 ID 锚点 效果
LoadingProgress loadingSpinner __container__ center + middle 正中央
提示文字 loadingLabel loadingSpinner top→bottom 图标正下方
百分比文字 progressPercent loadingSpinner top→bottom 图标下方
底部文字 footerHint __container__ bottom + middle 底部居中

五、完整代码

import { BusinessError } from '@kit.BasicServicesKit';

@Entry
@Component
struct Index {
  @State loadingProgress: number = 0;
  @State loadingText: string = '正在加载...';
  private timerId: number = -1;

  aboutToAppear(): void {
    this.timerId = setInterval(() => {
      if (this.loadingProgress < 100) {
        this.loadingProgress++;
      } else {
        this.loadingProgress = 0;
      }
      if (this.loadingProgress < 30) {
        this.loadingText = '正在加载...';
      } else if (this.loadingProgress < 60) {
        this.loadingText = '加载中,请稍候';
      } else if (this.loadingProgress < 90) {
        this.loadingText = '即将完成...';
      } else {
        this.loadingText = '马上就好...';
      }
    }, 80);
  }

  aboutToDisappear(): void {
    clearInterval(this.timerId);
  }

  build() {
    RelativeContainer() {
      // 组件① 加载图标——锚定父容器正中央
      LoadingProgress()
        .id('loadingSpinner')
        .width(60).height(60).color(Color.White)
        .alignRules({
          center: { anchor: '__container__', align: VerticalAlign.Center },
          middle: { anchor: '__container__', align: HorizontalAlign.Center }
        })
        .shadow({ radius: 12, color: 'rgba(0,0,0,0.15)', offsetX: 0, offsetY: 4 })

      // 组件② 提示文字——锚定到 loadingSpinner 底部
      Text(this.loadingText)
        .id('loadingLabel')
        .fontSize(16)
        .fontColor('rgba(255,255,255,0.90)')
        .fontWeight(FontWeight.Medium)
        .textAlign(TextAlign.Center)
        .alignRules({
          top: { anchor: 'loadingSpinner', align: VerticalAlign.Bottom },
          middle: { anchor: '__container__', align: HorizontalAlign.Center }
        })
        .margin({ top: 16 })

      // 组件③ 百分比文字
      Text(this.loadingProgress + '%')
        .id('progressPercent')
        .fontSize(14)
        .fontColor('rgba(255,255,255,0.60)')
        .textAlign(TextAlign.Center)
        .alignRules({
          top: { anchor: 'loadingSpinner', align: VerticalAlign.Bottom },
          middle: { anchor: '__container__', align: HorizontalAlign.Center }
        })
        .margin({ top: 48 })

      // 组件④ 底部提示——锚定到父容器底部
      Text('RelativeContainer + LoadingProgress 示例')
        .id('footerHint')
        .fontSize(12)
        .fontColor('rgba(255,255,255,0.35)')
        .textAlign(TextAlign.Center)
        .alignRules({
          bottom: { anchor: '__container__', align: VerticalAlign.Bottom },
          middle: { anchor: '__container__', align: HorizontalAlign.Center }
        })
        .margin({ bottom: 32 })
    }
    .width('100%').height('100%')
    .linearGradient({
      direction: GradientDirection.Bottom,
      colors: [['#667eea', 0], ['#764ba2', 1]]
    })
  }
}

六、代码深度解析

6.1 @Entry @Component 装饰器

@Entry    // 标记为页面入口,每个页面唯一
@Component // 声明为 ArkUI 自定义组件
struct Index { }

API 24 中 @Component 支持更严格的类型检查和更丰富的生命周期钩子。

6.2 @State 响应式状态

@State loadingProgress: number = 0;
@State loadingText: string = '正在加载...';

@State 是 ArkTS 响应式编程的核心——变量变化时框架自动重绘依赖该变量的 UI。loadingProgress 每 80ms 递增驱动百分比更新;loadingText 随进度阶段变化,让用户感知加载进程。

6.3 生命周期管理

回调 触发时机 用途
aboutToAppear 组件即将挂载到视图树 启动定时器、初始化数据
aboutToDisappear 组件即将从视图树移除 清除定时器、释放资源

关键:必须在 aboutToDisappear 中清除 setInterval,否则页面退出后定时器仍在运行,导致内存泄漏。

6.4 alignRules 定位详解

场景一:相对于父容器居中

.alignRules({
  center: { anchor: '__container__', align: VerticalAlign.Center },
  middle: { anchor: '__container__', align: HorizontalAlign.Center }
})

执行逻辑:

  1. center → 组件的垂直中心线
  2. anchor: '__container__' → 锚定到父容器
  3. align: VerticalAlign.Center → 与锚点垂直中心线对齐
  4. 结果:组件垂直中心线与父容器垂直中心线重合 → 垂直居中
  5. 同理 middle 实现水平居中

场景二:相对于兄弟组件底部对齐

.alignRules({
  top: { anchor: 'loadingSpinner', align: VerticalAlign.Bottom },
  middle: { anchor: '__container__', align: HorizontalAlign.Center }
})

执行逻辑:

  1. top → 组件的上边界
  2. anchor: 'loadingSpinner' → 锚定到 loadingSpinner
  3. align: VerticalAlign.Bottom → 与锚点组件的下边界对齐
  4. 结果:组件上边界紧贴在 loadingSpinner 下边界上
  5. 通过 .margin({ top: 16 }) 增加 16vp 间距

两个场景展示了 RelativeContainer 的一大优势:一个组件可以同时相对于不同锚点定位不同方向——垂直方向跟兄弟组件,水平方向跟父容器。

6.5 渐变色背景

.linearGradient({
  direction: GradientDirection.Bottom,
  colors: [
    ['#667eea', 0],   // 起始色
    ['#764ba2', 1]    // 结束色
  ]
})

API 24 的 GradientDirection 枚举包含:Left / Right / Top / Bottom(基本方向)和 LeftTop / LeftBottom / RightTop / RightBottom(对角线方向)。

注意BottomEndBottomStartTopEndTopStart 在 API 23 中存在,但在 API 24 中已移除。迁移时请改用 RightBottomBottom


七、RelativeContainer 对比其他布局

布局方式 居中复杂度 兄弟定位 推荐场景
RelativeContainer 低(alignRules) 居中、锚定、叠加布局
Row + justifyContent 水平排列
Column + alignItems 垂直排列
Stack + alignContent 叠加布局

对于"居中加载动画"场景,RelativeContainer 的独特优势在于:

  1. 声明式定位:不依赖代码书写顺序,每个组件独立声明位置
  2. 组件间锚定:提示文字轻松跟随加载图标
  3. 混合锚定:同一组件垂直方向跟兄弟组件、水平方向跟父容器
  4. 容器即锚点:无需额外嵌套,直接 __container__ 锚定

如果使用 Column 实现:

Column() {
  LoadingProgress().width(60).height(60).color(Color.White)
  Text('正在加载...').fontSize(16).margin({ top: 16 })
  Text('56%').fontSize(14).margin({ top: 8 })
}
.width('100%').height('100%').justifyContent(FlexAlign.Center)

这种方法无法实现"底部信息固定在页面底部"——文字会随 Column 整体居中而非固定在底部。RelativeContainer 则能轻松实现"部分居中、部分固定底部"的混合布局。


八、LoadingProgress 属性与最佳实践

属性 类型 说明
width / height Length 图标尺寸,全屏加载建议 50~70vp
color ResourceColor 支持 Color 枚举、十六进制、rgba、资源引用
visibility Visibility 可见性控制
enabled boolean 是否启用

颜色示例:

// 四种写法均有效
.color(Color.White)
.color('#FFFFFF')
.color('rgba(255, 255, 255, 0.85)')
.color($r('app.color.loading_color'))

大小建议:

  • 全屏加载页:50~70vp
  • 局部加载(下拉刷新):24~36vp
  • 按钮内加载:16~20vp

九、API 24 注意事项

9.1 模块导入路径

API 24 统一使用 @kit.* 格式:

功能 旧路径(API ≤ 23) 新路径(API 24)
基础服务 @ohos.basicServicesKit @kit.BasicServicesKit
网络请求 @ohos.net.http @kit.NetworkKit
数据存储 @ohos.data.preferences @kit.DataKit

9.2 GradientDirection 变化

API 24 移除了 BottomEnd 等方向变体,编译时若遇此错误,直接替换即可:

- direction: GradientDirection.BottomEnd,
+ direction: GradientDirection.Bottom,

9.3 定时器生命周期

setInterval / clearInterval 在 API 24 中行为不变,但务必配对使用,避免页面销毁后定时器持续执行。


十、扩展实践

10.1 结合显隐控制

实际业务中通常需要控制加载页的显隐:

@State isLoading: boolean = true;

build() {
  Stack() {
    YourMainContent()
    if (this.isLoading) {
      RelativeContainer() { /* 加载动画代码 */ }
        .width('100%').height('100%')
    }
  }
}

10.2 配合页面路由

import { router } from '@kit.ArkUI';

this.isLoading = true;
setTimeout(() => {
  router.pushUrl({ url: 'pages/TargetPage' });
}, 2000);

十一、常见问题

Q1:alignRules 不生效?
A:最常见的原因是忘记设置 .id()。RelativeContainer 通过 ID 识别锚点组件。

Q2:加载动画卡顿?
A:LoadingProgress 使用 GPU 渲染,正常情况下非常流畅。检查是否有大量 UI 更新在同一帧触发。

Q3:编译报错 “Child component of RelativeContainer must have a unique ID”?
A:所有子组件必须设置唯一 ID,且不可重复。

Q4:API 24 报错 “Property ‘BottomEnd’ does not exist”?
A:改用 GradientDirection.BottomGradientDirection.RightBottom


十二、总结

本文通过一个完整的全屏居中加载动画示例,讲解了 HarmonyOS NEXT(API 24)中 RelativeContainerLoadingProgress 的联合使用。

核心要点:

  1. RelativeContainer 通过 alignRules + __container__ 实现精确居中
  2. LoadingProgress 是开箱即用的加载动画,配合 @State 实现动态进度
  3. alignRules 支持锚定父容器、锚定兄弟组件、混合锚定
  4. 生命周期管理aboutToAppear 启动、aboutToDisappear 清理
  5. API 24:导入路径用 @kit.*,注意 GradientDirection 枚举变化

RelativeContainer 是鸿蒙布局体系中的"瑞士军刀"——不一定写代码最快,但一定最灵活、最可控。掌握好它,鸿蒙应用的各种复杂布局都能游刃有余。

完整项目代码可在 DevEco Studio 中打开运行,直观感受 RelativeContainer + LoadingProgress 的布局效果。

Logo

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

更多推荐