第2.1篇:相机开发实战------调用系统相机拍照
难度 :⭐⭐ 进阶
前置知识 :第 1.6 篇 页面路由与生命周期
涉及源文件 :
products/default/src/main/ets/components/CreationComponents.ets

概述
在"画伴梦工厂"中,用户需要拍摄自己的画作,然后将其转化为动画。HarmonyOS 提供了 startAbilityForResult 接口,让我们可以启动系统相机拍照并获取返回的图片 URI。本文将详细拆解这一流程,涵盖 Want 配置、权限申请、结果回调解析以及父子组件之间的数据同步。
一、相机 Want 动作常量
启动系统相机需要指定一个标准的 Want Action,告诉系统我们要执行"图像捕获"操作:
typescript
const CAMERA_WANT_ACTION: string = 'ohos.want.action.imageCapture';
ohos.want.action.imageCapture 是 HarmonyOS 预定义的系统公共事件,用于唤起系统相机应用进行拍照。
二、权限申请前置检查
在调用系统相机之前,必须先申请 ohos.permission.CAMERA 权限。项目中将权限申请抽取为独立的 PermissionGuard 服务:
typescript
import { PermissionGuard, PermissionResult } from '../services/PermissionGuard';
private async openCamera() {
const context = getContext(this) as common.UIAbilityContext;
const permissionResult: PermissionResult = await PermissionGuard.requestCamera(context);
if (!permissionResult.granted) {
this.noticeText = permissionResult.message;
return;
}
// ... 继续打开相机
}
PermissionGuard.requestCamera 内部调用 abilityAccessCtrl.createAtManager().requestPermissionsFromUser,向用户弹出权限请求对话框。若用户拒绝,会通过 noticeText 提示用户前往设置中开启权限。
typescript
// PermissionGuard.ets
static async requestCamera(context: common.UIAbilityContext): Promise<PermissionResult> {
return PermissionGuard.request(context, ['ohos.permission.CAMERA'], '请在设置里打开相机权限后继续');
}
三、启动系统相机
权限通过后,通过 UIAbilityContext 的 startAbilityForResult 方法启动系统相机:
typescript
context.startAbilityForResult({
action: CAMERA_WANT_ACTION
}).then((result: common.AbilityResult) => {
const uri = this.getImageUriFromResult(result);
this.capturePhoto(uri, '拍照图片');
if (uri !== '') {
this.noticeText = '拍照完成,已用新图片覆盖当前预览';
}
}).catch(() => {
this.noticeText = '相机未成功返回,已载入示例画作便于继续流程';
this.capturePhoto();
});
关键点解析
| 要点 | 说明 |
|---|---|
| startAbilityForResult | 启动系统 Ability 并异步等待返回结果,适用于需要获取返回数据的场景 |
| Want 对象 | 只需传入 action 字段,无需指定 uri 或 type,系统相机自动处理 |
| Promise 回调 | then 接收拍照成功的结果,catch 处理用户取消或相机异常的情况 |
| 兜底策略 | 即使拍照失败,也会调用 capturePhoto()(无参数),让流程可以继续 |
这种"兜底策略"设计非常巧妙------即使用户没有成功拍照,仍会使用示例画作填充,保证用户体验不中断。
四、解析相机返回结果
系统相机返回的 AbilityResult 结构因设备型号和系统版本而异,为了兼容不同机型,getImageUriFromResult 方法实现了多层 fallback 解析:
typescript
private getImageUriFromResult(result: common.AbilityResult): string {
// 第一层:直接获取 want.uri
if (result.want && result.want.uri) {
return result.want.uri;
}
// 第二层:当 want.uri 为空时,从 parameters 中查找
if (!result.want || !result.want.parameters) {
return '';
}
const parameters = result.want.parameters;
const uriKeys: string[] = ['uri', 'imageUri', 'resourceUri', 'select-item-list'];
for (let i = 0; i < uriKeys.length; i++) {
const value = parameters[uriKeys[i]];
if (typeof value === 'string') {
return value;
}
if (Array.isArray(value) && value.length > 0 && typeof value[0] === 'string') {
return value[0];
}
}
return '';
}
解析策略说明
result.want.uri(优先级最高):部分系统版本会将图片 URI 直接放在want对象的顶层uri字段。result.want.parameters遍历 (兼容模式):在不同 HarmonyOS 版本上,返回的 key 名可能不同,代码尝试了uri、imageUri、resourceUri、select-item-list四种常见 key 名。- 值类型兼容:同一个 key 在某些版本上是字符串,在某些版本上是数组(单选时数组长度为 1),代码对两种类型都做了处理。
- 兜底返回空字符串:所有查找方式均失败时返回空,上层逻辑会载入示例画作。
五、拍照后的统一处理
无论来自相机还是相册的图片,最终都走 capturePhoto 方法统一处理:
typescript
private capturePhoto(uri: string = '', sourceLabel: string = '拍照图片') {
if (uri !== '') {
this.capturedImageUri = uri;
this.imageSourceLabel = sourceLabel;
}
this.hasPhoto = true;
this.activeStep = 1; // 进入"准备图片"步骤
this.generationProgress = 35; // 更新进度条
this.noticeText = '已采集画作,可以直接生成动画';
}
该方法做了三件事:
- 存储图片信息:保存 URI 和来源标签("拍照图片"或"相册图片")
- 更新状态 :标记
hasPhoto = true,推进步骤到第 1 步 - 反馈用户:更新进度值(35%)和提示文字
六、子组件与父组件通信 (@Link)
PhotoRecognitionComponent 组件将内部状态通过 @Link 装饰器暴露给父组件 PhotoRecognitionPage,实现双向数据同步:
子组件定义
typescript
@Component
export struct PhotoRecognitionComponent {
@Link generationProgress: number;
@Link noticeText: string;
@State private hasPhoto: boolean = false;
@State private capturedImageUri: string = '';
// ...
}
@Link:与父组件共享状态,子组件对generationProgress和noticeText的修改会直接反映到父组件 UI 上。@State:组件私有状态,仅在组件内部可见,对外部透明。
父组件调用
typescript
PhotoRecognitionComponent({
generationProgress: $generationProgress,
noticeText: $noticeText
})
父组件通过 $ 语法传递状态变量的引用,子组件即可通过 @Link 接收并双向绑定。
七、用户界面触发
在 UI 层,用户通过点击"拍照采集"按钮触发整个流程:
typescript
Button('拍照采集')
.onClick(() => {
if (!this.recognizing) {
this.openCamera();
}
})
同时检查 recognizing 状态,避免在生成动画过程中重复触发相机操作。
八、完整流程时序图
用户点击"拍照采集"
│
▼
PermissionGuard.requestCamera()
│
├── 用户拒绝 → noticeText 提示 → 结束
│
└── 用户授权
│
▼
startAbilityForResult({ action: 'ohos.want.action.imageCapture' })
│
├── 成功 → getImageUriFromResult() 提取 URI → capturePhoto(uri)
│
└── 失败/取消 → capturePhoto()(载入示例画作)
│
▼
hasPhoto = true
activeStep = 1
generationProgress = 35
总结
本文通过"画伴梦工厂"的相机拍照功能,完整演示了 HarmonyOS 系统相机调用的最佳实践:
| 知识点 | 实现方式 |
|---|---|
| 启动系统相机 | startAbilityForResult + ohos.want.action.imageCapture |
| 权限申请 | PermissionGuard.requestCamera 封装 |
| 结果解析 | 多层 fallback 解析 AbilityResult.want |
| 状态同步 | @Link 双向绑定父子组件数据 |
| 异常兜底 | catch 中自动载入示例画作 |
下一节我们将探讨另一种图片采集方式------通过 PhotoViewPicker 从相册中选择图片,以及它相对相机模式的不同场景和权限模型差异。