一、技术前言

在移动影像创作领域,摄影早已不止于按下快门那一刻。从前期取景跟拍、手动对焦精修,到中期素材下载归档,再到后期 WebP 元数据读写校验与暗房纹理生成,一条完整的影像采编链路需要横跨相机硬件、图像编解码、浏览器内核三大底层能力域。传统摄影类应用往往只覆盖其中一环——或专注相机预览、或专注图片管理、或专注后期调色——难以形成"采、编、存、校"一体化的创作闭环。更深层的挑战在于:影随人动跟拍时构图主体容易跑偏、WebP 动图元数据读写缺乏回读校验闭环、下载素材来源难以溯源防伪。

HarmonyOS ArkUI 框架为这类多能力域融合场景提供了系统级解决方案。ArkUI 的声明式 UI 范式通过 @Component 封装可复用组件、@State 管理响应式状态、@Builder 拆分复杂 UI 结构,天然适合"采集-编校-管理"七 Tab 分层架构。@Observed 装饰器让数据模型字段级变化被 UI 感知,实现"对焦记录即时刷新""元数据日志实时滚动"的流畅体验。@Entry 标记入口组件,ForEach 驱动列表渲染,Flex 实现双列换行布局,Stack 层叠弹窗与主界面——这些原生组件为复杂交互提供了坚实基座。

本平台深度融合 HarmonyOS 6.1.1 的三大前沿特性。Camera Kit 提供 VideoSession 的 AUTO_FRAMING(影随人动)能力链——通过 isControlCenterSupportedgetSupportedEffectTypesenableControlCenter 三步实现跟拍时人物主体始终居中;同时 PhotoSession 的手动对焦三接口 isFocusDistanceSupportedsetFocusDistancegetFocusDistance 实现从微距静物到远距街景的精确对焦控制,设置值与读回值差值小于 0.01 判定生效。ImageKit 的 WebP 元数据读写通过 readImageMetadataByType 类型化读取五字段(canvasWidth/canvasHeight/delayTime/unclampedDelayTime/loopCount),再以 writeImageMetadata 字面量构造写回,写入后重建 ImageSource 回读校验形成闭环。ArkWebonDownloadFinish 完成回调中调用 6.1.1 新增的 getOriginalUrlgetReferrerUrl 双接口,实现下载文件原始直链与引用页面的双 URL 溯源。

二、整体架构流程图

弹窗系统

三大特性引擎

内容区七Tab

主组件层

影像采编主组件

headerMain 头部暗房渐变横幅

内容区七Tab切换

tabBar 底部单排导航

弹窗系统新增编辑删除

Tab0 作品
统计三卡+双列作品卡+月度柱状图

Tab1 相机
授权卡+XComponent预览+影随人动

Tab2 对焦
能力查询+三档预设+滑杆+读回校验

Tab3 网页
地址栏+Web组件+下载双URL溯源

Tab4 工坊
暗房纹理五选一+WebP生成器

Tab5 元数据
五字段卡+写入控制台+回读校验

Tab6 我的
摄影师渐变大卡+器材清单

Camera Kit
AUTO_FRAMING影随人动

Camera Kit
手动对焦三接口

ArkWeb
onDownloadFinish双URL溯源

ImageKit
像素画编码WebP落盘

ImageKit
元数据读写回读校验

panelAdd 新增摄影作品

panelEdit 编辑收藏数

panelDel 删除确认

架构以主组件为根,使用 Stack 容器层叠:底层 Column 纵向排列头部暗房渐变横幅、分割线、内容区和底部 Tab 栏,顶层是三个独立弹窗(新增/编辑/删除各自条件渲染)。内容区通过 currentTab 状态在七个 Builder 方法间切换,其中相机 Tab 和网页 Tab 因需独占有界高度(XComponent 与 Web 组件需 layoutWeight(1) 撑满剩余空间),不走 Scroll 滚动容器,其余五个 Tab 进入主滚动容器。三大特性分散在相机(影随人动)、对焦(手动对焦)、网页(双 URL 溯源)、工坊(WebP 生成)、元数据(读写校验)五个 Tab 上,状态变量统一声明在组件顶层实现跨 Tab 共享。

三、色彩体系设计

3.1 ColorPalette 接口定义

interface ColorPalette {
  bg: string;      // 页面背景(暗房黑)
  card: string;    // 卡片底色(深灰绿)
  title: string;   // 主标题(冷白)
  sub: string;     // 副标题(灰绿)
  text3: string;   // 三级弱文本(暗灰绿)
  cyan: string;    // 银盐青(主色)
  cyanD: string;   // 银盐青深色
  amber: string;   // 暗房琥珀(辅助暖色)
  blue: string;    // 信息蓝(原始 URL)
  red: string;     // 警示红(失败 / 删除)
  line: string;    // 分割线
  tabOn: string;   // Tab 选中色
  mask: string;    // 弹窗遮罩
}

3.2 COLORS 常量逐色分析

const COLORS: ColorPalette = {
  bg: '#101314',      // 暗房黑,沉浸式深色环境
  card: '#1A1F21',    // 卡片底色,比背景亮一档灰绿
  dark: '#232A2D',    // 次级容器底色(统计格 / 标签底)
  title: '#EAF2F2',   // 冷白标题,暗光高对比
  sub: '#A8BCBC',     // 灰绿副标题,层次柔和
  text3: '#6E8484',   // 暗灰绿弱文本,辅助信息不抢视觉
  cyan: '#3FBFB0',    // 银盐青主色,渐变横幅与按钮主色
  cyanD: '#2A9688',   // 银盐青深色,渐变起点与终点
  amber: '#E8A54B',   // 暗房琥珀强调色,收藏数与对焦预设
  blue: '#4E9BE3',    // 信息蓝,原始 URL 与本月出片
  red: '#E05E5E',     // 警示红,失败状态与删除操作
  line: '#28302F',    // 灰黑分割线,低对比不干扰
  tabOn: '#3FBFB0',   // Tab 选中色为银盐青
  mask: 'rgba(0,0,0,0.6)' // 半透黑遮罩
};

色彩体系以"暗房黑 + 银盐青 + 暗房琥珀"为核心三色组,致敬传统暗房冲洗工艺的视觉语言。暗房黑营造沉浸式深色环境,模拟暗房工作时的低光氛围;银盐青作为主色呼应银盐胶片的金属质感,用于渐变横幅、主按钮和 Tab 选中态;暗房琥珀作为辅助暖色模拟琥珀色安全灯,用于收藏数、对焦预设和循环次数等强调元素。值得注意的是 Tab 选中色使用 cyan(银盐青)而非 amber(暗房琥珀),这是因为青色在暗黑背景上的对比度更高,用户视觉定位更迅速。头部横幅使用 linearGradientcyanDcyan 的 120° 渐变,模拟暗房显影液由深到浅的层次过渡。

四、Tab 元数据与常量定义

4.1 底部导航七 Tab 定义

const TAB_LIST: TabMeta[] = [
  { icon: '🖼️', label: '作品' },
  { icon: '📷', label: '相机' },
  { icon: '🎯', label: '对焦' },
  { icon: '🌐', label: '网页' },
  { icon: '🧪', label: '工坊' },
  { icon: '🧬', label: '元数据' },
  { icon: '👤', label: '我的' }
];

