鸿蒙 ArkTS 实战:Calligraphy Grid 从状态建模到交互闭环完整解析

前言

Calligraphy Grid 是一个面向 学习成长工具 的鸿蒙 ArkTS 小应用。记录书法作品、当前练习字和拍照次数。 本文围绕项目中的 entry/src/main/ets/pages/Index.ets 展开,拆解它的 数据模型@State 状态业务方法Builder 组件 和页面布局方式,帮助读者理解一个轻量鸿蒙应用如何从静态数据走向可交互页面。

在这里插入图片描述

图示说明:页面以卡片、列表、输入框、按钮和状态统计为主体,适合在 DevEco Studio 中作为 ArkTS 组件化练习项目。

一、项目定位与功能闭环

1.1 应用要解决的问题

Calligraphy Grid 的业务入口非常轻:用户打开页面后,先看到已有数据和统计信息,再通过输入框或按钮触发状态变化。它不是重后台项目,而是典型的 单页状态驱动应用

维度项目表现技术落点
数据承载使用接口描述业务对象interface
页面状态使用 @State 保存列表、输入和开关ArkTS 状态管理
交互动作点击按钮后重建数组或修改字段不直接突变旧数组
视觉组织卡片、行组件、统计块Builder 复用
运行环境DevEco Studio + 鸿蒙 ArkTSStage 模型页面

1.2 本文阅读重点

  1. 先看数据模型,理解页面到底在管理什么。
  2. 再看状态变量,判断哪些字段会触发 UI 刷新。
  3. 接着看核心方法,梳理新增、切换、筛选、统计等动作。
  4. 最后看页面布局,把业务数据映射成用户能理解的界面。

核心观点:ArkTS 页面开发的关键不是把控件堆满,而是让数据结构、状态变化和 UI 呈现形成稳定闭环。

二、工程结构与入口文件

2.1 目录结构

本项目是标准鸿蒙工程,核心页面位于 entry/src/main/ets/pages/Index.ets。文章聚焦这个文件,因为它包含了主要模型、状态、方法和 UI。

Calligraphy Grid
├── AppScope
├── entry
│   └── src
│       └── main
│           ├── ets
│           │   └── pages
│           │       └── Index.ets
│           └── module.json5
├── hvigor
├── build-profile.json5
└── oh-package.json5

2.2 页面入口

ArkTS 页面通过 @Entry 和 @Component 标记入口组件,uild() 方法负责描述 UI 树。

@Entry
@Component
struct Index {
  build() {
    Scroll() {
      Column() {
        // 页面内容
      }
    }
  }
}

2.3 技术栈表

技术点用途在项目中的角色
ArkTS页面逻辑与类型声明主开发语言
ArkUI声明式组件构建界面
@State响应式状态驱动刷新
@Builder组件片段复用降低重复布局
Hvigor构建系统工程构建

三、数据模型设计

3.1 模型清单

源码中出现的业务模型为:$ifaceList。这些接口用于约束列表数据结构,让 UI 渲染时能明确字段来源。

interface WorkItem {
  id: number;
  title?: string;
  name?: string;
  done?: boolean;
  status?: string;
}

上面的代码展示了这类模型的常见形态:id 用于列表 key,名称字段用于展示,状态字段用于判断颜色、按钮文案和操作结果。

3.2 模型设计表

模型主要价值页面用途
$primaryInterface承载核心业务条目列表渲染、按钮操作
辅助模型承载记录、提示或分类详情展示、过滤统计
状态字段表示完成、提醒、选中等状态控制颜色和文案

3.3 为什么用 interface

ArkTS 中使用 interface 可以在编写对象数组时获得更强的结构约束,减少字段拼写错误。对于列表型应用,这一点尤其重要。

实践要点:如果页面中有多条同结构数据,应优先抽象成接口,再让 @State 数组保存该接口类型。

四、状态变量拆解

4.1 @State 清单

项目中的状态声明包括:$stateList。

@State items: WorkItem[] = [];
@State draftText: string = '';
@State selectedIndex: number = 0;
@State enabled: boolean = true;

实际源码会根据业务命名,例如列表、输入框、筛选条件、开关状态等。它们共同决定页面当前显示什么、按钮点击后更新什么。

4.2 状态分类

状态类型示例作用
列表状态$primaryState页面主体数据
输入状态draftName、keyword、nswer接收用户输入
选择状态selectedId、 ilter、cursor决定当前视图
开关状态
eminder、shared、
ecording控制功能启停
统计状态correct、wrong、seconds形成即时反馈

