《2026鸿蒙NEXT纯血开发与AI辅助》第六章:「微距」项目启动——工程创建与整体架构设计-卓伊凡


《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。但它们有几个共同的问题:

  1. 单设备操作:只能用一台手机,不能两台设备协同测量大空间
  2. 无材质识别:只能测尺寸,不能告诉你被测物体的材质
  3. 数据不上云也不做本地加密:要么数据全上云有隐私风险,要么只存本地换手机就丢

而「微距」基于鸿蒙的能力,有三个核心差异化优势:

  1. 多设备协同测量:两个人各拿一台鸿蒙设备站在不同位置,数据自动拼接,测量更大更准
  2. 端侧 AI 材质识别:摄像头扫一下,自动识别木材、金属、玻璃等材质,离线可用,隐私安全
  3. 数据安全存储:测量数据端侧加密存储,不上云,通过鸿蒙分布式能力在多设备间安全同步

这三个点,也是后续参加创新赛时的核心得分点。

二、创建「微距」工程

产品定义清楚了,接下来就是正式创建工程。

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 | @ohos/ar |
| 3D 场景展示 | ArkUI 3D 组件 | @ohos/ui |
| 摄像头调用 | Camera Kit | @ohos/camera |
| 多设备协同 | 分布式软总线 | @ohos/distributed |
| 材质识别 | 端侧 AI 视觉 | @ohos/ai |
| 传感器(水平仪) | Sensor Kit | @ohos/sensor |
| 地理位置 | Location Kit | @ohos/geoLocationManager |
| 数据持久化 | 关系型数据库 | @ohos/data.relationalStore |
| 文件存储 | 文件管理 | @ohos/file |

这个表很重要。它明确了我们后面每个功能模块要用到什么能力,开发的时候按图索骥就行。

四、本章的代码实现:搭建页面导航骨架

架构设计完成了,接下来我们做第一个实际开发任务:把应用的页面导航骨架搭起来。

这一节的目标是:应用启动后,出现一个底部四个 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,你应该看到:

  1. 应用启动后直接进入主页面
  2. 底部有四个 Tab:测量、空间、工具、记录
  3. 点击不同的 Tab,页面会切换,每个页面显示各自的标题和"即将上线"占位文字
  4. 选中的 Tab 图标和文字颜色会高亮

如果这四步都能正常运作,说明你的页面导航骨架已经搭建成功。

相关推荐
sell-cloud2 小时前
Prompt Engineering要凉了?Fable 5.5能“猜“意图,但银行正在封杀AI Agent——企业AI的真正护城河是什么
人工智能·prompt
Navicat中国3 小时前
Navicat 18 内置 AI 助手让数据库开发从繁琐操作迈向智能协作
大数据·人工智能·数据库开发
4SAPI4 小时前
多模型请求路由怎么设计?成本、速度和能力如何平衡
人工智能·python·gateway·php
魔乐社区4 小时前
翼芯・智算测试与适配优化专区,让国产算力“选得准、跑得通、用得好“
人工智能·开源·大模型
XCDoris6 小时前
郴州logo设计机构
大数据·人工智能·python
Goboy6 小时前
一个人带三个Agent,我用全新TRAE从0到1搭了个资讯周刊站
人工智能·trae
俊哥V6 小时前
每日 AI 研究简报 · 2026-10-19
人工智能·ai
AI_AGENT_DEV_AI6 小时前
AI英语教培智能体的开发
人工智能
物联网软硬件开发-轨物科技6 小时前
【技术白皮书】光伏清检一体化机器人系统解决方案技术白皮书
大数据·运维·网络·人工智能·机器人
海宇数据6 小时前
零信任架构实战:基于海宇网上踪迹欺诈行为识别构建自动化信贷资信评估网关
运维·人工智能·架构·自动化