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

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

鸿蒙 ArkTS 实战:从零开发「兰亭序」阅读器 —— 基于 API 24 (HarmonyOS 6.1.1) 的完整开发指南

一、引言

1.1 为什么选择「兰亭序」作为入门项目

在鸿蒙生态快速发展的今天,越来越多的开发者开始接触 HarmonyOS 应用开发。但对于初学者来说,最困难的事情往往不是学习语法,而是找到一个合适的入手项目——既不能太简单(只写个 Hello World 学不到东西),也不能太复杂(从电商App做起显然不现实)。

「兰亭序」阅读器就是这样一个恰到好处的入门项目。它的核心需求非常清晰:展示一篇文言文文本,配以优雅的排版和交互。但看似简单的需求背后,涉及了 HarmonyOS 应用开发的诸多核心知识点:

  • ArkTS 语言基础与声明式 UI 编程范式
  • ArkUI 组件体系与布局系统
  • 资源管理(字符串、颜色、尺寸等)
  • 页面结构与生命周期
  • 样式与主题设计
  • 调试与构建配置

本文将以「兰亭序」阅读器的实际开发过程为主线,系统地讲解 HarmonyOS 应用开发的完整流程,并针对 API 24 (HarmonyOS 6.1.1) 版本中的新特性和最佳实践进行深入分析。

1.2 项目最终效果预览

在开始编码之前,让我们先明确我们要构建什么:

┌─────────────────────────────┐
│       兰 亭 序              │
│       王 羲 之              │
│     ───●───                 │
│                             │
│  永和九年,岁在癸丑,       │
│  暮春之初,会于会稽山阴    │
│  之兰亭,修禊事也。         │
│  群贤毕至,少长咸集。       │
│  ...                        │
│                             │
│  —— 东晋·王羲之 撰并书     │
│  ─────────────────          │
│  「后之览者,亦将有感于     │
│   斯文」                    │
│        · 终 ·               │
└─────────────────────────────┘

页面主体为纵轴滚动阅读器,仿古书卷风格,背景采用宣纸暖色 #FFF8F0


二、HarmonyOS API 24 核心认知

2.1 什么是 API 24

在开始编码之前,我们需要先理解 API 24 在 HarmonyOS 版本体系中的位置。

HarmonyOS 版本号映射关系:

HarmonyOS 版本 API Level SDK 版本 主要变化
HarmonyOS 3.0 API 9 3.x.x 首个稳定版 ArkUI
HarmonyOS 4.0 API 10 4.x.x 增强声明式UI、Stage模型完善
HarmonyOS 4.1 API 11 5.x.x Added大量新组件
HarmonyOS 5.0 (NEXT) API 12 5.x.x 全栈自研、去AOSP
HarmonyOS 5.5 / 6.x API 24 6.1.1 最新稳定版

API 24 的关键能力包括:

  1. Stage 模型全面成熟:从 API 9 引入的 Stage 模型到 API 24 已经非常成熟,Application、Ability、Context 三层架构清晰稳定
  2. ArkUI 组件体系完整:基础组件、容器组件、媒体组件、画布组件等体系完备
  3. 声明式 UI 性能优化:状态管理机制的底层优化,@State/@Prop/@Link 装饰器链路的改进
  4. 包管理能力增强:oh-package 依赖管理更加完善
  5. 安全能力升级:权限模型更加细化,ACE 安全框架增强

在我们的项目中,build-profile.json5 中的关键配置如下:

{
  "app": {
    "products": [
      {
        "targetSdkVersion": "6.1.1(24)",
        "compatibleSdkVersion": "6.1.1(24)",
        "runtimeOS": "HarmonyOS"
      }
    ]
  }
}

targetSdkVersion 设为 6.1.1(24) 意味着我们明确以 API 24 为目标平台,可以利用该版本的所有最新 API 特性。

2.2 Stage 模型概览

API 24 默认使用 Stage 模型(在 entry/build-profile.json5 中通过 "apiType": "stageMode" 指定)。Stage 模型的核心设计理念是 “以 Ability 为最小调度单元,以 Context 为上下文纽带”

