在鸿蒙系统中实现拍照预览功能
1. 配置权限和设备能力
首先在 module.json5 中配置权限:
c
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.CAMERA"
}
],
"abilities": [
{
"permissions": [
"ohos.permission.CAMERA"
]
}
]
}
}
2. 主页面实现拍照预览
c
// CameraPreview.ets
import camera from '@ohos.multimedia.camera';
import image from '@ohos.multimedia.image';
import fs from '@ohos.file.fs';
import common from '@ohos.app.ability.common';
import { BusinessError } from '@ohos.base';
@Entry
@Component
struct CameraPreviewPage {
// 相机相关变量
@State previewSurfaceId: string = '';
@State isPreviewing: boolean = false;
@State capturePath: string = '';
// 相机实例
private cameraManager: camera.CameraManager | null = null;
private cameraInput: camera.CameraInput | null = null;
private previewOutput: camera.PreviewOutput | null = null;
private photoOutput: camera.PhotoOutput | null = null;
private cameraObj: camera.CameraDevice | null = null;
aboutToAppear() {
this.initCamera();
}
aboutToDisappear() {
this.releaseCamera();
}
// 初始化相机
async initCamera() {
try {
// 1. 获取相机管理器
this.cameraManager = await camera.getCameraManager(globalThis.abilityContext as common.Context);
// 2. 获取相机列表
const cameras = this.cameraManager.getSupportedCameras();
if (cameras.length === 0) {
console.error('No camera found');
return;
}
// 3. 选择后置摄像头
const backCamera = cameras.find(camera => camera.position === camera.CameraPosition.CAMERA_POSITION_BACK) || cameras[0];
// 4. 创建相机输入
this.cameraInput = this.cameraManager.createCameraInput(backCamera);
await this.cameraInput.open();
// 5. 创建相机会话
const cameraObj = await this.createCameraSession();
this.cameraObj = cameraObj;
// 6. 开始预览
await this.startPreview();
} catch (error) {
console.error('Camera initialization failed:', error);
}
}
// 创建相机会话
async createCameraSession(): Promise<camera.CameraDevice> {
if (!this.cameraManager || !this.cameraInput) {
throw new Error('Camera not initialized');
}
// 1. 获取相机输出能力
const cameraOutputCap = this.cameraManager.getSupportedOutputCapability(
this.cameraInput.cameraDevice,
camera.SceneMode.NORMAL_PHOTO
);
// 2. 获取预览配置
const previewProfiles = cameraOutputCap.previewProfiles;
if (previewProfiles.length === 0) {
throw new Error('No preview profile available');
}
// 3. 创建预览输出
this.previewOutput = this.cameraManager.createPreviewOutput(
previewProfiles[0],
this.previewSurfaceId
);
// 4. 获取拍照配置
const photoProfiles = cameraOutputCap.photoProfiles;
if (photoProfiles.length > 0) {
this.photoOutput = this.cameraManager.createPhotoOutput(photoProfiles[0]);
}
// 5. 创建相机会话
const session = this.cameraManager.createSession(camera.SceneMode.NORMAL_PHOTO);
// 6. 配置会话
session.beginConfig();
session.addInput(this.cameraInput);
session.addOutput(this.previewOutput);
if (this.photoOutput) {
session.addOutput(this.photoOutput);
}
await session.commitConfig();
return this.cameraInput.cameraDevice;
}
// 开始预览
async startPreview() {
if (!this.cameraObj) {
return;
}
const session = this.cameraObj.session;
await session.start();
this.isPreviewing = true;
console.log('Camera preview started');
}
// 拍照
async takePhoto() {
if (!this.photoOutput) {
console.error('Photo output not available');
return;
}
try {
const photoSettings: camera.PhotoSettings = {
rotation: camera.ImageRotation.ROTATION_0,
quality: camera.QualityLevel.QUALITY_LEVEL_HIGH
};
// 设置拍照回调
this.photoOutput.on('photoAvailable', async (err: BusinessError, photo: camera.Photo) => {
if (err || !photo) {
console.error('Photo capture failed:', err);
return;
}
// 保存照片
await this.savePhoto(photo);
});
// 触发拍照
await this.photoOutput.capture(photoSettings);
} catch (error) {
console.error('Take photo failed:', error);
}
}
// 保存照片
async savePhoto(photo: camera.Photo) {
try {
// 创建保存路径
const context = globalThis.abilityContext as common.Context;
const filesDir = context.filesDir;
const timestamp = new Date().getTime();
const photoPath = `${filesDir}/photo_${timestamp}.jpg`;
// 获取照片数据
const photoBuffer = await photo.main;
const imageBuffer = await photoBuffer.getComponent(image.ComponentType.JPEG);
// 创建文件并写入
const file = await fs.open(photoPath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
await fs.write(file.fd, imageBuffer.byteArray);
await fs.close(file.fd);
// 更新路径用于显示
this.capturePath = photoPath;
console.log('Photo saved at:', photoPath);
} catch (error) {
console.error('Save photo failed:', error);
}
}
// 释放相机资源
releaseCamera() {
if (this.cameraObj) {
const session = this.cameraObj.session;
session.stop();
session.release();
}
if (this.cameraInput) {
this.cameraInput.close();
}
this.isPreviewing = false;
}
build() {
Column({ space: 20 }) {
// 相机预览区域
Stack() {
// XComponent用于相机预览
XComponent({
id: 'cameraPreview',
type: 'surface',
controller: new XComponentController()
})
.width('100%')
.height('70%')
.onLoad((xComponentContext?: XComponentContext) => {
if (xComponentContext) {
// 获取Surface ID用于相机预览
this.previewSurfaceId = xComponentContext.getXComponentSurfaceId();
console.log('Surface ID:', this.previewSurfaceId);
}
})
// 拍照按钮
if (this.isPreviewing) {
Column({ space: 10 }) {
// 拍照按钮
Button('拍照')
.width(80)
.height(80)
.backgroundColor(Color.White)
.borderRadius(40)
.border({ width: 4, color: Color.Gray })
.onClick(() => {
this.takePhoto();
})
// 显示拍摄的照片
if (this.capturePath) {
Image(this.capturePath)
.width(60)
.height(60)
.borderRadius(8)
.margin({ top: 10 })
}
}
.position({ x: '50%', y: '90%' })
.translate({ x: -40, y: -40 })
}
}
.width('100%')
.height('70%')
.backgroundColor(Color.Black)
// 控制区域
Row({ space: 30 }) {
Button(this.isPreviewing ? '停止预览' : '开始预览')
.width('40%')
.onClick(() => {
if (this.isPreviewing) {
this.releaseCamera();
} else {
this.startPreview();
}
})
Button('切换摄像头')
.width('40%')
.enabled(this.isPreviewing)
.onClick(() => {
this.switchCamera();
})
}
.width('100%')
.padding(20)
.justifyContent(FlexAlign.Center)
// 状态显示
Text(this.isPreviewing ? '相机预览中...' : '相机未启动')
.fontSize(16)
.fontColor(Color.Gray)
}
.width('100%')
.height('100%')
.padding(10)
}
// 切换摄像头(示例方法)
async switchCamera() {
// 停止当前预览
this.releaseCamera();
// 这里可以添加切换摄像头的逻辑
// 需要重新初始化相机,选择不同的摄像头
setTimeout(() => {
this.initCamera();
}, 100);
}
}
3. 预览组件优化版本
c
// 更简洁的相机预览组件
@Component
struct CameraView {
@State surfaceId: string = '';
@Link isPreviewing: boolean;
private xComponentController: XComponentController = new XComponentController();
build() {
XComponent({
id: 'camera',
type: 'surface',
controller: this.xComponentController
})
.width('100%')
.height('100%')
.onLoad((xComponentContext?: XComponentContext) => {
if (xComponentContext) {
this.surfaceId = xComponentContext.getXComponentSurfaceId();
console.log('Camera surface loaded, ID:', this.surfaceId);
}
})
}
getSurfaceId(): string {
return this.surfaceId;
}
}
4. 使用示例
// 在应用中使用
c
@Entry
@Component
struct MainPage {
@State showCamera: boolean = false;
build() {
Column() {
if (this.showCamera) {
CameraPreviewPage()
} else {
Button('打开相机')
.onClick(() => {
this.showCamera = true;
})
}
}
}
}
注意事项
- 权限管理:确保在应用首次启动时申请相机权限
- 资源释放:页面销毁时务必释放相机资源
- Surface生命周期:确保XComponent加载完成后再初始化相机
- 错误处理:添加适当的错误处理机制
- 性能优化:预览分辨率不宜设置过高,避免性能问题