底部单排七 Tab 覆盖影像采编全流程:作品(素材管理)→ 相机(影随人动采集)→ 对焦(手动精修)→ 网页(素材下载)→ 工坊(WebP 生成)→ 元数据(读写校验)→ 我的(个人中心)。每个 Tab 的布局完全不同,从双列卡片到 XComponent 预览,从滑杆控制台到 Web 组件,从像素画生成到五字段快照,充分展现 ArkUI 多布局能力的驾驭。

4.2 Camera Kit 效果类型与对焦预设

const EFFECT_INFOS: EffectInfo[] = [
  { type: 0, name: 'BEAUTY', desc: '美颜 · since 20' },
  { type: 1, name: 'PORTRAIT', desc: '人像 · since 20' },
  { type: 2, name: 'AUTO_FRAMING', desc: '影随人动 · 6.1.1 新增' }
];

const FOCUS_PRESETS: FocusPreset[] = [
  { label: '微距', distance: 0.1, scene: '0.1 · 静物微距' },
  { label: '近距', distance: 0.5, scene: '0.5 · 人像特写' },
  { label: '远距', distance: 0.9, scene: '0.9 · 街景纵深' }
];

效果类型枚举表展示 ControlCenterEffectType 的三个取值,其中 AUTO_FRAMING(值为 2)是 6.1.1 新增的影随人动能力。对焦预设三档覆盖 0.0(最近)到 1.0(最远)的值域,分别对应静物微距、人像特写和街景纵深三种典型拍摄场景。

4.3 暗房纹理与快捷站点

const TEXTURES: TextureInfo[] = [
  { key: 'grain', name: '颗粒', algo: '对角斜纹 (row+col)%N' },
  { key: 'vignette', name: '渐晕', algo: '同心环 dist(r,c)/7' },
  { key: 'scratch', name: '划痕', algo: '竖带 ⌊col/7⌋%N' },
  { key: 'flare', name: '光斑', algo: '棋盘 (⌊row/8⌋+⌊col/8⌋)%N' },
  { key: 'sharpen', name: '锐化', algo: '横带 ⌊row/6⌋%N' }
];

const QUICK_SITES: QuickSite[] = [
  { icon: '🖼️', name: '图虫', url: 'https://tuchong.com' },
  { icon: '📷', name: '500px', url: 'https://500px.com' },
  { icon: '🎨', name: 'Pexels', url: 'https://www.pexels.com' },
  { icon: '🌌', name: 'Unsplash', url: 'https://unsplash.com' },
  { icon: '🐦', name: 'Flickr', url: 'https://www.flickr.com' },
  { icon: '🦅', name: '蜂鸟网', url: 'https://www.fengniao.com' }
];

暗房纹理五选一对应五种像素算法,每种算法以不同的数学规律将坐标映射到颜色调色板,生成具有暗房工艺质感的像素画,为 WebP 编码提供素材源。快捷站点收录六个摄影社区与图库真实站点,点击即加载至 Web 组件,方便用户浏览素材并触发下载。

五、工具函数

5.1 颜色转换与字段格式化

/** '#RRGGBB' → 0xFFBBGGRR(RGBA_8888 缓冲按 R,G,B,A 字节序排列,A 固定 255) */
function hexToRgba(hex: string): number {
  const r = parseInt(hex.slice(1, 3), 16);
  const g = parseInt(hex.slice(3, 5), 16);
  const b = parseInt(hex.slice(5, 7), 16);
  return 0xFF000000 | (b << 16) | (g << 8) | r;
}

/** 元数据字段格式化:-1(undefined 占位)→ '未提供' */
function fmtField(v: number, unit: string): string {
  return v < 0 ? '未提供' : `${v}${unit}`;
}

hexToRgba 是整个暗房纹理生成的底层基石。ArkUI 的 image.createPixelMap 接受 RGBA_8888 格式的 ArrayBuffer,其字节序为 R、G、B、A 四字节一组,而 JavaScript 位运算以小端序处理 32 位整数,因此需要将颜色分量按 B、G、R 的顺序移位拼接,最高字节固定为 0xFF(不透明)。fmtField 则是元数据展示的统一兜底,undefined 字段在 WebpMetaSnapshot 中以 -1 占位,渲染时转为"未提供"文案,与 loopCount 的合法值 0(不限次数)严格区分。

5.2 状态颜色映射函数群

/** 影随人动状态 → 颜色(已启用青 / 能力缺失琥珀 / 失败红) */
function framingStateColor(s: string): string { ... }

/** 对焦距离值 → 景别文案(微距静物 / 近距人像 / 远距街景) */
function distanceLabel(v: number): string { ... }

/** 下载状态 → 颜色(完成青 / 失败红 / 进行蓝) */
function dlStateColor(s: string): string { ... }

/** 会话模式 → 中文说明(idle 未启动 / video 录像 / photo 拍照) */
function sessionLabel(mode: string): string { ... }

/** 对焦校验结果 → 颜色(已生效青 / 偏差琥珀 / 失败红) */
function focusOkColor(ok: string): string { ... }

这组纯函数将业务状态字符串映射为颜色值或中文文案,实现视图与逻辑的解耦。framingStateColor 按"已启用→青、能力缺失→琥珀、失败→红"三档分配颜色;distanceLabel 以 0.3 和 0.7 为分界点将连续的对焦距离值离散化为三档景别文案;sessionLabelidle/video/photo 三种会话模式转为带技术注解的中文说明。这些函数不持有状态、不产生副作用,可独立测试与复用。

六、数据模型层

6.1 作品条目 WorkItem

@Observed export class WorkItem {
  title: string;     // 作品主题名
  camera: string;    // 拍摄器材
  param: string;     // 曝光参数(焦段/光圈/快门/感光度)
  likes: number;     // 收藏数
  constructor(title: string, camera: string, param: string, likes: number) { ... }
}

@Observed 装饰器使 WorkItem 的字段级变化被 UI 感知。当用户在编辑弹窗中调整收藏数并保存时,this.workList[this.editIdx].likes = this.editLikes 的赋值会触发绑定该条目的 ForEach 项重新渲染,无需手动刷新整个列表。初始数据 WORK_LIST 包含八幅纪实摄影作品,涵盖外滩雾景、弄堂晨光、夜轨流光、雪山星野等题材,器材横跨 Sony、Fuji、Nikon、Leica、Canon 五大品牌。

6.2 对焦记录 FocusRecord

@Observed export class FocusRecord {
  time: string;      // 操作时刻(HH:mm:ss)
  distance: number;  // 设置的对焦距离 [0.0,1.0]
  readback: number;  // 读回的对焦距离(失败时为 -1)
  ok: string;        // 已生效 / 读回偏差 / 失败(错误码)
  constructor(distance: number, readback: number, ok: string) { ... }
}