┌─────────────────────────────────────┐
│           Application               │
│  ┌──────────────────────────────┐   │
│  │      UIAbility (EntryAbility) │   │
│  │  ┌──────────────────────┐   │   │
│  │  │  WindowStage          │   │   │
│  │  │  ┌────────────────┐ │   │   │
│  │  │  │  Window (页面)  │ │   │   │
│  │  │  └────────────────┘ │   │   │
│  │  └──────────────────────┘   │   │
│  └──────────────────────────────┘   │
│  ┌──────────────────────────────┐   │
│  │  ExtensionAbility (组件)     │   │
│  └──────────────────────────────┘   │
└─────────────────────────────────────┘

在我们的项目中,EntryAbility.ets 就是继承自 UIAbility 的入口 Ability,它在 onWindowStageCreate 回调中加载主页面:

onWindowStageCreate(windowStage: window.WindowStage): void {
  windowStage.loadContent('pages/Index', (err) => {
    if (err.code) {
      hilog.error(DOMAIN, 'testTag', 'Failed: %{public}s', JSON.stringify(err));
      return;
    }
  });
}

三、项目初始化与配置

3.1 使用 DevEco Studio 创建项目

环境要求:

  • DevEco Studio 5.0+(建议最新版)
  • HarmonyOS SDK API 24
  • Node.js 18+(用于 ohpm 包管理)

创建步骤:

  1. 打开 DevEco Studio → FileNewCreate Project
  2. 选择 Empty Ability 模板(基于 Stage 模型)
  3. 配置项目信息:包名、项目名称、保存路径
  4. SDK 选择 API 24 (HarmonyOS 6.1.1)
  5. 语言选择 ArkTS

3.2 项目结构分析

创建完成后,项目的核心目录结构如下:

MyApplication/
├── AppScope/                    # 应用级配置
│   ├── app.json5               # 应用全局配置(名称、版本、图标等)
│   └── resources/              # 应用级资源
├── entry/                      # 模块目录
│   ├── src/main/
│   │   ├── ets/                # ArkTS 源码目录
│   │   │   ├── entryability/   # Ability 入口
│   │   │   │   └── EntryAbility.ets
│   │   │   └── pages/          # 页面
│   │   │       └── Index.ets
│   │   ├── module.json5        # 模块配置(abilities、权限、设备类型等)
│   │   └── resources/          # 资源文件
│   │       ├── base/           # 基础资源(不限方向)
│   │       │   ├── element/    # 字符串、颜色、浮点数等
│   │       │   ├── media/      # 图片等媒体资源
│   │       │   ├── profile/    # 配置(main_pages、backup_config等)
│   │       │   └── rawfile/    # 原生文件
│   │       └── dark/           # 深色模式资源
│   ├── build-profile.json5     # 模块构建设置
│   ├── oh-package.json5        # ohpm 依赖声明
│   └── hvigorfile.ts           # Hvigor 构建脚本
├── build-profile.json5         # 应用级构建设置
├── hvigor/                     # 构建工具配置
├── hvigorfile.ts               # 根构建脚本
├── oh-package.json5            # 根依赖
└── oh_modules/                 # 依赖安装目录

3.3 关键配置文件详解

build-profile.json5(应用级) 是项目中最重要的配置文件,它决定了应用的目标平台、签名方式和模块结构。

{
  app: {
    signingConfigs: [],          // 签名配置
    products: [
      {
        name: "default",
        signingConfig: "default",
        targetSdkVersion: "6.1.1(24)",     // 目标 SDK
        compatibleSdkVersion: "6.1.1(24)", // 兼容 SDK
        runtimeOS: "HarmonyOS",            // 运行 OS
        buildOption: {
          strictMode: {
            caseSensitiveCheck: true,      // 大小写检查
            useNormalizedOHMUrl: true      // URL 规范化
          }
        }
      }
    ]
  },
  modules: []  // 模块列表
}

module.json5 则声明了模块的具体能力:

{
  module: {
    name: "entry",
    type: "entry",              // entry: 应用入口模块
    deviceTypes: ["phone"],     // 支持的设备类型
    pages: "$profile:main_pages", // 页面配置引用
    abilities: [                // Ability 声明
      {
        name: "EntryAbility",
        srcEntry: "./ets/entryability/EntryAbility.ets",
        exported: true,
        skills: [
          {
            entities: ["entity.system.home"],
            actions: ["ohos.want.action.home"]
          }
        ]
      }
    ]
  }
}

3.4 页面路由配置

页面路由在 resources/base/profile/main_pages.json 中声明:

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

