【在鸿蒙系统中实现拍照预览功能】

在鸿蒙系统中实现拍照预览功能

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;
          })
      }
    }
  }
}

注意事项

  1. 权限管理:确保在应用首次启动时申请相机权限
  2. 资源释放:页面销毁时务必释放相机资源
  3. Surface生命周期:确保XComponent加载完成后再初始化相机
  4. 错误处理:添加适当的错误处理机制
  5. 性能优化:预览分辨率不宜设置过高,避免性能问题
相关推荐
萌虎不虎4 小时前
【鸿蒙实现显示屏测试实现方法】
华为·harmonyos
用户5951433221776 小时前
HarmonyOS应用开发之滚动容器Scroll
harmonyos
用户5951433221776 小时前
HarmonyOS应用开发之瀑布流、上拉加载、无限滚动一文搞定
harmonyos
用户5951433221777 小时前
鸿蒙应用开发之@Builder自定义构建函数:值传递与引用传递与UI更新
harmonyos
不爱吃糖的程序媛8 小时前
Flutter 开发的鸿蒙AtomGit OAuth 授权应用
华为·harmonyos
xq952713 小时前
编程之路 2025年终总结 ,勇往直前 再战江湖
harmonyos
不爱吃糖的程序媛14 小时前
鸿蒙PC命令行开发 macOS 上解决 pkg-config 命令未安装的问题
macos·华为·harmonyos
二流小码农16 小时前
鸿蒙开发:自定义一个圆形动画菜单
android·ios·harmonyos
yumgpkpm16 小时前
Cloudera CDP7、CDH5、CDH6 在华为鲲鹏 ARM 麒麟KylinOS做到无缝切换平缓迁移过程
大数据·arm开发·华为·flink·spark·kafka·cloudera