HarmonyOS 6 效果实现 Tabs双层嵌套手势联动调试,区分SELF_FIRST与SELF_ONLY两种滚动模式行为差异
一、技术前言
在移动影像创作领域,摄影早已不止于按下快门那一刻。从前期取景跟拍、手动对焦精修,到中期素材下载归档,再到后期 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(影随人动)能力链——通过 isControlCenterSupported、getSupportedEffectTypes、enableControlCenter 三步实现跟拍时人物主体始终居中;同时 PhotoSession 的手动对焦三接口 isFocusDistanceSupported、setFocusDistance、getFocusDistance 实现从微距静物到远距街景的精确对焦控制,设置值与读回值差值小于 0.01 判定生效。ImageKit 的 WebP 元数据读写通过 readImageMetadataByType 类型化读取五字段(canvasWidth/canvasHeight/delayTime/unclampedDelayTime/loopCount),再以 writeImageMetadata 字面量构造写回,写入后重建 ImageSource 回读校验形成闭环。ArkWeb 在 onDownloadFinish 完成回调中调用 6.1.1 新增的 getOriginalUrl 与 getReferrerUrl 双接口,实现下载文件原始直链与引用页面的双 URL 溯源。
二、整体架构流程图
架构以主组件为根,使用 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(暗房琥珀),这是因为青色在暗黑背景上的对比度更高,用户视觉定位更迅速。头部横幅使用 linearGradient 从 cyanD 到 cyan 的 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 为分界点将连续的对焦距离值离散化为三档景别文案;sessionLabel 将 idle/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 切换;弹窗状态以三个布尔独立控制三个弹窗的条件渲染,editIdx 和 delIdx 记录操作目标索引;动画状态 breath 由 setInterval 每秒翻转一次,驱动呼吸圆点与柱状图微动;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 翻转时圆点在 bg 与 cyanD 间切换,文案在"暗房显影中"与"定影完成"间切换,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.CAMERA(user_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 绑定 previewController,onLoad 回调中将 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 连同文件名、大小、时间一起存入 DownloadRecord。triggerDownload 方法则支持应用侧主动发起下载(无需网页内点击),通过 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 · VideoSession | isControlCenterSupported / getSupportedEffectTypes / enableControlCenter | 模式切换按钮 → 能力链自动执行 | 三步状态文案 + 颜色映射 |
| 手动对焦 | Camera Kit · PhotoSession | isFocusDistanceSupported / setFocusDistance / getFocusDistance | 三档预设 + 滑杆 + 应用按钮 | 时间线记录 + 读回校验结论 |
| WebP 生成 | ImageKit · ImagePacker | createPixelMap / packToData / fileIo.openSync | 纹理五选一 + 质量滑杆 + 生成按钮 | 像素画预览 + 沙箱路径 + 字节数 |
| 元数据读取 | ImageKit · ImageSource | readImageMetadataByType(WEBP_METADATA, 0) | 读取按钮 | 五字段卡 + undefined 兜底 |
| 元数据写入 | ImageKit · ImageSource | writeImageMetadata(ImageMetadata) | 帧延迟三档 + 循环四档 + 写入按钮 | 操作日志 + 回读校验高亮卡 |
| 双 URL 溯源 | ArkWeb · WebDownloadDelegate | onDownloadFinish / getOriginalUrl / getReferrerUrl | 网页内点击 / 应用侧主动发起 | 下载进度 + 精简横滑卡 + 完整记录 |
| 作品管理 | ArkUI · @Observed | unshift / 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 将自动执行以下操作:
- 生成项目骨架(Stage 模型目录结构)
- 执行
ohpm install安装依赖 - 运行 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.1 | Release | ✅ 已安装 |
界面顶部提示:“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 24 | 6.1.1.100 | Release | ✅ 已安装 |
| API Version 23 | 6.1.0.28 | Beta1 | 未安装 |
| API Version 22 | 6.0.2.112 | Release | 未安装 |
安装路径示例:D:\DevTools\ArkUI-X\sdk
说明:ArkUI-X 允许开发者使用一套 ArkTS 主代码,同时构建多平台应用。如果仅开发 HarmonyOS 原生应用,无需额外安装 ArkUI-X SDK。

三、小结
| 步骤 | 操作 | 关键点 |
|---|---|---|
| 创建项目 | 欢迎页 → 新建项目 → 选择 Empty Ability 模板 → 配置项目信息 → 完成 | 使用 Stage 模型 + ArkTS 语言 |
| 查看 SDK | 设置 → HarmonyOS SDK | SDK 已内置,无需手动安装 |
| 跨平台扩展 | 设置 → ArkUI-X | 根据需要安装对应 API 版本 |
至此,DevEco Studio 的项目创建与 SDK 环境确认全部完成,可以开始 HarmonyOS 应用的功能开发。
更多推荐

所有评论(0)