当需要多页面时,只需在此数组中追加新的页面路径即可。例如:

{
  "src": [
    "pages/Index",
    "pages/Detail",
    "pages/About"
  ]
}

四、ArkTS 语言基础:从 TypeScript 到 ArkTS

4.1 ArkTS 与 TypeScript 的关系

ArkTS 是 HarmonyOS 原生开发语言,它基于 TypeScript 进行了定制和增强。理解二者的关系对快速上手至关重要:

方面 TypeScript ArkTS
类型系统 渐进式类型,any 可用 严格类型,禁用 any
装饰器 标准装饰器 增强装饰器(@State、@Prop、@Link 等)
运行时 JavaScript 引擎 方舟编译器直接编译为机器码
UI 描述 声明式 UI(@Component + build)
状态管理 无内置机制 @State / @Prop / @Link / @Provide / @Consume
并发模型 单线程 + Worker TaskPool / Actor 并发模型
null 安全 可选 strictNullChecks 内置空安全
模块系统 ES Module ES Module + ohpm

ArkTS 的严格性约束是开发者需要特别注意的:

// ❌ TypeScript 中可以这样做,但 ArkTS 不允许
let data: any = getData();  // 错误:不支持 any 类型
data.someMethod();

// ✅ ArkTS 中必须明确类型
interface DataType {
  value: string;
}
let data: DataType = getData() as DataType;

4.2 声明式 UI 的核心范式

ArkTS 采用声明式 UI 编程范式。这与传统的命令式 UI(如 Android 的 XML + Java/Kotlin)有本质区别。

命令式编程(伪代码):

// 创建元素 → 设置属性 → 添加到父容器
let text = new TextWidget()
text.setText("Hello")
text.setFontSize(20)
let container = new Column()
container.addChild(text)
rootView.addChild(container)

声明式编程(ArkTS):

build() {
  Column() {
    Text("Hello")
      .fontSize(20)
  }
}

声明式 UI 的核心优势是:开发者只描述"界面应该长什么样",框架负责计算"如何从状态A变到状态B"

4.3 @Component 和 @Entry 装饰器

在 ArkTS 中,一个可复用的 UI 单元通过 @Component 装饰器声明:

@Component
struct MyComponent {
  build() {
    // UI 描述
  }
}

@Entry 装饰器将组件标记为页面的入口:

@Entry
@Component
struct Index {
  build() {
    // 页面 UI
  }
}

关键区别:

  • @Component:声明一个可复用的 UI 组件
  • @Entry:声明该组件是一个页面入口(只能用在页面根组件上)

每个组件必须实现 build() 方法,该方法返回组件的 UI 描述。

4.4 @State 装饰器与状态管理

@State 是 ArkTS 中最常用的装饰器,用于声明组件的内部状态。当状态变量发生变化时,框架会自动重新渲染依赖该状态的 UI 部分。

@Component
struct Counter {
  @State count: number = 0;

  build() {
    Column() {
      Text(`计数: ${this.count}`)
        .fontSize(24)
      Button("增加")
        .onClick(() => {
          this.count++;  // 修改状态,UI 自动更新
        })
    }
  }
}

在我们的兰亭序应用中,虽然目前只有一个页面且没有复杂状态,但理解状态管理仍然是开发更复杂功能的基础。比如后续可以扩展为:

@Component
struct LantingxuReader {
  @State fontSize: number = 20;     // 字体大小
  @State lineHeight: number = 40;   // 行高
  @State backgroundColor: string = '#FFF8F0'; // 背景色

  build() {
    // 根据状态动态调整排版
  }
}

@State 的触发规则:

  • 基础类型(number、string、boolean):赋值即触发
  • 对象类型:属性的直接修改不触发,需要整体赋值
  • 数组:push/pop 不触发,需要重新赋值或使用展开运算符

五、ArkUI 组件体系:构建阅读器界面

5.1 布局容器选择

ArkUI 提供了多种布局容器,正确选择是构建优雅界面的第一步:

容器 特点 适用场景
Column 垂直排列 纵向滚动内容、表单
Row 水平排列 导航栏、头像+文字
Stack 层叠排列 叠加图层、居中定位
Flex 弹性布局 均匀分布、自适应
Grid 网格布局 照片墙、卡片列表
RelativeContainer 相对定位 精确锚点对齐

在我们的阅读器中,使用了 三层嵌套布局