FocusRecord 记录每次手动对焦操作的完整三元组:设置值、读回值、校验结论。构造函数中自动生成 HH:mm:ss 格式的时间戳,通过 String(d.getHours()).padStart(2, '0') 补零保证两位宽度。记录通过 unshift 置顶插入列表,超过 20 条时 pop 移除最旧条目,形成滑动窗口式的时间线。

6.3 下载记录 DownloadRecord

@Observed export class DownloadRecord {
  fileName: string;     // 建议文件名
  fileSize: string;     // 文件大小
  finishTime: string;   // 完成时间
  originalUrl: string;  // ★ getOriginalUrl() 结果:下载项原始 URL
  referrerUrl: string;  // ★ getReferrerUrl() 结果:引用页 URL
  constructor(...) { ... }
}

DownloadRecord 是 ArkWeb 双 URL 溯源特性的数据载体。originalUrl 存储文件直链来源(如 CDN 地址),referrerUrl 存储触发下载的页面地址(如摄影社区帖子页)。这两条 URL 在素材溯源与防伪场景中互补:原始 URL 确认文件的真实下载地址,引用页 URL 追溯素材的发现上下文。初始 DOWNLOAD_LIST 包含六条种子记录,覆盖 LUT 包、预设文件、动作脚本、PDF 指南、DNG 原片等多种摄影素材类型。

6.4 WebP 元数据快照与操作日志

@Observed export class WebpMetaSnapshot {
  canvasWidth: number;         // 画布宽(px),-1=未提供
  canvasHeight: number;        // 画布高(px),-1=未提供
  delayTime: number;           // 钳制后帧延迟(ms),-1=未提供
  unclampedDelayTime: number;  // 未钳制帧延迟(ms),-1=未提供
  loopCount: number;           // 循环次数,-1=未提供(0=不限)
  constructor(w, h, d, u, l) { ... }
}

@Observed export class MetaOpLog {
  op: string;      // 操作类型
  detail: string;  // 结果描述(含错误码)
  time: string;    // 操作时刻
  constructor(op: string, detail: string) { ... }
}

WebpMetaSnapshot 是 WebP 元数据五字段的不可变快照,用于读取结果与回读校验的对比展示。-1 作为 undefined 的占位值贯穿整个元数据子系统。MetaOpLog 记录生成、读取、写入、回读四类操作的详细结果,通过 unshift 置顶形成倒序日志流,错误码直接嵌入 detail 字段(如 7700202=不支持7700204=参数非法)方便排查。

七、组件主体

7.1 状态变量全景

主组件 Page1250@Entry @Component 标记为入口,声明了五大类状态变量:

@Entry
@Component
struct Page1250 {
  // --- Tab 状态 ---
  @State currentTab: number = 0;
  // --- 弹窗状态 ---
  @State addModal: boolean = false;
  @State editModal: boolean = false;
  @State delModal: boolean = false;
  @State editIdx: number = -1;
  @State delIdx: number = -1;
  // --- 动画状态 ---
  @State breath: boolean = false;
  timer: number = -1;
  // --- 作品数据 + 表单 ---
  @State workList: WorkItem[] = WORK_LIST;
  // --- Camera 成员 ---
  private previewController: XComponentController = new XComponentController();
  private videoSession?: camera.VideoSession;
  private photoSession?: camera.PhotoSession;
  @State sessionMode: string = 'idle';
  @State framingState: string = '未查询';
  @State focusRecords: FocusRecord[] = [];
  // --- WebP 成员 ---
  @State texIdx: number = 0;
  @State pixelMap?: image.PixelMap = undefined;
  @State webpPath: string = '';
  @State opLogs: MetaOpLog[] = [];
  // --- ArkWeb 成员 ---
  private webController: webview.WebviewController = new webview.WebviewController();
  private downloadDelegate: webview.WebDownloadDelegate = new webview.WebDownloadDelegate();
  @State downloadRecords: DownloadRecord[] = DOWNLOAD_LIST;
}

Tab 状态仅一个 currentTab 整数驱动七个 Builder 切换;弹窗状态以三个布尔独立控制三个弹窗的条件渲染,editIdxdelIdx 记录操作目标索引;动画状态 breathsetInterval 每秒翻转一次,驱动呼吸圆点与柱状图微动;Camera 成员区分 private(控制器与会话对象,非响应式)与 @State(UI 需感知的会话模式、能力状态、对焦记录);WebP 成员涵盖纹理索引、像素图、沙箱路径、操作日志等完整工坊状态;ArkWeb 成员以 private 持有控制器与下载代理,@State 持有地址栏输入、下载进度和记录列表。

7.2 生命周期与根构建

aboutToAppear() {
  this.setupDownloadDelegate();
  this.focusRecords.unshift(new FocusRecord(0.9, 0.9, '已生效'));
  this.focusRecords.unshift(new FocusRecord(0.5, 0.5, '已生效'));
  this.focusRecords.unshift(new FocusRecord(0.1, 0.12, '读回偏差'));
  this.opLogs.unshift(new MetaOpLog('初始化', 'WebP 元数据工坊就绪'));
  this.timer = setInterval(() => { this.breath = !this.breath; }, 1000);
}

aboutToDisappear() {
  clearInterval(this.timer);
  this.releaseSession();
}

aboutToAppear 完成四件初始化:绑定下载代理(注册四回调)、种入三条对焦记录(含一条"读回偏差"示例)、种入初始操作日志、启动呼吸动画定时器。aboutToDisappear 负责清理定时器与释放相机资源,防止后台占用摄像头。switchTab 方法在离开相机 Tab 时主动调用 releaseSession,因为 cameraInput 同一时间只能绑定一个会话,不及时释放会导致后续模式切换失败。

7.3 根构建结构

build() {
  Stack({ alignContent: Alignment.Center }) {
    Column() {
      this.headerMain()
      Divider().strokeWidth(1).color(COLORS.line)
      if (this.currentTab === 1) {
        this.tabCamera()
      } else if (this.currentTab === 3) {
        this.tabWeb()
      } else {
        Scroll() {
          Column({ space: 12 }) {
            if (this.currentTab === 0) { this.tabWorks() }
            else if (this.currentTab === 2) { this.tabFocus() }
            else if (this.currentTab === 4) { this.tabStudio() }
            else if (this.currentTab === 5) { this.tabMeta() }
            else { this.tabMine() }
          }
        }.layoutWeight(1).scrollBar(BarState.Off).edgeEffect(EdgeEffect.Spring)
      }
      this.tabBar()
    }
    if (this.addModal) { this.panelAdd(() => { this.addModal = false; }) }
    if (this.editModal) { this.panelEdit(() => { this.editModal = false; }) }
    if (this.delModal) { this.panelDel(() => { this.delModal = false; }) }
  }
}

根构建以 Stack 层叠主界面列与弹窗层。主界面列从上到下依次为头部、分割线、内容区、底部 Tab 栏。内容区的分支逻辑值得注意:相机 Tab(索引 1)和网页 Tab(索引 3)因需独占有界高度,直接渲染不走 Scroll;其余五个 Tab 进入 Scroll 滚动容器,配以 EdgeEffect.Spring 弹性边缘效果。弹窗层三个弹窗各自条件渲染,互不干扰,点击遮罩关闭。

