《2026鸿蒙NEXT纯血开发与AI辅助》第六章:「微距」项目启动——工程创建与整体架构设计-卓伊凡
《2026鸿蒙NEXT纯血开发与AI辅助》第六章实战摘要: 本章正式启动"微距"AR测量应用开发,首先明确产品定位为基于HarmonyOS 6.0的智能测量工具,具备四大核心场景:家居测量、搬家估体积、快递测量和装修验房。通过EmptyAbility模板创建工程后,设计了五大功能模块(AR测量、3D空间、协同测量、工具集和数据管理)及对应的四Tab导航架构。重点实现了页面导航骨
《2026鸿蒙NEXT纯血开发与AI辅助》第六章:「微距」项目启动——工程创建与整体架构设计-卓伊凡
从这一章开始,我们的专栏正式进入实战阶段。
前五章我们做了很多基础工作:下载 DevEco Studio、理解项目模板、熟悉 IDE 界面、跑通第一个 Hello World。这些不是浪费时间,而是在打地基。地基打好了,接下来盖楼的每一层才会稳。
这一章,我们要做的事情很明确:把「微距」这个项目真正创建出来,把整体架构设计清楚,把后续开发的路线图确定下来。
一、「微距」到底是个什么样的产品
在动手写代码之前,我们先把产品定义清楚。
很多开发者一上来就急着写代码,结果写到一半发现方向偏了,再回头调整成本巨大。所以这一节的每一个字,都请你认真看。
1.1 产品一句话定义
「微距」是一款基于 HarmonyOS 6.0 AR Engine 和 3D 空间化能力的智能测量工具,让用户的手机变成随身携带的多功能测量仪。
1.2 核心使用场景
我帮你梳理了四个最典型的使用场景,这些场景不是凭空想出来的,而是真实生活中经常遇到的:
场景一:家居量尺寸
你准备买一张新沙发,但不确定客厅能不能放得下。传统做法是翻箱倒柜找卷尺,然后一个人拉着尺子,另一个人帮忙看数据,折腾半天。用「微距」,打开应用,对准墙角,用手指在屏幕上点两个点,长度直接显示在 AR 画面上。如果是一个人操作,就更有用了,因为你不需要找人帮忙拉尺子。
场景二:搬家估体积
要搬家了,搬家公司问你大概有多少立方米的东西,你一懵。用「微距」,扫描几个大件家具,应用自动计算体积,还能把多个物体的体积累加起来,给你一个合理估算。这个场景是真实刚需,被搬家公司的报价单逼出来过的人都懂。
场景三:快递测量
你在闲鱼上卖了个东西,需要填快递运费。运费是按体积重和实重取大值算的,你得知道包裹的长宽高。用「微距」扫一下,长宽高和体积一目了然。
场景四:装修验房
装修完验房,墙面是不是平的?用「微距」的水平检测功能扫一下,哪里不平一目了然。这是专业功能,但用手机就能做,门槛降低了一百倍。
1.3 目标用户画像
|
维度 |
描述 |
|
年龄段 |
20-45 岁 |
|
城市分布 |
一二线城市为主,有租房、装修、网购需求的用户 |
|
行为特征 |
经常网购家具、喜欢 DIY 家居改造、关注智能工具 |
|
痛点 |
需要测量但手边没卷尺、一个人无法完成测量、需要估算体积 |
1.4 与竞品的差异化
市面上已经有 AR 测量类的应用,比如 iOS 的 Measure 应用,也有第三方测量 App。但它们有几个共同的问题:
- 单设备操作:只能用一台手机,不能两台设备协同测量大空间
- 无材质识别:只能测尺寸,不能告诉你被测物体的材质
- 数据不上云也不做本地加密:要么数据全上云有隐私风险,要么只存本地换手机就丢
而「微距」基于鸿蒙的能力,有三个核心差异化优势:
- 多设备协同测量:两个人各拿一台鸿蒙设备站在不同位置,数据自动拼接,测量更大更准
- 端侧 AI 材质识别:摄像头扫一下,自动识别木材、金属、玻璃等材质,离线可用,隐私安全
- 数据安全存储:测量数据端侧加密存储,不上云,通过鸿蒙分布式能力在多设备间安全同步
这三个点,也是后续参加创新赛时的核心得分点。
二、创建「微距」工程
产品定义清楚了,接下来就是正式创建工程。
2.1 选择哪个模板
我们在第五章详细对比过三种原生方案。对于「微距」这个项目,我们选择 Empty Ability。
为什么不选 Native C++?因为 AR Engine 的 API 在 ArkTS 层已经有完整封装,我们不需要直接操作 C++ 层的 AR 接口。Empty Ability 够用,而且依赖关系最简洁,构建速度最快。
为什么不选 CloudDev Empty Ability?因为「微距」的核心功能都在端侧完成,不依赖云服务。数据存储也是本地为主,不需要云数据库。
所以,Empty Ability,干净利落。
2.2 工程配置
打开 DevEco Studio,点击 Create Project,选择 Empty Ability 模板,然后填写以下配置:
|
配置项 |
填写内容 |
说明 |
|
Project name |
MicroDistance |
项目名称,也就是「微距」的英文名 |
|
Bundle name |
com.zhuoyifan.microdistance |
应用包名,遵循反向域名规范 |
|
Save location |
D:\Projects\MicroDistance |
项目保存路径 |
|
Module name |
entry |
入口模块名,保持默认 |
|
Device type |
Phone、Tablet |
勾选手机和平板,因为「微距」需要在大屏上有更好的体验 |
|
Enable Native C++ |
不勾选 |
不涉及 C++ 开发 |
点击 Finish,等待工程初始化完成。
2.3 初始化验证
工程创建完成后,注意观察底部 Build 输出区。你应该看到的是 BUILD SUCCESSFUL,而不是第四章那种红色报错。
这里我强调一个习惯:每次创建新工程,第一件事永远是确认构建成功。 这是你的基线。如果构建失败,后面写的所有代码都是无效的。先排错,再开发。
三、项目的整体架构设计
工程跑通了,接下来是架构设计。
很多教程在这一步会直接开始写页面代码,但我们不这样做。因为「微距」不是一个演示 Demo,它是一个有完整功能模块的应用。如果不先把架构想清楚,写到后面一定会乱。
3.1 功能模块划分
我们把「微距」拆成五个核心功能模块:
┌─────────────────────────────────────────────┐
│ 「微距」应用 │
├───────────┬───────────┬───────────┬──────────┤
│ AR测量 │ 3D空间 │ 协同测量 │ 工具集 │
│ ┌─────┐ │ ┌─────┐ │ ┌─────┐ │ ┌─────┐ │
│ │ 长度 │ │ │空间建│ │ │设备发│ │ │水平仪│ │
│ │ 面积 │ │ │模 │ │ │现连接│ │ │高度计│ │
│ │ 体积 │ │ │3D标注│ │ │数据拼│ │ │材质识│ │
│ │ 角度 │ │ │ │ │ │接 │ │ │别 │ │
│ └─────┘ │ └─────┘ │ └─────┘ │ └─────┘ │
├───────────┴───────────┴───────────┴──────────┤
│ 数据管理层 │
│ 测量记录存储 / 历史管理 / 导出 │
└─────────────────────────────────────────────┘
这五个模块的职责分别是:
AR 测量:核心功能模块。包含长度测量、面积测量、体积测量、角度测量。用户通过摄像头对准真实世界,在屏幕上选点,实时计算并显示测量结果。
3D 空间:扫描真实空间,生成 3D 点云模型,可以在模型上标注尺寸。这个模块面向的是更专业的使用场景,比如装修前的空间规划。
协同测量:两台鸿蒙设备通过分布式能力连接,各自从不同角度测量,数据自动拼接。这个模块是「微距」区别于市面上所有其他测量 App 的核心竞争力。
工具集:包含水平仪、高度计、材质识别等辅助功能。这些是独立的小工具,和核心测量功能互补。
数据管理:所有测量记录的存储、历史查看、数据导出。测量完成不是结束,用户需要回看和分享。
3.2 页面导航结构
根据功能模块,我们设计应用的页面导航结构:
应用入口(Index.ets - 启动页)
│
▼
主页面(MainPage.ets - TabBar 导航)
│
├── Tab 1:测量页(MeasurePage.ets)
│ ├── 长度测量模式
│ ├── 面积测量模式
│ ├── 体积测量模式
│ └── 角度测量模式
│
├── Tab 2:空间页(SpacePage.ets)
│ ├── 3D 扫描入口
│ ├── 历史模型列表
│ └── 模型详情/标注
│
├── Tab 3:工具页(ToolsPage.ets)
│ ├── 水平仪
│ ├── 高度计
│ └── 材质识别
│
└── Tab 4:记录页(HistoryPage.ets)
├── 测量记录列表
├── 记录详情
└── 数据导出
这是一个典型的底部四 Tab 导航结构。用户进入主页面后,通过底部的 Tab 栏切换四个核心功能区。
3.3 数据模型设计
在写任何 UI 代码之前,我们先把数据模型定义清楚。这是很多新手容易跳过的步骤,但没有数据模型,后面状态管理和数据持久化会很混乱。
测量记录模型(MeasRecord)
// 测量记录
class MeasRecord {
id: string; // 唯一标识
type: MeasType; // 测量类型:长度/面积/体积/角度
value: number; // 测量结果数值
unit: string; // 单位:米/平方米/立方米/度
points: Point3D[]; // 测量选点坐标列表
material: string; // 被测物体材质(AI识别结果)
createTime: number; // 创建时间戳
location: Location; // 测量时的地理位置
imageUrl: string; // 测量截图
tags: string[]; // 用户自定义标签
}
// 测量类型枚举
enum MeasType {
LENGTH = 'length',
AREA = 'area',
VOLUME = 'volume',
ANGLE = 'angle'
}
// 3D 空间点
class Point3D {
x: number;
y: number;
z: number;
}
3D 空间模型(SpaceModel)
// 3D 空间扫描结果
class SpaceModel {
id: string;
name: string; // 空间名称
pointCloud: Point3D[]; // 点云数据
annotations: Annotation[]; // 空间标注列表
createTime: number;
deviceModel: string; // 扫描设备型号
}
// 空间标注
class Annotation {
id: string;
type: AnnotationType; // 标注类型
position: Point3D; // 标注位置
content: string; // 标注内容
measurement: MeasRecord; // 关联的测量数据
}
enum AnnotationType {
TEXT = 'text', // 文字标注
DIMENSION = 'dimension', // 尺寸标注
MATERIAL = 'material' // 材质标注
}
协同测量会话(CollabSession)
// 多设备协同测量会话
class CollabSession {
sessionId: string;
deviceList: DeviceInfo[]; // 参与设备列表
measurements: MeasRecord[]; // 会话中的测量数据
mergedData: MeasRecord[]; // 拼接后的数据
status: SessionStatus;
}
class DeviceInfo {
deviceId: string;
deviceName: string;
deviceType: string;
position: Point3D; // 设备相对位置
}
enum SessionStatus {
CONNECTING = 'connecting',
MEASURING = 'measuring',
MERGING = 'merging',
COMPLETED = 'completed'
}
3.4 技术选型与依赖
「微距」项目涉及的技术能力,以及对应使用的鸿蒙 SDK 和 API:
|
功能需求 |
使用的鸿蒙能力 |
API / SDK |
|
AR 测量核心 |
AR Engine |
|
|
3D 场景展示 |
ArkUI 3D 组件 |
|
|
摄像头调用 |
Camera Kit |
|
|
多设备协同 |
分布式软总线 |
|
|
材质识别 |
端侧 AI 视觉 |
|
|
传感器(水平仪) |
Sensor Kit |
|
|
地理位置 |
Location Kit |
|
|
数据持久化 |
关系型数据库 |
|
|
文件存储 |
文件管理 |
|
这个表很重要。它明确了我们后面每个功能模块要用到什么能力,开发的时候按图索骥就行。
四、本章的代码实现:搭建页面导航骨架
架构设计完成了,接下来我们做第一个实际开发任务:把应用的页面导航骨架搭起来。
这一节的目标是:应用启动后,出现一个底部四个 Tab 的主页面,每个 Tab 对应一个功能模块的占位页面。
4.1 创建页面文件
首先,在 entry/src/main/ets/pages/ 目录下创建以下文件:
pages/
├── Index.ets # 应用入口(自动生成,暂时保留不改)
├── MainPage.ets # 主页面(TabBar 容器)
├── MeasurePage.ets # 测量页
├── SpacePage.ets # 空间页
├── ToolsPage.ets # 工具页
└── HistoryPage.ets # 记录页
4.2 实现 MainPage(TabBar 主页面)
MainPage.ets 是整个应用的核心容器,承载底部导航和四个页面的切换。
import MeasurePage from './MeasurePage';
import SpacePage from './SpacePage';
import ToolsPage from './ToolsPage';
import HistoryPage from './HistoryPage';
@Entry
@Component
struct MainPage {
@State currentIndex: number = 0;
// Tab 数据配置
private tabItems: TabItem[] = [
{
title: '测量',
icon: $r('app.media.ic_measure'),
selectedIcon: $r('app.media.ic_measure_selected')
},
{
title: '空间',
icon: $r('app.media.ic_space'),
selectedIcon: $r('app.media.ic_space_selected')
},
{
title: '工具',
icon: $r('app.media.ic_tools'),
selectedIcon: $r('app.media.ic_tools_selected')
},
{
title: '记录',
icon: $r('app.media.ic_history'),
selectedIcon: $r('app.media.ic_history_selected')
}
];
// 构建 TabBar 内容
@Builder
TabContentBuilder(index: number) {
if (index === 0) {
MeasurePage()
} else if (index === 1) {
SpacePage()
} else if (index === 2) {
ToolsPage()
} else {
HistoryPage()
}
}
build() {
Tabs({
barPosition: BarPosition.End,
index: this.currentIndex
}) {
ForEach(this.tabItems, (item: TabItem, tabIndex: number) => {
TabContent() {
this.TabContentBuilder(tabIndex)
}
.tabBar(this.TabBarBuilder(tabIndex))
})
}
.onChange((index: number) => {
this.currentIndex = index;
})
.barMode(BarMode.Fixed)
.backgroundColor('#F5F5F5')
}
// Tab 栏样式构建器
@Builder
TabBarBuilder(index: number) {
Column() {
Image(this.currentIndex === index ?
this.tabItems[index].selectedIcon :
this.tabItems[index].icon)
.width(24)
.height(24)
.objectFit(ImageFit.Contain)
Text(this.tabItems[index].title)
.fontSize(10)
.fontColor(this.currentIndex === index ? '#007AFF' : '#999999')
.margin({ top: 2 })
}
.padding({ top: 8, bottom: 8 })
.width('100%')
.alignItems(HorizontalAlign.Center)
.onClick(() => {
this.currentIndex = index;
})
}
}
// Tab 项数据接口
interface TabItem {
title: string;
icon: Resource;
selectedIcon: Resource;
}
4.3 实现四个占位页面
四个功能页面目前先用最简单的占位代码,后续章节再逐一实现。
MeasurePage.ets(测量页)
@Component
export default struct MeasurePage {
build() {
Column() {
// 顶部标题栏
Row() {
Text('AR 测量')
.fontSize(20)
.fontWeight(FontWeight.Bold)
}
.width('100%')
.padding({ left: 16, top: 48, right: 16, bottom: 16 })
.backgroundColor('#FFFFFF')
// 测量模式选择区域(占位)
Column() {
Text('测量模块即将上线')
.fontSize(16)
.fontColor('#999999')
}
.width('100%')
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
.width('100%')
.height('100%')
}
}
SpacePage.ets(空间页)
@Component
export default struct SpacePage {
build() {
Column() {
Row() {
Text('3D 空间')
.fontSize(20)
.fontWeight(FontWeight.Bold)
}
.width('100%')
.padding({ left: 16, top: 48, right: 16, bottom: 16 })
.backgroundColor('#FFFFFF')
Column() {
Text('3D 空间模块即将上线')
.fontSize(16)
.fontColor('#999999')
}
.width('100%')
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
.width('100%')
.height('100%')
}
}
ToolsPage.ets(工具页)
@Component
export default struct ToolsPage {
build() {
Column() {
Row() {
Text('工具集')
.fontSize(20)
.fontWeight(FontWeight.Bold)
}
.width('100%')
.padding({ left: 16, top: 48, right: 16, bottom: 16 })
.backgroundColor('#FFFFFF')
Column() {
Text('工具模块即将上线')
.fontSize(16)
.fontColor('#999999')
}
.width('100%')
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
.width('100%')
.height('100%')
}
}
HistoryPage.ets(记录页)
@Component
export default struct HistoryPage {
build() {
Column() {
Row() {
Text('测量记录')
.fontSize(20)
.fontWeight(FontWeight.Bold)
}
.width('100%')
.padding({ left: 16, top: 48, right: 16, bottom: 16 })
.backgroundColor('#FFFFFF')
Column() {
Text('记录模块即将上线')
.fontSize(16)
.fontColor('#999999')
}
.width('100%')
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
.width('100%')
.height('100%')
}
}
4.4 修改应用入口
找到 entry/src/main/ets/entryability/EntryAbility.ets,确认 onWindowStageCreate 中加载的是 MainPage:
import { UIAbility } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
import { hilog } from '@kit.PerformanceAnalysisKit';
export default class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage: window.WindowStage): void {
// 应用启动时加载主页面
windowStage.loadContent('pages/MainPage', (err, data) => {
if (err.code) {
hilog.error(0x0000, 'microdistance', 'Failed to load content. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
hilog.info(0x0000, 'microdistance', 'Succeeded in loading content. Data: %{public}s', JSON.stringify(data) ?? '');
});
}
}
4.5 添加 Tab 图标资源
在 entry/src/main/resources/base/media/ 目录下,需要准备以下图标文件:
media/
├── ic_measure.png # 测量 Tab 默认图标
├── ic_measure_selected.png # 测量 Tab 选中图标
├── ic_space.png # 空间 Tab 默认图标
├── ic_space_selected.png # 空间 Tab 选中图标
├── ic_tools.png # 工具 Tab 默认图标
├── ic_tools_selected.png # 工具 Tab 选中图标
├── ic_history.png # 记录 Tab 默认图标
├── ic_history_selected.png # 记录 Tab 选中图标
在正式的开发中,你需要自己制作或从图标库中导出这些图标。作为教学演示,你可以先用简单的纯色方块替代,确保功能跑通后再替换为正式图标。
五、运行验证
代码写完了,我们验证一下。
启动本地真机模拟器,点击 Run,你应该看到:
- 应用启动后直接进入主页面
- 底部有四个 Tab:测量、空间、工具、记录
- 点击不同的 Tab,页面会切换,每个页面显示各自的标题和"即将上线"占位文字
- 选中的 Tab 图标和文字颜色会高亮
如果这四步都能正常运作,说明你的页面导航骨架已经搭建成功。
更多推荐



所有评论(0)