Stack(最外层:层叠背景 + 前景内容)
└── Column(背景层:纯色背景)
└── Scroll(滚动层)
    └── Column(内容层:标题+正文+底部)
        ├── Column(标题区域)
        │   ├── Text(主标题)
        │   ├── Text(副标题)
        │   └── Row(装饰分隔线)
        │       ├── Line
        │       ├── Text(●)
        │       └── Line
        ├── Text(正文)
        ├── Text(底部署名)
        ├── Divider(分隔线)
        ├── Text(引用)
        └── Text(终)

选择 Stack 作为最外层容器,是因为我们需要叠加一个纯色背景层(Column)和前景内容层(Scroll)。这样即使内容不足一屏,背景色也能填满整个屏幕。

5.2 Text 组件的全面使用

Text 是阅读器中最核心的组件。ArkUI 的 Text 组件提供了丰富的排版能力:

Text(content: string | Resource)
  .fontSize(value: number | string | Resource)    // 字号
  .fontWeight(value: number | FontWeight)          // 字重
  .fontColor(value: ResourceColor)                 // 颜色
  .fontStyle(value: FontStyle)                     // 样式(正常/斜体)
  .letterSpacing(value: number)                    // 字间距
  .lineHeight(value: number | string | Resource)   // 行高
  .textAlign(value: TextAlign)                     // 对齐方式
  .textOverflow({ overflow: TextOverflow })        // 溢出处理
  .maxLines(value: number)                         // 最大行数
  .textIndent(value: number)                       // 首行缩进

在兰亭序阅读器中,我们精心配置了正文排版:

Text(this.lantingxuText)
  .fontSize(20)              // 字号 20,适合阅读
  .fontColor('#3C2415')      // 深褐色,仿古籍
  .lineHeight(40)            // 行高 40,保持阅读呼吸感
  .letterSpacing(2)          // 字间距 2,避免拥挤
  .textAlign(TextAlign.Start) // 左对齐,符合阅读习惯
  .width('100%')             // 撑满父容器
  .padding({ left: 24, right: 24 }) // 左右留白,防止文字贴边

多段文字的处理技巧:

由于 Text 组件不支持富文本(同一个 Text 内不同部分有不同的样式),我们用 \n 换行符来分割段落:

private lantingxuText: string =
  '永和九年,岁在癸丑,暮春之初,会于会稽山阴之兰亭,修禊事也。\n' +
  '群贤毕至,少长咸集。\n' +
  '此地有崇山峻岭,茂林修竹;又有清流激湍,映带左右,引以为流觞曲水,列坐其次。\n' +
  '虽无丝竹管弦之盛,一觞一咏,亦足以畅叙幽情。\n\n' +
  // ...更多段落

如果后续需要对不同段落应用不同样式,可以将文本拆分为多个独立的 Text 组件放在 Column 中,各自应用不同的样式属性。

5.3 Scroll 组件实现滚动

阅读器需要支持长文本的滚动浏览。Scroll 组件是实现滚动功能的核心:

Scroll() {
  Column() {
    // 所有内容
  }
  .width('100%')
}
.width('100%')
.height('100%')

Scroll 组件的关键属性:

Scroll(scroller?: Scroller)  // 可选:传入 Scroller 做编程式滚动控制
  .scrollable(ScrollDirection.Vertical)  // 滚动方向
  .scrollBar(BarState.Auto)             // 滚动条策略
  .friction(0.8)                        // 摩擦系数
  .edgeEffect(EdgeEffect.Spring)        // 边缘效果

编程式滚动(进阶用法):

@Component
struct LantingxuReader {
  private scroller: Scroller = new Scroller();

  build() {
    Column() {
      Button("回到顶部")
        .onClick(() => {
          this.scroller.scrollTo({ xOffset: 0, yOffset: 0 })
        })
      Scroll(this.scroller) {
        // 内容
      }
    }
  }
}

5.4 Line 与 Divider 装饰组件

在标题和正文之间,我们使用 Line 组件和 Text 组件组合了一个装饰分隔线:

Row() {
  Line()
    .width(60)
    .height(1)
    .backgroundColor('#C4A882')
  Text('●')
    .fontSize(8)
    .fontColor('#C4A882')
    .margin({ left: 8, right: 8 })
  Line()
    .width(60)
    .height(1)
    .backgroundColor('#C4A882')
}
.margin({ top: 16, bottom: 4 })