八、头部详解

@Builder
headerMain() {
  Column({ space: 12 }) {
    Column({ space: 8 }) {
      Row() {
        Text('📸 影像采编').fontSize(18).fontColor(COLORS.bg).fontWeight(FontWeight.Bold)
        Blank()
        Row({ space: 5 }) {
          Circle({ width: 6, height: 6 }).fill(this.breath ? COLORS.bg : COLORS.cyanD)
          Text(this.breath ? '暗房显影中' : '定影完成').fontSize(9).fontColor(COLORS.bg)
        }.padding({...}).backgroundColor(COLORS.card).borderRadius(11).opacity(this.breath ? 1 : 0.78)
      }
      Text('影随人动跟拍 · WebP 元数据工坊 · 下载双 URL 溯源').fontSize(11).fontColor(COLORS.bg).opacity(0.85)
      Row({ space: 8 }) {
        Text(`作品 ${this.workList.length}`)...
        Text(`收藏 ${(this.totalLikes() / 10000).toFixed(2)}w`)...
        Text(`本月出片 ${MONTH_SHOTS[5]}`)...
      }
    }.linearGradient({ angle: 120, colors: [[COLORS.cyanD, 0], [COLORS.cyan, 0.6], [COLORS.cyanD, 1]] })
    // 搜索条 + 新增作品按钮
    Row({ space: 8 }) {
      Row({ space: 6 }) { Text('🔍'); Text('搜作品 / 粘贴素材链接') }
        .onClick(() => { this.switchTab(3); })
      Text('+ 新增作品').onClick(() => { ...; this.addModal = true; })
    }
  }
}

头部由两大区块组成。上方是暗房渐变 Banner,使用 linearGradient 以 120° 角度从 cyanD(银盐青深色)到 cyan(银盐青)再到 cyanD 的三段渐变,模拟暗房显影液由深到浅再回深的层次。Banner 内含品牌名"影像采编"、呼吸圆点状态胶囊(breath 翻转时圆点在 bgcyanD 间切换,文案在"暗房显影中"与"定影完成"间切换,opacity 在 1 与 0.78 间切换)、三特性副标题和三枚创作数据胶囊(作品数、收藏数以万为单位、本月出片量)。下方是搜索条与新增按钮的横排,搜索条点击跳转至网页 Tab(素材链接下载入口),新增按钮清空表单后打开新增弹窗。

九、各 Tab 分析

9.1 作品 Tab:双列卡片与月度柱状图

作品 Tab 以三统计小卡开篇(作品总数用青色、收藏总数用琥珀色、本月出片用蓝色),随后是 Flex({ wrap: FlexWrap.Wrap }) 双列作品卡片。每张卡片顶部是 52 高度的渐变封面条替代缩略图,颜色按索引模 3 在青、琥珀、蓝三色渐变间轮换,卡片内依次展示作品主题(maxLines(1) + textOverflow(Ellipsis) 单行省略)、器材、曝光参数(fontFamily('monospace') 等宽字体)、收藏数与编辑/删除按钮。底部是月度出片柱状图。

9.2 相机 Tab:XComponent 预览与影随人动能力链

相机 Tab 是整个应用最复杂的硬件交互区。顶部授权状态卡展示 CAMERA 权限申请结果与 Surface 就绪状态,requestCameraPermission 通过 abilityAccessCtrl.createAtManager() 创建权限管理器,调用 requestPermissionsFromUser 动态申请 ohos.permission.CAMERAuser_grant 级),以 authResults[0] === 0 判定已授权。

模式切换行提供"开启影随人动"与"切换手动对焦"两个互斥按钮。startVideoMode 方法构建完整的 VideoSession 能力链:选后摄(CameraPosition.CAMERA_POSITION_BACK)→ 获取预览 Profile → 创建 CameraInput 并 open → 创建 PreviewOutput 绑定 XComponent SurfaceId → 创建 VideoSession → beginConfig/addInput/addOutput/commitConfig → 调用 queryFraming 执行影随人动三步能力链 → start

queryFraming(session: camera.VideoSession) {
  if (!session.isControlCenterSupported()) {
    this.framingState = '控制中心不支持'; return;
  }
  const effects = session.getSupportedEffectTypes();
  this.framingSupported = effects.includes(camera.ControlCenterEffectType.AUTO_FRAMING);
  if (!this.framingSupported) {
    this.framingState = 'AUTO_FRAMING 未声明'; return;
  }
  session.enableControlCenter(true);
  this.framingState = '影随人动已启用';
}

影随人动能力链是 6.1.1 的核心特性。第一步 isControlCenterSupported() 判断本机是否支持控制中心;第二步 getSupportedEffectTypes() 获取支持的效果类型列表,includes(AUTO_FRAMING) 判定是否声明影随人动;第三步 enableControlCenter(true) 请求系统接管构图,人物主体始终居中。任一步骤失败都会设置对应的状态文案,通过 framingStateColor 函数映射为不同颜色展示。

XComponent 预览本体以 type: XComponentType.SURFACE 声明为 Surface 类型,controller 绑定 previewControlleronLoad 回调中将 surfaceReady 置为 true。预览下方是影随人动状态卡和效果类型枚举表,展示三步能力链的执行结果与 ControlCenterEffectType 三个枚举值。

9.3 对焦 Tab:三档预设与读回校验

对焦 Tab 展示手动对焦三接口的完整闭环。顶部能力查询卡调用 isFocusDistanceSupported() 判定 PhotoSession 是否支持设置对焦距离(该方法仅 PhotoSession 有,VideoSession 无)。三档预设按钮(微距 0.1、近距 0.5、远距 0.9)选中时以青色高亮,未选中时为暗灰绿底。焦距滑杆卡以 Slider 组件提供 0.0 到 1.0 步进 0.01 的连续调节,distanceLabel 函数实时将数值映射为景别文案。

applyFocus() {
  this.photoSession.setFocusDistance(this.focusDistance);   // 设置
  const readBack = this.photoSession.getFocusDistance();    // 读回
  const ok = Math.abs(readBack - this.focusDistance) < 0.01 ? '已生效' : '读回偏差';
  this.focusRecords.unshift(new FocusRecord(this.focusDistance, readBack, ok));
}

applyFocus 方法是手动对焦的核心:先 setFocusDistance 设置目标距离,再 getFocusDistance 读回实际值,差值小于 0.01 判定"已生效",否则记录"读回偏差"。对焦记录时间线以 Scroll + ForEach 展示历史操作,每条记录显示时间、设置值、箭头、读回值(失败时显示"—")和校验结论胶囊,结论颜色通过 focusOkColor 函数映射。

9.4 网页 Tab:地址栏与下载双 URL 溯源

网页 Tab 独占内容区高度,顶部是地址栏(TextInput + 前往按钮),loadUrl 方法自动补全 https:// 协议前缀。快捷站点横滑行展示六个摄影社区,点击即加载至 Web 组件。Web 组件本体以 Web({ src: this.webUrl, controller: this.webController }) 声明,网页内点击下载链接自动进入下载代理四回调。

