HarmonyOS APP《画伴梦工厂》开发第9篇:相机开发实战——调用系统相机拍照

第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 字段,无需指定 uritype,系统相机自动处理
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 '';
}

解析策略说明

  1. result.want.uri (优先级最高):部分系统版本会将图片 URI 直接放在 want 对象的顶层 uri 字段。
  2. result.want.parameters 遍历 (兼容模式):在不同 HarmonyOS 版本上,返回的 key 名可能不同,代码尝试了 uriimageUriresourceUriselect-item-list 四种常见 key 名。
  3. 值类型兼容:同一个 key 在某些版本上是字符串,在某些版本上是数组(单选时数组长度为 1),代码对两种类型都做了处理。
  4. 兜底返回空字符串:所有查找方式均失败时返回空,上层逻辑会载入示例画作。

五、拍照后的统一处理

无论来自相机还是相册的图片,最终都走 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 :与父组件共享状态,子组件对 generationProgressnoticeText 的修改会直接反映到父组件 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 从相册中选择图片,以及它相对相机模式的不同场景和权限模型差异。