Line 组件用于绘制直线段,Divider 组件则用于在水平方向分割区块。注意区分:

  • Line:自由绘制直线,可控制起点终点、长度、颜色
  • Divider:专用分割线组件,自动撑满父容器宽度

5.5 组件属性链式调用模式

ArkUI 组件的一个显著特点是链式调用。每个组件方法都返回组件本身,可以连续调用:

Text('兰 亭 序')
  .fontSize(36)           // 返回 Text 组件
  .fontWeight(FontWeight.Bold)  // 返回 Text 组件
  .fontColor('#2C1810')   // 返回 Text 组件
  .letterSpacing(8)       // 返回 Text 组件
  .textAlign(TextAlign.Center)  // 返回 Text 组件

这种风格非常简洁,但需要注意:

  1. 属性设置顺序无关 —— 链式调用的顺序不影响最终渲染结果
  2. 后设置覆盖前设置 —— 如果一个属性被设置多次,以最后一次为准
  3. 类型安全 —— 编译时检查参数类型,避免传错参数

六、资源管理系统

6.1 资源分类与引用

HarmonyOS 的资源管理系统设计精良,支持多设备、多语言、多方向。

资源目录结构:

resources/
├── base/                    # 基础资源(所有设备、所有语言、所有方向适用)
│   ├── element/             # 元素资源(字符串、颜色、尺寸等)
│   │   ├── string.json
│   │   ├── color.json
│   │   └── float.json
│   ├── media/               # 媒体资源(图片、音视频等)
│   ├── profile/             # 配置文件
│   └── rawfile/             # 原始文件
├── dark/                    # 深色模式覆盖
│   └── element/
│       └── color.json
├── en_GB/                   # 英式英语本地化
├── zh_CN/                   # 简体中文本地化
└── zh_HK/                   # 繁体中文本地化

资源引用的两种方式:

// 方式一:通过 $r() 引用资源
Text($r('app.string.app_name'))
  .fontSize($r('app.float.page_text_font_size'))
  .fontColor($r('app.color.start_window_background'))

// 方式二:直接硬编码
Text('兰 亭 序')
  .fontSize(36)
  .fontColor('#2C1810')

推荐做法: 对于需要多语言支持或可能统一变更的值(如主题色、间距),使用 $r() 资源引用;对于一次性使用的、不会变更的样式(如特定的装饰色),可以硬编码。

6.2 element 资源详细配置

字符串资源(string.json):

{
  "string": [
    {
      "name": "module_desc",
      "value": "兰亭序阅读应用"
    },
    {
      "name": "EntryAbility_desc",
      "value": "兰亭序 - 王羲之"
    },
    {
      "name": "EntryAbility_label",
      "value": "兰亭序"
    }
  ]
}

这些字符串会用在:

  • module.json5 中的 descriptionlabel 字段
  • 系统设置中的应用名称
  • 多任务管理中的应用标题

颜色资源(color.json):

{
  "color": [
    {
      "name": "start_window_background",
      "value": "#FFF8F0"
    }
  ]
}

start_window_background 是一个特殊颜色资源,它定义了应用启动时的窗口背景色。将其设为 #FFF8F0 与阅读器界面保持一致,可以消除启动时的白屏闪烁。

Float 值资源(float.json):

{
  "float": [
    {
      "name": "page_text_font_size",
      "value": "50fp"
    }
  ]
}

这里的 50fp 是模板默认值,我们在实际开发中直接设置了具体的字号数值,未使用该资源。fp 是 HarmonyOS 的字体单位缩写,与 dp 类似但会跟随系统字体大小设置缩放。

6.3 深色模式支持

resources/dark/ 下,我们可以提供深色模式的颜色覆盖:

// resources/dark/element/color.json
{
  "color": [
    {
      "name": "start_window_background",
      "value": "#1A1A1A"  // 深色模式下用深色背景
    }
  ]
}

这样当系统切换到深色模式时,应用会自动使用深色背景。我们的阅读器要支持深色模式,可以在 dark 目录下覆盖所有颜色值,同时页面中的文字颜色也通过资源引用来定义。

6.4 多语言和本地化实践

resources 目录下,我们可以为不同语言创建子目录来实现国际化。例如,要为英文用户提供界面翻译:

resources/
├── base/                    # 默认(中文)资源
│   └── element/
│       ├── string.json      # 中文文本
│       └── color.json
├── en_US/                   # 美式英语
│   └── element/
│       └── string.json      # 英文翻译
└── dark/
    └── element/
        └── color.json

en_US/element/string.json 示例:

{
  "string": [
    {
      "name": "app_title",
      "value": "Preface to Orchid Pavilion"
    },
    {
      "name": "app_author",
      "value": "Wang Xizhi (303–361)"
    },
    {
      "name": "app_subtitle",
      "value": "The Greatest Masterpiece of Chinese Calligraphy"
    }
  ]
}

在代码中,通过 $r() 引用即可自动匹配当前系统语言:

// 中英文模式下自动显示不同的文本
Text($r('app.string.app_title'))
  .fontSize(36)
  .fontWeight(FontWeight.Bold)
  .letterSpacing(8)

这种资源引用方式的好处是彻底解耦了代码和文本——添加新语言不需要修改任何 .ets 文件,只需新建资源目录和对应的 JSON 文件即可。

6.5 响应式布局适配

虽然「兰亭序」阅读器目前仅面向手机设备,但 API 24 提供了强大的响应式布局能力。我们可以通过 GridRow/GridCol 和断点监听来实现对不同屏幕尺寸的适配:

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

@Component
struct ResponsiveReader {
  @State isWideScreen: boolean = false;

  aboutToAppear(): void {
    // 监听屏幕宽度变化
    let listener = mediaquery.matchMediaSync('(min-width: 600)');
    listener.on('change', (result) => {
      this.isWideScreen = result.matches;
    });
  }

  build() {
    if (this.isWideScreen) {
      // 平板或折叠屏展开态:分两栏显示
      Row() {
        Text('原文').width('50%')
        Text('注释').width('50%')
      }
    } else {
      // 手机竖屏:单栏滚动
      Scroll() {
        Text('原文')
      }
    }
  }
}

七、构建配置与签名

7.1 Hvigor 构建系统

HarmonyOS 使用 Hvigor 作为构建系统(类似于 Android 的 Gradle)。核心配置文件包括:

  • hvigorfile.ts:导入模块级构建脚本
  • 模块 hvigorfile.ts:模块级构建任务配置
  • hvigor/hvigor-config.json5:构建工具版本和插件配置

hvigor-config.json5 示例:

{
  modelVersion: "5.0.0",
  toolChains: {
    hvigor: "5.5.0",
    hvigor-arkts: "5.5.0",
    hvigor-ohos: "5.5.0"
  }
}

7.2 签名配置

在发布应用之前,必须配置签名。签名文件类型:

类型 用途 有效期
Debug 签名 开发调试,自动生成 随时可重新生成
Release 签名 应用市场发布 需要正式申请

签名配置在 build-profile.json5signingConfigs 数组中。在 DevEco Studio 中可以通过 UI 界面生成和管理。

7.3 编译选项配置

entry/build-profile.json5 中的编译选项:

{
  apiType: "stageMode",
  buildOption: {
    resOptions: {
      copyCodeResource: {
        enable: false
      }
    }
  },
  buildOptionSet: [
    {
      name: "release",
      arkOptions: {
        obfuscation: {
          ruleOptions: {
            enable: false,
            files: ["./obfuscation-rules.txt"]
          }
        }
      }
    }
  ]
}

关键配置解释:

  • apiType:选择 Stage 模型
  • copyCodeResource.enable:是否复制源码资源到产物中
  • obfuscation:代码混淆配置,Release 构建时建议开启
  • obfuscation-rules.txt:混淆规则文件,可指定哪些类/方法不混淆

八、性能优化与最佳实践

8.1 长文本渲染优化

对于长文本阅读器,性能优化的主要考虑点是:

1. 避免使用过多的布局嵌套

每个 Column/Row 都是独立的布局节点。对于非常长的内容,可以将段落文本组合到一个 Text 中(用 \n 分隔),而不是每个段落一个 Text 组件。

推荐做法:

Text('段落1\n段落2\n段落3\n...')  // 一个 Text 组件渲染全部
  .fontSize(20)
  .lineHeight(40)

不推荐的做法:

Column() {
  Text('段落1').fontSize(20).lineHeight(40)
  Text('段落2').fontSize(20).lineHeight(40)
  Text('段落3').fontSize(20).lineHeight(40)
  // 每个段落都需要独立布局计算
}