setupDownloadDelegate() {
  this.downloadDelegate.onBeforeDownload((item) => {
    item.start(dir + '/' + item.getSuggestedFileName());
  });
  this.downloadDelegate.onDownloadUpdated((item) => {
    this.dlPercent = item.getPercentComplete();
  });
  this.downloadDelegate.onDownloadFailed((item) => {
    this.dlState = '下载失败 · ' + item.getGuid();
  });
  this.downloadDelegate.onDownloadFinish((item) => {
    const originalUrl = item.getOriginalUrl();   // 原始 URL
    const referrerUrl = item.getReferrerUrl();   // 引用页 URL
    this.downloadRecords.unshift(new DownloadRecord(..., originalUrl, referrerUrl));
  });
  this.webController.setDownloadDelegate(this.downloadDelegate);
}

下载代理四回调齐全:onBeforeDownload 必须调用 item.start() 提供沙箱路径否则任务永远停在 PENDING;onDownloadUpdated 刷新进度条与百分比文案;onDownloadFailed 置失败文案并清零进度;onDownloadFinish 是 6.1.1 新特性的落点——调用 getOriginalUrl() 获取文件直链来源、getReferrerUrl() 获取触发下载的引用页地址,将两条 URL 连同文件名、大小、时间一起存入 DownloadRecordtriggerDownload 方法则支持应用侧主动发起下载(无需网页内点击),通过 webController.startDownload(url) 触发。

底部下载精简横滑卡展示最近四条下载记录的文件名与大小时间,完整双 URL 溯源信息保留在 downloadRecords 列表中。

9.5 工坊 Tab:暗房纹理与 WebP 生成

工坊 Tab 是 ImageKit 的素材准备区。暗房纹理五选一以 Flex 换行布局展示五种像素算法(颗粒对角斜纹、渐晕同心环、划痕竖带、光斑棋盘、锐化横带),选中时以青色高亮。参数卡提供编码质量滑杆(60 到 100 步进 5),下方是生成按钮与状态文案。

async genWebpFile() {
  const total = CANVAS_SIZE * CANVAS_SIZE;
  const buf = new ArrayBuffer(total * 4);
  const pixels = new Uint32Array(buf);
  for (let i = 0; i < total; i++) {
    pixels[i] = this.texPixelAt(this.texIdx, row, col);
  }
  const pm = await image.createPixelMap(buf, opts);
  const packer = image.createImagePacker();
  const webpBuf = await packer.packToData(pm, { format: 'image/webp', quality: this.webpQuality });
  const file = fileIo.openSync(path, READ_WRITE | CREATE | TRUNC);
  fileIo.writeSync(file.fd, webpBuf);
}

genWebpFile 方法分三步生成 WebP 文件:第一步按暗房纹理算法生成 96×96 像素画,texPixelAt 根据纹理索引将 (row, col) 坐标映射为 0xFFBBGGRR 颜色值写入 Uint32Array 缓冲;第二步 image.createImagePacker() 创建编码器,packToData 以指定质量编码为 WebP 字节流;第三步以 READ_WRITE | CREATE | TRUNC 模式打开沙箱文件写入字节流,READ_WRITE 模式是后续元数据写回的前提条件。重复生成前先 release() 旧 PixelMap 防内存增长。

像素画预览区直接以 Image(this.pixelMap) 渲染生成的 PixelMap,未生成时显示占位提示。沙箱落盘状态卡在文件生成后展示完整路径与落盘说明。

9.6 元数据 Tab:五字段读写与回读校验

元数据 Tab 是 ImageKit 元数据读写特性的核心展示区。顶部读取按钮调用 readMeta

async readMeta() {
  const types: image.MetadataType[] = [image.MetadataType.WEBP_METADATA];
  const meta = await source.readImageMetadataByType(types, 0);
  const webp = meta.webPMetadata;
  this.metaSnapshot = new WebpMetaSnapshot(
    webp?.canvasWidth ?? -1, webp?.canvasHeight ?? -1,
    webp?.delayTime ?? -1, webp?.unclampedDelayTime ?? -1,
    webp?.loopCount ?? -1);
}

readImageMetadataByType 接受元数据类型数组和帧索引(静态 WebP 恒传 0),返回的 webPMetadata 对象的五个字段均为可选(undefined 时以 ?? -1 兜底)。读取结果以五字段卡展示,fmtField 函数将 -1 渲染为"未提供",loopCount 为 0 时渲染为"不限"。

写入控制台提供帧延迟三档预设(120/200/500ms)和循环次数四档预设(0 不限/1/3/5 次),writeMeta 方法以字面量构造 WebPMetadata 对象挂到 ImageMetadata 上,调用 writeImageMetadata 写回文件。写入成功后立即调用 verifyRead 重建 ImageSource 回读校验:

async verifyRead() {
  const file = fileIo.openSync(this.webpPath, fileIo.OpenMode.READ_WRITE);
  const source = image.createImageSource(file.fd);
  const meta = await source.readImageMetadataByType([image.MetadataType.WEBP_METADATA], 0);
  const ok = this.verifySnapshot.delayTime === this.writeDelay
    && this.verifySnapshot.loopCount === this.writeLoop;
}

回读校验重建 ImageSource 是关键——同实例读取可能命中解码缓存导致读到旧值,重新 createImageSource 确保读到写入后的最新值。校验结果以高亮底色卡片区分于读取结果卡。操作日志流以固定高度 Scroll 展示生成/读取/写入/回读四类操作的详细结果,错误码直接嵌入描述文本。

9.7 我的 Tab:摄影师大卡与器材清单

我的 Tab 以银盐青渐变大卡开篇,展示摄影师身份信息(银盐客 · Chen,纪实街头/弱光人文认证摄影师)和三枚数据胶囊(出片数、收藏数、影龄)。器材清单以左色条区分类型(机身青、镜头琥珀、其他蓝),每行展示型号与用途说明。底部特性栈卡片速览本应用三大技术栈:Camera Kit(影随人动 + 手动对焦)、Image Kit(WebP 元数据读写 + 回读校验)、ArkWeb(onDownloadFinish 双 URL 溯源)。

十、图表卡片

@Builder
chartCard() {
  Column({ space: 10 }) {
    Row() {
      Text('📊 月度出片量').fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
      Blank()
      Text('近 6 个月 · 单位张').fontSize(9).fontColor(COLORS.text3)
    }
    Row({ space: 10 }) {
      ForEach(MONTH_SHOTS, (val: number, idx: number) => {
        Column({ space: 5 }) {
          Text(val.toString()).fontSize(8).fontColor(COLORS.sub)
          Column().width('100%').height((val / MONTH_MAX) * 88 + (this.breath ? 4 : 0))
            .linearGradient({ angle: 180, colors: [[COLORS.cyan, 0], [COLORS.cyanD, 1]] })
          Text(MONTH_NAME[idx]).fontSize(8).fontColor(COLORS.text3)
        }.layoutWeight(1).alignItems(HorizontalAlign.Center)
      })
    }.alignItems(VerticalAlign.Bottom)
  }
}