4.3 状态更新原则

ArkTS 页面中,列表更新常采用重建数组的方式,这样更容易触发界面刷新。

private updateItem(id: number): void {
  this.items = this.items.map((item: WorkItem) => {
    if (item.id !== id) {
      return item;
    }
    return {
      ...item,
      done: !item.done
    };
  });
}

五、核心方法解析

5.1 方法清单

源码中的核心方法包括:$methodList。这些方法构成了应用的业务动作层。

方法类型常见方法说明
统计方法otal、count、verage从列表中派生数字
新增方法ddItem、ddTask从输入状态创建新对象
切换方法oggle、 inish、
ext修改完成、提醒、进度
过滤方法isible、 iltered根据关键词或分类筛选
展示方法
ow、card、sectionHeader组织 UI 片段

5.2 统计方法

很多轻量应用都需要顶部统计卡片。统计方法通常通过 ilter、
educe 或数组长度计算。

private countActive(): number {
  return this.items.filter((item: WorkItem) => !item.done).length;
}

private totalValue(): number {
  return this.items.reduce((sum: number, item: WorkItem) => sum + 1, 0);
}

5.3 新增方法

新增逻辑的关键是从输入框状态读取文本,做空值判断,再把新对象插入列表。

private addItem(): void {
  const title = this.draftText.trim();
  if (title.length === 0) {
    return;
  }
  const id = this.items.reduce((maxId: number, item: WorkItem) => Math.max(maxId, item.id), 0) + 1;
  const item: WorkItem = { id: id, title: title, done: false };
  this.items = [item, ...this.items];
  this.draftText = '';
}

5.4 切换方法

完成、提醒、收藏、订阅这类动作都可以通过 map 生成新数组。

private toggleItem(id: number): void {
  this.items = this.items.map((item: WorkItem) => {
    return item.id === id ? { ...item, done: !item.done } : item;
  });
}

六、Builder 组件复用

6.1 Builder 清单

项目中的 Builder 组件包括:$builderList。

@Builder 适合承载重复出现的 UI 片段,比如统计卡、列表行、筛选按钮、章节标题等。

@Builder
private row(item: WorkItem) {
  Row() {
    Text(item.title ?? item.name ?? '未命名')
      .fontSize(16)
      .fontWeight(FontWeight.Medium)
  }
  .padding(12)
  .backgroundColor('#FFFFFF')
  .borderRadius(8)
}

6.2 复用价值

Builder复用价值适用位置
统计卡片避免重复写数字和标签布局顶部概览
列表行统一条目样式主列表
筛选按钮保持选中态一致分类切换
区块标题统一标题和辅助信息页面分组

6.3 组件边界

Builder 不需要承担复杂业务判断,它更适合做“给定数据,渲染界面”。业务变化仍然放在 private 方法中。

七、页面布局结构

7.1 Scroll + Column

这批项目大多采用 Scroll 包裹 Column 的布局方式,适合移动端纵向内容流。

Scroll() {
  Column() {
    // 头部区域
    // 输入区域
    // 列表区域
    // 详情区域
  }
  .width('100%')
  .padding(16)
}
.height('100%')
.width('100%')

7.2 页面分区

分区作用常用组件
头部展示标题、说明、统计Text、Row、Column
输入区接收新增数据TextInput、Button
操作区筛选、切换、开关Button、Toggle
列表区展示业务条目ForEach、Builder 行
结果区展示统计或详情Card 风格容器

7.3 ForEach 渲染

列表通常通过 ForEach 渲染,并使用 id 作为稳定 key。

ForEach(this.items, (item: WorkItem) => {
  this.row(item)
}, (item: WorkItem) => item.id.toString())

稳定 key 能减少列表刷新时的错位,也更利于后续扩展动画或局部更新。

八、交互闭环设计

8.1 从输入到列表

新增动作通常经过以下路径:

  1. TextInput 的 onChange 更新草稿状态。
  2. Button 的 onClick 调用新增方法。
  3. 新增方法创建业务对象。
  4. 列表状态重建,页面自动刷新。
TextInput({ text: this.draftText })
  .onChange((value: string) => {
    this.draftText = value;
  })

Button('添加')
  .onClick(() => {
    this.addItem();
  })

8.2 从按钮到状态

切换类按钮不需要复杂表单,只要传入条目 id 即可。

Button('完成')
  .onClick(() => {
    this.toggleItem(item.id);
  })

8.3 状态反馈