2. 懒加载(LazyForEach)

如果内容非常长(例如数万字的小说),可以使用 LazyForEach 实现懒加载渲染:

class ParagraphSource extends DataSource {
  // 实现数据源
}

LazyForEach(new ParagraphSource(), (item: string) => {
  Text(item)
    .fontSize(20)
    .lineHeight(40)
}, (item: string) => item)

LazyForEach 只渲染可视区域内的组件,滚动时回收不可见组件,大幅降低内存占用。

8.2 状态更新的最小化

在 ArkTS 中,@State 变量的修改会触发组件重新渲染。为了性能,应该:

1. 将状态尽量向下传递

// ✅ 推荐:只有需要的地方才使用 @State
@Entry
@Component
struct Index {
  @State fontSize: number = 20;

  build() {
    Column() {
      TextReader({ fontSize: this.fontSize })
      FontSizeChanger({ onChange: (v) => { this.fontSize = v } })
    }
  }
}

@Component
struct TextReader {
  @Prop fontSize: number;  // 仅接收,不修改
  build() {
    Text('内容').fontSize(this.fontSize)
  }
}

2. 避免频繁修改 @State

如果需要连续修改多个状态,尽量减少修改次数:

// ❌ 多次触发渲染
this.count++;
this.name = 'new';
this.visible = true;

// ✅ 合并状态(或将相关状态合并为一个对象)
this.state = { count: 1, name: 'new', visible: true };
// 但注意:对象整体赋值才会触发

8.3 资源复用

对于固定的字符串常量(如兰亭序全文),声明为 private 成员变量而非 @State

// ✅ 正确:常量数据,不需要响应式
private lantingxuText: string = '全文内容...';

// ❌ 错误:数据不变化,不需要 @State
@State lantingxuText: string = '全文内容...';

二者的区别:

  • 普通成员变量:只读,不触发 UI 更新
  • @State 变量:变化时触发 UI 渲染

对于不会变化的数据,使用普通成员变量可以避免不必要的渲染检查。

8.4 图片资源优化

虽然我们的阅读器没有使用图片,但作为扩展建议:

  • 格式选择:优先使用 PNG(支持透明),非透明图片用 JPEG
  • 分辨率适配:提供不同 DPI 的图片(ldpi/mdpi/hdpi/xhdpi/xxhdpi)
  • 矢量图:对于简单图标,使用 SVG 而非位图
  • 压缩:使用 TinyPNG 等工具压缩,减小包体积

九、测试与调试

9.1 DevEco Studio 调试工具

DevEco Studio 提供了完善的调试支持:

1. Previewer(预览器)

在编码过程中,可以实时预览 UI 效果:

右侧面板 → Previewer 标签页

Previewer 支持:

  • 实时预览代码变更
  • 多设备模拟(手机、平板、折叠屏等)
  • 交互操作模拟(点击、滑动等)

2. HiLog(日志系统)

import { hilog } from '@kit.PerformanceAnalysisKit';

const DOMAIN = 0x0000;

// 打印日志
hilog.info(DOMAIN, 'Reader', '页面加载完成');
hilog.error(DOMAIN, 'Reader', '加载失败: %{public}s', errMsg);

3. Profiler(性能分析器)

用于分析应用性能:

  • CPU profiling:分析函数调用耗时
  • Memory profiling:检测内存泄露
  • Frame profiling:分析帧率

9.2 单元测试

HarmonyOS 支持基于 TestNG 的单元测试框架:

// src/ohosTest/ets/test/LocalUnit.test.ets
import { describe, it, expect } from '@ohos/hypium';

describe('LantingxuReaderTest', () => {
  it('text_should_not_be_empty', 0, () => {
    const text = '永和九年';
    expect(text.length).assertGreater(0);
  });
});

测试代码放在 ohosTest 源码集下:

entry/src/ohosTest/ets/test/
├── Ability.test.ets    # Ability 生命周期测试
├── List.test.ets       # 测试集列表
└── LocalUnit.test.ets  # 本地单元测试

9.3 常见的编译错误与排查

错误 1:类型不匹配

error: Type 'string' is not assignable to type 'number'

解决方案:检查参数类型,ArkTS 对类型要求严格。

错误 2:找不到模块

error: Cannot find module '@kit.AbilityKit'