月度出片量柱状图采用传统 Column + ForEach 方案实现,每根柱子的高度按 val / MONTH_MAX * 88 计算(180 为 Y 轴最大值,88 为像素基准高度)。breath 状态联动时柱高叠加 4px 微动,模拟暗房显影的呼吸感。柱体使用 180° 纵向渐变从 cyan(顶部浅青)到 cyanD(底部深青),底部对齐 VerticalAlign.Bottom 保证柱子从下往上生长。每根柱子上方标注数值,下方标注月份,整体无需 Canvas 绘制即可实现可接受的柱状图效果。

十一、底部 Tab 栏

@Builder
tabBar() {
  Row() {
    ForEach(TAB_LIST, (t: TabMeta, idx: number) => {
      Column({ space: 3 }) {
        Text(t.icon).fontSize(16).opacity(this.currentTab === idx ? 1 : 0.65)
        Text(t.label).fontSize(8).fontColor(this.currentTab === idx ? COLORS.tabOn : COLORS.text3)
          .fontWeight(this.currentTab === idx ? FontWeight.Bold : FontWeight.Normal)
      }.layoutWeight(1).onClick(() => { this.switchTab(idx); })
    })
  }.backgroundColor(COLORS.card).border({ width: { top: 1 }, color: COLORS.line })
}

底部 Tab 栏以单排七项布局,每项 layoutWeight(1) 等宽分配。选中态以三重视觉差异强化:图标 opacity 从 0.65 升至 1、文字颜色从 text3 切换为 tabOn(银盐青)、字重从 Normal 切换为 Bold。顶部 1px 灰黑分割线与卡片底色形成层次边界。switchTab 方法在离开相机 Tab 时主动释放会话,避免后台摄像头占用。

十二、弹窗系统

12.1 通用遮罩

@Builder
modalOverlay(onClose: () => void) {
  Stack() {
    Column().width('100%').height('100%').backgroundColor(COLORS.mask)
  }.width('100%').height('100%').alignContent(Alignment.Center).onClick(() => onClose())
}

通用遮罩以 rgba(0,0,0,0.6) 半透黑覆盖全屏,点击遮罩触发 onClose 回调关闭弹窗。三个弹窗均复用此遮罩,实现统一的交互行为。

12.2 新增作品弹窗

新增弹窗包含主题名、器材、曝光参数三个 TextInput 输入框和收藏数 Slider 滑杆(0 到 3000 步进 10)。saveWork 方法以 unshift 将新作品置顶插入列表,空输入时给默认值(“未命名作品”“手机直出”“auto f/1.8 1/120s”),保证数据完整性。

12.3 编辑与删除弹窗

编辑弹窗仅调整收藏数,展示当前作品标题与器材信息作为上下文提示,updateWork 方法直接赋值 this.workList[this.editIdx].likes = this.editLikes,得益于 @Observed 装饰器,该字段级修改即时反映到作品卡片。删除弹窗以红色删除按钮和警示图标强化风险提示,delWork 方法以 splice 移除对应条目。

十三、功能模块对比表

功能模块技术特性核心 API交互方式状态反馈
影随人动Camera Kit · VideoSessionisControlCenterSupported / getSupportedEffectTypes / enableControlCenter模式切换按钮 → 能力链自动执行三步状态文案 + 颜色映射
手动对焦Camera Kit · PhotoSessionisFocusDistanceSupported / setFocusDistance / getFocusDistance三档预设 + 滑杆 + 应用按钮时间线记录 + 读回校验结论
WebP 生成ImageKit · ImagePackercreatePixelMap / packToData / fileIo.openSync纹理五选一 + 质量滑杆 + 生成按钮像素画预览 + 沙箱路径 + 字节数
元数据读取ImageKit · ImageSourcereadImageMetadataByType(WEBP_METADATA, 0)读取按钮五字段卡 + undefined 兜底
元数据写入ImageKit · ImageSourcewriteImageMetadata(ImageMetadata)帧延迟三档 + 循环四档 + 写入按钮操作日志 + 回读校验高亮卡
双 URL 溯源ArkWeb · WebDownloadDelegateonDownloadFinish / getOriginalUrl / getReferrerUrl网页内点击 / 应用侧主动发起下载进度 + 精简横滑卡 + 完整记录
作品管理ArkUI · @Observedunshift / splice / 字段赋值新增/编辑/删除三弹窗即时列表刷新 + 统计联动

深化解析:从代码结构到业务闭环

布局方式与数据流

摄影创作页面的主线是取景、对焦、采集、加工与归档。作品列表是业务结果,相机和对焦是采集手段,网页下载提供外部素材,工坊与元数据功能负责后期加工和校验。分析代码时要区分 VideoSession 与 PhotoSession 的能力边界,理解 Surface、控制器和记录模型的生命周期,并观察银盐青、琥珀色及警示红如何表达正常、偏差和失败。

页面根结构通常由头部、内容区和底部 Tab 栏组成。头部负责展示当前业务状态,内容区根据索引选择不同的 @Builder,底部导航负责修改索引。这样的结构把“当前显示什么”收敛为一个明确状态:用户点击 Tab 后先更新索引,ArkUI 再重新计算相关分支。各个 Builder 虽然共享主题色和页面级数据,却可以采用完全不同的布局方式;高密度列表适合纵向 Scroll,概览数据适合横向统计卡或双列 Flex,实时预览类组件需要独占有界高度,历史事件则适合时间轴或固定行高 List。

数据模型层承担界面与业务之间的契约。使用 @Observed 的实体保存可编辑字段,页面级 @State 数组负责驱动 ForEach。新增时创建新实体并插入数组,编辑时修改目标实体,删除时移除对应项。为了让列表差分稳定,key 应来自不会改变的唯一标识,不宜使用标题等可编辑字段。统计数字、完成比例和分类数量属于派生信息,可以从数组即时计算,避免同时维护两份状态后出现卡片已经更新、图表仍显示旧值的情况。

弹窗表单使用独立缓存是必要的。打开新增弹窗时清空缓存,打开编辑弹窗时复制目标字段,用户确认后才写回正式模型。这样点击取消不会污染列表数据。若直接把 TextInput 双向绑定到列表实体,用户尚未保存时卡片就可能跟着变化,破坏“确认提交”的交互语义。删除弹窗还需要保存目标索引或唯一标识,并在确认时再次校验目标存在,避免列表变化后误删其他项。

核心代码与状态驱动机制

@State 的价值不是简单替代普通变量,而是建立状态与界面之间的依赖关系。当前 Tab、筛选条件、动画开关、弹窗显隐、下载进度或能力状态发生变化时,只有读取这些变量的组件需要刷新。代码段中连续的修饰器调用分别控制尺寸、间距、背景、字体和事件,它们共同构成声明式描述;阅读时应从容器方向、子项分布、状态绑定和交互回调四个层面理解,而不是逐个孤立翻译属性名称。

ForEach 负责把数组映射为重复 UI。回调中的 item 提供业务字段,index 适合显示顺序,但不适合作为长期身份。列表发生新增或删除时,稳定 key 可以让框架复用未变化节点,减少重建。若直接修改对象属性后界面没有按预期刷新,可在保持实体身份的前提下替换数组引用;但不应为了刷新把所有元素都重新构造,否则会增加无意义渲染并丢失局部状态。

