鸿蒙自定义相机开发:Camera Kit
本文介绍了在鸿蒙系统(HarmonyOSNext)中开发自定义相机的完整流程。主要内容包括:1)权限申请配置;2)使用XComponent组件构建相机预览界面;3)通过CameraKit实现相机核心功能,包括创建会话、配置输入输出、启动控制等;4)处理拍照结果并保存图片;5)常见问题解决方案。文章详细讲解了从UI布局到后台逻辑的实现过程,重点说明了surfaceId获取、分辨率匹配等关键点,为开发
·
在鸿蒙(HarmonyOS Next)开发中,如果系统自带的相机界面无法满足需求(例如需要自定义取景框、添加实时滤镜、或做证件扫描),我们就需要使用 Camera Kit 来开发自定义相机。
本文将带你从零实现一个具备实时预览和拍照功能的自定义相机页面。
1. 核心流程概览
开发自定义相机主要分为 5 个步骤:
- 权限申请:相机和麦克风权限。
- 创建会话 (Session):管理相机的输入流和输出流。
- 配置输入 (Input):选择相机设备(前置/后置)。
- 配置输出 (Output):
-
- PreviewOutput: 用于在屏幕上显示实时画面(绑定
XComponent)。 - PhotoOutput: 用于捕获静态照片。
- PreviewOutput: 用于在屏幕上显示实时画面(绑定
- 启动与控制:开启会话,处理拍照动作。
2. 权限配置
2.1 module.json5
在 entry/src/main/module.json5 中添加权限:
"requestPermissions": [
{
"name": "ohos.permission.CAMERA",
"reason": "$string:camera_reason",
"usedScene": { "abilities": ["EntryAbility"], "when": "always" }
},
{
"name": "ohos.permission.MICROPHONE",
"reason": "$string:mic_reason",
"usedScene": { "abilities": ["EntryAbility"], "when": "always" }
},
{
"name": "ohos.permission.WRITE_MEDIA", // 保存图片需要
"reason": "$string:media_reason",
"usedScene": { "abilities": ["EntryAbility"], "when": "always" }
}
]
2.2 动态申请
在页面加载时(aboutToAppear),记得使用 abilityAccessCtrl 弹窗申请权限。
3. UI 布局:XComponent
鸿蒙相机预览必须使用 XComponent 组件,因为它能提供底层的 Surface 给 Camera Kit 渲染画面。
// CameraPage.ets
@Entry
@Component
struct CameraPage {
private mXComponentController: XComponentController = new XComponentController();
private surfaceId: string = '';
build() {
Stack() {
// 1. 预览区域
XComponent({
id: 'cameraPreview',
type: XComponentType.SURFACE,
controller: this.mXComponentController
})
.onLoad(() => {
// 获取 surfaceId,这是连接相机和 UI 的关键
this.mXComponentController.setXComponentSurfaceSize({ surfaceWidth: 1080, surfaceHeight: 1920 });
this.surfaceId = this.mXComponentController.getXComponentSurfaceId();
// 初始化相机
this.initCamera();
})
.width('100%')
.height('100%')
// 2. 拍照按钮
Button()
.width(80)
.height(80)
.backgroundColor(Color.White)
.borderRadius(40)
.margin({ bottom: 50 })
.onClick(() => {
this.takePhoto();
})
}
.width('100%')
.height('100%')
.alignContent(Alignment.Bottom)
.backgroundColor(Color.Black)
}
}
4. 核心逻辑:Camera Kit
创建一个 CameraService.ts 来封装复杂的相机逻辑。
import { camera } from '@kit.CameraKit';
import { image } from '@kit.ImageKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { fileIo } from '@kit.CoreFileKit';
export class CameraService {
private cameraManager: camera.CameraManager | undefined = undefined;
private cameraInput: camera.CameraInput | undefined = undefined;
private previewOutput: camera.PreviewOutput | undefined = undefined;
private photoOutput: camera.PhotoOutput | undefined = undefined;
private session: camera.PhotoSession | undefined = undefined;
// 初始化相机
async initCamera(surfaceId: string) {
// 1. 获取 CameraManager
this.cameraManager = camera.getCameraManager(getContext(this));
// 2. 获取支持的相机设备列表(0通常是后置,1是前置)
const cameras = this.cameraManager.getSupportedCameras();
const backCamera = cameras[0]; // 简化逻辑,默认取第一个
if (!backCamera) return;
// 3. 创建 Input
this.cameraInput = this.cameraManager.createCameraInput(backCamera);
await this.cameraInput.open();
// 4. 获取支持的输出配置 (Profile)
const profiles = backCamera.capability;
const previewProfile = profiles.previewProfiles[0]; // 预览分辨率
const photoProfile = profiles.photoProfiles[0]; // 拍照分辨率
// 5. 创建 Output
// 预览流 -> 绑定到 UI 的 surfaceId
this.previewOutput = this.cameraManager.createPreviewOutput(previewProfile, surfaceId);
// 拍照流 -> 这里的 surfaceId 暂时传 null,后续在回调里拿 buffer
this.photoOutput = this.cameraManager.createPhotoOutput(photoProfile);
// 6. 创建 Session 并开始配置
this.session = this.cameraManager.createSession(camera.SceneMode.NORMAL_PHOTO) as camera.PhotoSession;
this.session.beginConfig();
// 7. 添加输入输出
this.session.addInput(this.cameraInput);
this.session.addOutput(this.previewOutput);
this.session.addOutput(this.photoOutput);
// 8. 提交配置并启动
await this.session.commitConfig();
await this.session.start();
}
// 拍照
async takePhoto() {
if (!this.photoOutput) return;
// 配置拍照参数
const photoCaptureSetting: camera.PhotoCaptureSetting = {
quality: camera.QualityLevel.QUALITY_LEVEL_HIGH,
rotation: camera.ImageRotation.ROTATION_0
};
// 触发拍照
await this.photoOutput.capture(photoCaptureSetting);
}
// 释放资源(页面销毁时调用)
async release() {
await this.session?.stop();
await this.session?.release();
await this.cameraInput?.close();
}
}
5. 处理拍照结果 (保存图片)
上面的 takePhoto 只是触发了动作,要拿到图片数据,需要监听 photoOutput 的回调。
修改 initCamera 方法,在创建 photoOutput 后添加监听:
// 在 initCamera 中
this.photoOutput.on('photoAvailable', (err, photo: camera.Photo) => {
if (err) return;
// photo.mainImage 就是图片数据
let imageObj = photo.mainImage;
// 获取 ByteBuffer
imageObj.getComponent(image.ComponentType.JPEG).then((component) => {
const buffer = component.byteBuffer;
// TODO: 使用 fileIo 将 buffer 写入沙箱文件
this.savePicture(buffer);
// 记得释放 image
imageObj.release();
});
});
6. 常见坑点总结
- SurfaceId 为空:必须在
XComponent.onLoad回调中获取 surfaceId,不能在aboutToAppear里拿,否则相机没地方渲染,会黑屏。 - 分辨率匹配:
previewProfile和photoProfile必须从camera.capability中选取,不能随意捏造宽高,否则会报错。 - 生命周期:页面跳转或后台时,务必调用
session.stop()和release(),否则下次回来相机可能被占用无法启动。 - 方向问题:拍照出来的照片可能会旋转 90 度,需要根据设备的
orientation设置capture参数中的rotation。
通过以上步骤,你就能在鸿蒙上实现一个功能完备的自定义相机了。
更多推荐



所有评论(0)