解决方案:检查 oh-package.json5 中是否声明了依赖,或者 SDK 版本是否正确。

错误 3:API 在目标 SDK 中不可用

error: Property 'xxx' does not exist on type 'TextAttribute'

解决方案:确认使用的 API 在目标 SDK(API 24)中是否可用,或检查 SDK 平台版本是否正确。


十、发布与上架

10.1 应用包生成

HarmonyOS 应用的发布格式为 .app 文件。生成流程:

  1. 配置 Release 签名:生成正式签名证书
  2. 构建 Release 包:Build → Build Hap(s)/App(s) → Build App(s)
  3. 产物路径entry/build/default/outputs/default/xxx-release-app.app

10.2 上架华为应用市场

发布到华为应用市场的基本流程:

  1. 注册开发者账号:在 AppGallery Connect 注册
  2. 创建应用:填写应用信息、上传图标截图
  3. 上传应用包:上传 .app 文件
  4. 填写隐私政策:说明数据收集和使用规则
  5. 审核与发布:等待审核通过

10.3 版本更新策略

应用版本号在 AppScope/app.json5 中配置:

{
  app: {
    bundleName: "com.example.myapplication",
    vendor: "example",
    version: {
      code: 1000000,    // 版本号(数字),用于系统判断版本新旧
      name: "1.0.0"     // 版本名称(字符串),展示给用户
    }
  }
}

版本号规范建议:

  • version.code 使用大数字:主版本(1) × 百万 + 次版本(0) × 千 + 补丁(0) = 1000000
  • version.name 使用语义化版本:主版本.次版本.补丁

十一、总结与扩展方向

11.1 本文总结

通过「兰亭序」阅读器的开发实践,我们系统地掌握了:

  1. HarmonyOS API 24 的项目结构与配置方式
  2. ArkTS 声明式 UI 的编程范式
  3. ArkUI 组件体系:Container、Text、Scroll、Line、Divider
  4. 资源管理系统:多语言、多模式、多设备的资源管理
  5. 构建与签名:从开发到发布的完整流程
  6. 性能优化基础:长文本渲染、状态管理最佳实践

11.2 扩展方向

「兰亭序」阅读器虽然功能简单,但可以在此基础上扩展出大量有价值的功能:

1. 字体切换功能

@State fontFamily: string = 'default';
// 支持切换为楷体、宋体等书法字体
Text('内容').fontFamily(this.fontFamily)

2. 阅读设置面板

  • 字号调节(滑动条 + 预览)
  • 背景主题(宣纸/羊皮纸/护眼绿/深夜黑)
  • 行距调节

3. 书签与进度保存

import { preferences } from '@kit.ArkData';
// 使用 Preferences 持久化阅读进度
let pref = await preferences.getPreferences(context, 'reader_pref');
await pref.put('scrollPosition', scrollPosition);

4. 白日依山尽自动滚动
使用 animateTo 实现自动滚屏阅读:

this.scroller.scrollTo({
  yOffset: targetPosition,
  duration: 3000  // 3秒内滚动到目标位置
})

5. 多语言支持
通过资源目录 resources/en_GB/ 添加英文版本简介:

// resources/en_GB/element/string.json
{
  "string": [
    {
      "name": "app_title",
      "value": "Preface to Orchid Pavilion"
    }
  ]
}

6. 注释与翻译
将兰亭序逐句拆分,配合注释弹窗,做成学习工具:

Text('永和九年')
  .onClick(() => {
    // 弹出注释弹窗
    this.showAnnotation('晋穆帝永和九年(公元353年)')
  })

11.3 最后的思考

从一篇千年古文到一个鸿蒙应用,这本身就是一个非常"中国"的故事。技术是载体,文化是灵魂。

我们在开发这个应用时所做的每一个技术选择——暖色背景模拟宣纸质感、行高字距模拟古籍排版、简洁界面避免干扰阅读体验——都是在用现代技术语言去诠释古典精神。

正如王羲之在《兰亭序》中所说:"后之览者,亦将有感于斯文。"一千六百多年后,我们用数字技术让更多人能够"览"到这篇美文,而技术的温度,就在于它能够成为文化传承的桥梁。

愿这段代码,也成为一座桥梁。


附录:参考资料


本文使用 DevEco Studio 5.x + HarmonyOS SDK API 24 完成开发与验证
博客代码示例均取自实际运行的项目工程

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。