条件渲染体现了页面状态机。空闲时展示引导,准备中展示进度,成功时展示结果,失败时展示原因和重试入口。相比一个布尔值,四态文案更能覆盖异步能力。系统接口调用前先检查权限、设备支持和会话状态,调用后再读取结果校验。异常处理除了记录错误码,还要把可理解的反馈写入响应式状态,让用户知道失败发生在哪一步。

动画效果与颜色使用策略

呼吸动画通常由定时器周期翻转 breath,再把该状态映射为透明度、柱高或圆点半径的小幅变化。它适合表达“正在运行”或让统计图保持生命感,但幅度应克制,不能改变核心数据含义。柱状图的基础高度仍由真实数值计算,动画只能在很小范围内偏移;进度环的角度仍由完成比例决定,不能为了视觉效果显示超过真实进度的结果。页面离开时必须清理定时器,避免后台继续刷新。

颜色常量应按语义使用。主色承担选中态和主要操作,辅助色突出数据或次级动作,绿色表达完成与可用,橙色表达进行中或需要注意,红色只用于失败、逾期和删除等高风险场景。弱文本与分割线降低视觉权重,遮罩色用于聚焦弹窗。颜色不能成为唯一的状态信息,还要配合文字、图标或进度值,保证色觉差异用户也能理解。

渐变更适合头部大卡、核心指标或柱状图,不宜在每个小元素上重复使用。深色主题要检查正文与卡片背景的对比度,浅色主题则要避免辅助文字过淡。选中和未选中 Tab 除颜色差异外,还可以通过字重、图标透明度或底部指示器区分。这样既保持主题统一,又能建立清晰的信息层级。

各 Tab 之间的交互联动

各 Tab 不应只共享一个导航索引,还应围绕业务对象建立必要联动。列表页新增或编辑数据后,头部计数、图表和个人统计要同步更新;网页或地图产生的结果应写入记录模型,供下载、日志或我的页面继续展示;通知、字幕、相机等系统能力的状态应在头部胶囊或对应 Tab 中保持一致。跨 Tab 跳转时先更新必要参数,再修改当前索引,可以避免目标页面读取到旧条件。

切换离开重型组件时需要处理资源边界。相机输入、地图监听、字幕控制器、Web 下载代理和定时器都不能只创建不释放。可以在统一的 switchTab 方法中判断来源与目标,离开能力页时解除监听或停止会话;页面销毁时再执行兜底释放。释放方法应允许重复调用,并对每个资源独立判空,确保一次异常不会阻止后续清理。

交互反馈要覆盖成功与失败。按钮点击后先进入处理中状态并防止重复提交;成功后更新模型、关闭弹窗并显示结果;失败后保留用户输入,展示错误原因和重试入口。权限拒绝、能力不支持、网络失败、文件不存在和输入非法都属于正常业务分支。通过状态卡或行内提示展示这些分支,比只在控制台打印更符合完整产品体验。

边界场景与验证思路

空列表时应显示占位说明和新增入口,不能只留下空白。长标题需要限制行数并使用省略号,数字字段需要限定上下界,文本提交前要去除首尾空格。筛选后无结果应保留清除条件的入口。删除最后一项后,当前选择索引要回退到有效范围。异步搜索连续触发时,应防止较早请求晚返回后覆盖新结果。

验证数据链路时,可以依次检查新增、编辑、删除和筛选:新增后列表条数、统计数字和图表是否同时变化;编辑取消后正式数据是否保持不变;删除后 ForEach key 是否稳定;切换 Tab 再返回时必要数据是否仍在。验证系统能力时分别模拟支持、拒绝和异常,确认界面都有明确状态。验证动画时检查页面离开后是否停止,低性能设备上是否仍保持流畅。

视觉验收需要检查不同屏幕宽度、系统字体放大、深浅背景对比和长文本换行。表格中的布局方式、模型、字段数、核心操作、动画、状态颜色、数据量和特殊组件应与正文一致。Mermaid 图则需要对应真实的数据流和能力链路,节点文字加引号以避免中文或特殊字符导致解析失败。

组件化设计的进一步理解

参数化 Builder 适合抽取重复的统计格、状态行、标签和按钮组。参数只传入渲染所需数据和事件,不让子构建器直接依赖过多页面变量,可以降低耦合。业务复杂后,可把模型与系统能力封装为独立控制器,页面只负责组合 UI 和响应状态。这样既保留声明式代码的直观性,也能让权限、错误码翻译和资源释放得到集中管理。

当前单页面集中展示完整源码,便于博文逐段讲解。若演进为正式项目,可以按领域拆分组件:导航和页面框架位于容器层,列表、图表和弹窗位于展示层,数据读写和 Kit 接入位于服务层。组件之间通过参数、回调、@Link@ObjectLink 传递状态,不使用全局变量代替清晰的数据流。

性能优化首先来自减少不必要刷新。派生数据不要重复存储,动画状态不要进入列表 key,长列表使用稳定标识,Canvas 只在数据或尺寸变化时重绘。其次是控制资源生命周期,页面不可见时停止高成本任务。最后才是微调阴影、渐变和绘制细节。这样的优先级能保证页面在功能增加后仍然可维护。

通过以上补充,可以看到 ArkUI 的声明式模式并非只让布局语法更简洁,它更重要的价值是把数据变化、界面刷新和交互反馈连接为可追踪链路。理解每个代码段读取什么状态、写入什么状态、影响哪些组件,才能真正掌握文章中多个 Tab、图表、弹窗和系统能力协同工作的原理。

十四、总结与展望

本影像采编平台以"暗房黑 + 银盐青 + 暗房琥珀"三色体系为视觉基底,以七 Tab 分层架构为骨架,将 Camera Kit 的影随人动与手动对焦、ImageKit 的 WebP 元数据读写回读校验、ArkWeb 的下载双 URL 溯源三大 HarmonyOS 6.1.1 前沿特性编织成一条完整的"采-编-存-校"创作链路。从暗房纹理像素画生成到 WebP 编码落盘,从类型化元数据读取到字面量构造写回再到重建 ImageSource 回读校验,从下载代理四回调到双 URL 溯源记录,每个特性都形成了可验证的闭环。

在工程实践层面,本组件展现了若干值得借鉴的模式:状态变量按职责分类声明(Tab 状态、弹窗状态、动画状态、Camera 成员、WebP 成员、ArkWeb 成员),跨 Tab 共享统一在组件顶层;纯函数提取状态颜色映射与文案转换,实现视图与逻辑解耦;@Observed 数据模型让字段级修改即时反映到 ForEach 列表项,无需手动刷新;会话释放统一走 releaseSession 五步释放链(off error → stop → release session → release preview → close input),防止后台摄像头占用;元数据回读校验重建 ImageSource 防解码缓存命中旧值;下载代理 onBeforeDownload 必须调用 start() 提供沙箱路径否则任务停在 PENDING。