按钮文案、颜色、标签和统计数字都来自状态。这样用户的每次点击都能马上看到反馈。

关键点:页面反馈越直接,工具应用越容易形成“点一下就知道结果”的操作感。

九、视觉层与信息层

9.1 色彩表达

项目常用白色卡片承载内容,用主题色突出主按钮,再用绿色、橙色、红色表达安全、待处理、异常等状态。

状态色彩倾向用户理解
正常绿色已完成、可用、安全
待处理橙色需要关注
异常红色需要立即处理
中性灰色普通说明

9.2 文本层级

标题使用较大字号,列表项名称使用中等字号,说明文本使用小字号。这样的层级适合小屏阅读。

Text('页面标题')
  .fontSize(30)
  .fontWeight(FontWeight.Bold)

Text('辅助说明')
  .fontSize(14)
  .fontColor('#667085')

十、鸿蒙 ArkTS 开发要点

10.1 类型先行

先定义接口,再写状态数组,可以让页面开发更稳。

10.2 状态集中

当前项目把状态集中在 Index 组件内,适合轻量单页应用。随着功能增加,可以再拆分组件或引入持久化。

10.3 方法命名

源码中的 $primaryMethod 等方法名直观表达动作,有利于阅读和维护。

10.4 UI 与逻辑分离

Builder 负责渲染,private 方法负责业务动作,这种分工能让页面不至于变成一整块难读代码。

十一、运行与调试流程

11.1 使用 DevEco Studio 打开

# 使用 DevEco Studio 打开项目目录
# 同步 oh-package 依赖
# 选择模拟器或真机运行 entry 模块

11.2 命令行构建思路

hvigorw clean
hvigorw assembleHap

11.3 调试观察点

观察点关注内容
页面是否进入@Entry 是否正常加载
列表是否刷新@State 数组是否重建
输入是否生效onChange 是否写回状态
按钮是否响应onClick 是否调用方法
样式是否稳定卡片宽度、颜色、字号是否一致

十二、可维护性拆解

12.1 文件规模

当前 Index.ets 约 52 行,属于可阅读的单页规模。随着功能继续增加,可以把列表行、统计卡和输入区拆到独立组件。

12.2 数据扩展

如果要接入本地存储,可以先为 $primaryInterface 增加序列化字段,再在页面加载时恢复列表。

interface StoredRecord {
  id: number;
  payload: string;
  updatedAt: number;
}

12.3 异常边界

输入为空时直接返回,是轻量应用常见处理方式。更复杂的产品可以加入 Toast 或错误提示。

十三、同类项目迁移思路

13.1 可复用结构

Calligraphy Grid 的结构可以迁移到很多轻量工具:

  • 一组业务对象。
  • 一组页面状态。
  • 若干统计方法。
  • 一个新增入口。
  • 一个列表展示区。

13.2 抽象模板

interface ItemModel {
  id: number;
  title: string;
  done: boolean;
}

@State items: ItemModel[] = [];

private addItem(title: string): void {
  this.items = [{ id: Date.now(), title: title, done: false }, ...this.items];
}

13.3 适配范围

应用类型可复用部分
清单工具列表、完成状态、统计
记录工具输入、历史、筛选
学习工具进度、得分、卡片
家庭工具提醒、状态、分组

十四、项目亮点总结

14.1 业务表达清晰

Calligraphy Grid 没有把逻辑藏在复杂框架里,而是直接用接口、状态和方法表达业务。

14.2 页面反馈及时

每个操作都会落到 @State,列表、统计和按钮状态随之变化。

14.3 结构适合继续扩展

当前结构可以自然扩展持久化、搜索、排序、通知和多页面跳转。

十五、总结

Calligraphy Grid 展示了鸿蒙 ArkTS 单页应用的典型写法:通过 $ifaceList 建模业务数据,通过 $stateList 保存页面状态,通过 $methodList 完成统计、筛选、新增和切换,再通过 $builderList 把重复 UI 片段组件化。它的价值不只是一个具体工具,而是一套可以迁移到其他轻量应用的工程组织方式。

对于正在学习鸿蒙 ArkTS 的开发者来说,这个项目适合重点观察三件事:数据模型如何约束页面、状态变化如何驱动 UI、Builder 如何让列表和卡片保持一致。掌握这三点后,就能快速搭建更多家庭、学习、办公类小工具。

如果这篇文章对你有帮助,欢迎点赞、收藏、关注,你的支持是我持续创作的动力!


相关资源:

Logo

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

更多推荐