展望未来,本平台可在以下方向深化:其一,引入 AI 辅助构图,将影随人动的主体居中能力与场景识别结合,实现人像/风光/街拍自适应构图建议;其二,扩展 WebP 动图批量生成能力,将暗房纹理五选一升级为多帧动画时间线编辑器;其三,接入云同步与协作分享,让下载素材的双 URL 溯源信息成为团队协作的素材溯源标准;其四,融合 Speech Kit 的 AI 字幕能力,为视频素材自动生成带时间戳的文字描述,实现影像采编的内容级检索。鸿蒙生态的多设备协同特性也为本平台提供了更广阔的想象空间——手机采集、平板精修、大屏展示的无缝流转,将让影像创作的每一个环节都拥有最合适的交互载体。

附录:DevEco Studio 创建新项目与查看 SDK 版本

本章节演示如何使用 DevEco Studio 创建一个 HarmonyOS 新项目,并查看当前 IDE 已安装的 SDK 版本,适合作为其他技术博文的补充操作指南。


一、创建新项目

1.1 进入欢迎界面

启动 DevEco Studio 后,首先看到的是欢迎界面。左侧导航栏默认选中 “项目”,右侧提供三个主要入口:

  • 新建项目:从头创建新项目
  • 打开项目:打开本地已有项目
  • 克隆仓库:从 Git 等版本控制拉取代码

点击 “新建项目” 按钮,进入项目创建向导。

在这里插入图片描述

1.2 选择项目模板

在弹出的"新建项目"对话框中,左侧分类标签提供了两种项目类型:

类型说明
应用(Application)开发标准的 HarmonyOS 应用,具备完整的 Ability 生命周期
元服务(Atomic Service)开发轻量级的原子化服务,无需安装即可使用

选择 “应用” 标签后,右侧展示多种模板。对于大多数场景,推荐选择 “Empty Ability” —— 这是一个最基础的入门模板,仅包含 Hello World 功能,适合从零开始构建应用。

在这里插入图片描述

1.3 配置项目信息

点击 “下一步” 后,进入项目配置界面,需要填写以下核心参数:

配置项示例值说明
项目名称(Project name)rollboat应用的项目名称,建议使用英文命名
包名(Bundle name)com.rollboat.myapplication应用唯一标识,采用反向域名格式
保存路径(Save location)D:\CodeFactory\rollboat项目本地存储路径,避免使用中文和空格
兼容 SDK(Compatible SDK)6.1.1(24)目标 HarmonyOS API 版本,点击"查看参考"可了解各版本差异
模块名称(Module name)entry主模块名称,默认 entry 为应用入口模块
设备类型(Device types)☑ Phone勾选目标设备:Phone / Tablet / 2in1 / Car / Wearable / TV

右侧预览区会实时展示当前模板的默认效果 —— 一个居中显示的 “Hello World” 文本。

在这里插入图片描述

1.4 完成创建

确认配置无误后,点击右下角 “完成” 按钮,IDE 将自动执行以下操作:

  1. 生成项目骨架(Stage 模型目录结构)
  2. 执行 ohpm install 安装依赖
  3. 运行 Hvigor 构建初始化(Build Init

构建日志中显示 “退出代码为 0” 表示项目初始化成功。

在这里插入图片描述

1.5 项目结构概览

创建完成后,左侧项目面板展示的是标准的 Stage 模型 目录结构:

rollboat/
├── .hvigor/                   # Hvigor 构建工具缓存
├── .idea/                     # IDE 配置文件
├── AppScope/                  # 应用级全局配置
│   └── app.json5
├── entry/                     # 主模块(入口模块)
│   ├── src/main/ets/
│   │   ├── entryability/      # Ability 生命周期管理
│   │   │   └── EntryAbility.ets
│   │   └── pages/             # UI 页面
│   │       └── Index.ets      # 首页(默认 Hello World)
│   ├── src/main/resources/    # 资源文件
│   ├── module.json5           # 模块配置
│   └── build-profile.json5    # 构建配置
├── oh_modules/                # OHPM 依赖包
├── build-profile.json5        # 工程构建配置
├── hvigorfile.ts              # Hvigor 构建脚本
└── oh-package.json5           # 包管理配置

核心文件 Index.ets 的默认代码如下,采用 ArkTS 声明式 UI 语法:

@Entry
@Component
struct Index {
  @State message: string = 'Hello World';

  build() {
    RelativeContainer() {
      Text(this.message)
        .id('HelloWorld')
        .fontSize($r('app.float.page_text_font_size'))
        .fontWeight(FontWeight.Bold)
        .alignRules({
          center: { anchor: '__container__', align: VerticalAlign.Center },
          middle: { anchor: '__container__', align: HorizontalAlign.Center }
        })
        .onClick(() => {
          this.message = 'Welcome';
        })
    }
    .height('100%')
    .width('100%')
  }
}
关键语法作用
@Entry标记为页面入口,可用于路由跳转
@Component声明为自定义组件
@State状态变量,数据变更时自动触发 UI 刷新
RelativeContainer相对布局容器,替代传统线性布局
.onClick()点击事件,此处点击后文本变为 “Welcome”

打开右侧 Previewer(预览器),选择 Phone 设备,即可实时预览 Hello World 效果,无需连接真机或启动模拟器。

在这里插入图片描述


二、查看 SDK 版本

2.1 查看 HarmonyOS SDK

DevEco Studio 安装时已内置 HarmonyOS SDK,无需单独下载。通过以下路径查看:

文件 → 设置 → HarmonyOS SDK(或快捷键 Ctrl + Alt + S 搜索 “HarmonyOS SDK”)

在设置面板中,可以看到当前已安装的 SDK 版本信息:

名称阶段状态
HarmonyOS 6.1.1Release✅ 已安装

界面顶部提示:“HarmonyOS SDK 已经包含在 IDE,无需单独安装”,省去了手动配置 SDK 的繁琐步骤。

在这里插入图片描述

2.2 查看 ArkUI-X SDK(跨平台扩展)

如果项目需要将 ArkUI 框架扩展到多个 OS 平台(Android / iOS / OpenHarmony),还需要配置 ArkUI-X SDK。路径如下:

文件 → 设置 → 语言和框架 → ArkUI-X

在这里可以查看已安装和可选的 ArkUI-X SDK 版本:

版本SDK 版本号阶段状态
API Version 246.1.1.100Release✅ 已安装
API Version 236.1.0.28Beta1未安装
API Version 226.0.2.112Release未安装

安装路径示例:D:\DevTools\ArkUI-X\sdk

说明:ArkUI-X 允许开发者使用一套 ArkTS 主代码,同时构建多平台应用。如果仅开发 HarmonyOS 原生应用,无需额外安装 ArkUI-X SDK。

在这里插入图片描述


三、小结

步骤操作关键点
创建项目欢迎页 → 新建项目 → 选择 Empty Ability 模板 → 配置项目信息 → 完成使用 Stage 模型 + ArkTS 语言
查看 SDK设置 → HarmonyOS SDKSDK 已内置,无需手动安装
跨平台扩展设置 → ArkUI-X根据需要安装对应 API 版本

至此,DevEco Studio 的项目创建与 SDK 环境确认全部完成,可以开始 HarmonyOS 应用的功能开发。


Logo

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

更多推荐