鸿蒙自定义相机的拍照页面

发布于:2025-07-03 ⋅ 阅读:(26) ⋅ 点赞:(0)

1、权限申请

    "requestPermissions": [
      {
        "name": "ohos.permission.CAMERA",
        "reason": "$string:reason_camera",
        "usedScene": {
          "abilities": [
            "EntryAbility"
          ]
        }
      },
      {
        "name": "ohos.permission.MEDIA_LOCATION",
        "reason": "$string:reason_media_location",
        "usedScene": {
          "abilities": [
            "EntryAbility"
          ]
        }
      }
    ]

2、ui实现

import { dataSharePredicates } from '@kit.ArkData';
import { abilityAccessCtrl, Permissions } from '@kit.AbilityKit';
import { camera } from '@kit.CameraKit';
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { filePreview } from '@kit.PreviewKit';
import { cameraShooting, capture, getUriAsync, previewPhoto, setPhotoFlashMode } from '../utils/CameraShooter';
import display from '@ohos.display';
import { AppStorageV2, curves } from '@kit.ArkUI';
import { sensor } from '@kit.SensorServiceKit';
import { DegreeConstants } from '../constants/DegreeConstants'

let cameraPosition = 0;
let surfaceId = '';
let context = getContext(this);

@Entry
@ComponentV2
struct XComponentPage {
  mXComponentController: XComponentController = new XComponentController;
  permissions: Array<Permissions> = [
    'ohos.permission.CAMERA',
    'ohos.permission.MEDIA_LOCATION',
  ];
  @Local cameraMargin: number = 40
  @Local cameraHeight: number = 2560
  @Local isRatio: boolean = true; // true: 16 / 9
  @Local isFront: boolean = false;
  @Local photoUri: string | Resource | PixelMap = ''
  @Local isStabilization: boolean = false;
  @Local isMovingPhoto: boolean = false;
  @Local flashPic: ResourceStr = ''
  textTimerController: TextTimerController = new TextTimerController();
  @Local rotation: number = 0;
  @Local moreTools: ResourceStr[] = ['相册', '拍照', '拍视频']

  @Builder
  bottomKeystrokeBuilder() {
    // 底部拍照翻转相机
    Row() {
      Image(this.photoUri)
        .borderWidth(this.photoUri === '' ? 0 : 1)
        .borderColor(Color.White)
        .height(70)
        .width(70)
        .borderRadius(35)
        .rotate({ angle: this.rotation })
        .animation({ curve: curves.springMotion() })
        .onClick(() => {
          if (this.photoUri !== '') {
            previewPhoto(context);
          }
        })
      // 拍照
      Column() {
        Column() {

        }.width(60).height(60).backgroundColor('#0FD7B8').borderRadius(30)
      }
      .width(72)
      .height(72)
      .border({ width: 3, color: Color.White })
      .borderRadius(36)
      .justifyContent(FlexAlign.Center)
      .alignItems(HorizontalAlign.Center)
      .onClick(() => {
        capture(this.isFront);
        getUriAsync().then(photoUri => {
          // photoUri 就是你要的图片uri
          // 可以赋值给 @State/@Local 变量,UI自动刷新
          this.photoUri = photoUri;
          console.log('hlasdlkas===' + this.photoUri)
        });
      })

      Column() {
        Text('翻转').width(28)
          .onClick(async () => {
            cameraPosition = cameraPosition === 1 ? 0 : 1
            cameraShooting(cameraPosition, surfaceId, context, this.isRatio);
            this.Initialize();
            this.isFront = cameraPosition !== 0;
          })
      }
      .height(70)
      .width(70)
      .justifyContent(FlexAlign.Center)
      .alignItems(HorizontalAlign.Center)
    }
    .margin({ top: 20 })
    .width('100%')
    .justifyContent(FlexAlign.SpaceAround).margin({ bottom: 39 })
  }

  @Builder
  proportionBuilder() {

    Row({ space: 20 }) {
      Text('1:1')
        .height(30)
        .onClick(() => {

          this.cameraMargin = 100
          this.cameraHeight = 1440
          this.isRatio = false
          cameraShooting(cameraPosition, surfaceId, context, this.isRatio);
          this.Initialize();
        })

      Text('9:16')
        .height(30)
        .onClick(() => {
          this.cameraHeight = 2560
          this.cameraMargin = 40
          this.isRatio = true
          cameraShooting(cameraPosition, surfaceId, context, this.isRatio);
          this.Initialize();
        })
    }
    .margin({ top: 500 })
  }

  onPageShow(): void {
    filePreview.closePreview(context);
  }

  async aboutToAppear() {
    sensor.on(sensor.SensorId.GRAVITY, (data: sensor.GravityResponse) => {
      let degree: number = -1;
      degree = this.getCalDegree(data.x, data.y, data.z);
      if (degree >= 0 && (degree <= DegreeConstants.DEGREE_ONE || degree >= DegreeConstants.DEGREE_FOUR)) {
        this.rotation = camera.ImageRotation.ROTATION_0;
      } else if (degree >= DegreeConstants.DEGREE_ONE && degree <= DegreeConstants.DEGREE_TWO) {
        this.rotation = camera.ImageRotation.ROTATION_270;
      } else if (degree >= DegreeConstants.DEGREE_TWO && degree <= DegreeConstants.DEGREE_THREE) {
        this.rotation = camera.ImageRotation.ROTATION_180;
      } else if (degree >= DegreeConstants.DEGREE_THREE && degree <= DegreeConstants.DEGREE_FOUR) {
        this.rotation = camera.ImageRotation.ROTATION_90;
      }
    })

    abilityAccessCtrl.createAtManager().requestPermissionsFromUser(context, this.permissions).then(() => {
      setTimeout(async () => {
        await cameraShooting(cameraPosition, surfaceId, context, this.isRatio);
      }, 200);
    });
  }

  aboutToDisappear(): void {
    sensor.off(sensor.SensorId.GRAVITY);
  }

  getCalDegree(x: number, y: number, z: number): number {
    let degree: number = -1;
    if ((x * x + y * y) * 3 < z * z) {
      return degree;
    }
    degree = 90 - (Number)(Math.round(Math.atan2(y, -x) / Math.PI * 180));
    return degree >= 0 ? degree % 360 : degree % 360 + 360;
  }

  build() {
    Stack({ alignContent: Alignment.Top }) {
      XComponent({ type: XComponentType.SURFACE, controller: this.mXComponentController })
        .onLoad(async () => {
          // todo:切换比例,暂时用不到
          if (this.isRatio === true) {
            this.mXComponentController.setXComponentSurfaceRect({
              surfaceWidth: display.getDefaultDisplaySync().width,
              surfaceHeight: display.getDefaultDisplaySync().width * 16 / 9,
              offsetY: 0
            });
            surfaceId = this.mXComponentController.getXComponentSurfaceId();
          } else {
            this.mXComponentController.setXComponentSurfaceRect({
              surfaceWidth: display.getDefaultDisplaySync().width,
              surfaceHeight: display.getDefaultDisplaySync().width,
            });
            surfaceId = this.mXComponentController.getXComponentSurfaceId();
          }
        })
        .width(px2vp(1440))
        .height(px2vp(this.cameraHeight))
        .align(Alignment.Top)
      Column() {
        Column() {
          Row() {
            Text('x').width(24)
            Text('拍照').fontWeight(600)
            Text().width(24)
          }
          .width('100%').justifyContent(FlexAlign.SpaceBetween)

          //比例
          this.proportionBuilder()
          // 底部按键
          this.bottomKeystrokeBuilder()
        }
        .width('100%')
        .height(px2vp(display.getDefaultDisplaySync().width * 16 / 9) - 10)
        .justifyContent(FlexAlign.SpaceBetween)

        // 更多
        Row() {
          ForEach(this.moreTools, (item: ResourceStr, index: number) => {
            Text(item)
          })
        }
        .width('70%')
        .layoutWeight(1)
        .justifyContent(FlexAlign.SpaceBetween)
        .alignItems(VerticalAlign.Center)

      }
      .height('100%')
      .align(Alignment.Top)
      .padding({ left: 14, right: 14, top: 10 })
    }
    .height('100%')
    .width('100%')
    .backgroundColor(Color.White)
    .padding({ top: 50 }) //顶部安全区

  }

  Initialize(): void {
    this.isStabilization = false;
    this.isMovingPhoto = false;
    if (this.isRatio === true) {
      this.mXComponentController.setXComponentSurfaceRect({
        surfaceWidth: display.getDefaultDisplaySync().width,
        surfaceHeight: display.getDefaultDisplaySync().width * 16 / 9, offsetY: 0
      })
    } else {
      this.mXComponentController.setXComponentSurfaceRect({
        surfaceWidth: display.getDefaultDisplaySync().width,
        surfaceHeight: display.getDefaultDisplaySync().width
      })
    }
  }

  switchFlash(flashMode: number): void {
    setPhotoFlashMode(flashMode);
  }

  async getThumbnail(): Promise<void> {
    let predicates: dataSharePredicates.DataSharePredicates = new dataSharePredicates.DataSharePredicates();
    predicates.orderByDesc(photoAccessHelper.PhotoKeys.DATE_ADDED);
    let fetchOptions: photoAccessHelper.FetchOptions = {
      fetchColumns: [],
      predicates: predicates
    };
    let photoHelper = photoAccessHelper.getPhotoAccessHelper(context);
    let fetchResult: photoAccessHelper.FetchResult<photoAccessHelper.PhotoAsset> =
      await photoHelper.getAssets(fetchOptions);
    if (fetchResult !== undefined) {
      let photoAsset: photoAccessHelper.PhotoAsset = await fetchResult.getFirstObject();
      this.photoUri = await photoAsset.getThumbnail();
    }
  }
}



3、工具

import { camera } from '@kit.CameraKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { common } from '@kit.AbilityKit';
import { AppStorageV2, display } from '@kit.ArkUI';
import { colorSpaceManager } from '@kit.ArkGraphics2D';

let previewOutput: camera.PreviewOutput;
let cameraInput: camera.CameraInput;
let photoSession: camera.PhotoSession;
let photoOutPut: camera.PhotoOutput;
let currentContext: Context;
let uri: string;
let uriWaiters: ((uri: string) => void)[] = [];

export async function cameraShooting(cameraPosition: number, surfaceId: string, context: Context, ratio: boolean):
  Promise<number[]> {
  currentContext = context;
  releaseCamera();
  let cameraManager: camera.CameraManager = camera.getCameraManager(context);
  if (!cameraManager) {
    return [];
  }
  let cameraArray: camera.CameraDevice[] = cameraManager.getSupportedCameras();
  if (cameraArray.length <= 0) {
    return [];
  }
  cameraInput = cameraManager.createCameraInput(cameraArray[cameraPosition]);
  await cameraInput.open();
  let sceneModes: camera.SceneMode[] = cameraManager.getSupportedSceneModes(cameraArray[cameraPosition]);
  let cameraOutputCap: camera.CameraOutputCapability =
    cameraManager.getSupportedOutputCapability(cameraArray[cameraPosition], camera.SceneMode.NORMAL_PHOTO);
  let isSupportPhotoMode: boolean = sceneModes.indexOf(camera.SceneMode.NORMAL_PHOTO) >= 0;
  if (!isSupportPhotoMode) {
    return [];
  }
  if (!cameraOutputCap) {
    return [];
  }
  let previewProfilesArray: camera.Profile[] = cameraOutputCap.previewProfiles;
  let photoProfilesArray: camera.Profile[] = cameraOutputCap.photoProfiles;
  let previewProfile: undefined | camera.Profile = previewProfilesArray.find((profile: camera.Profile) => {
    let screen = display.getDefaultDisplaySync();
    if (screen.width <= 1080) {
      if (ratio === true) {
        return profile.size.height === 1080 && profile.size.width === 1920;
      } else {
        return profile.size.height === 1080 && profile.size.width === 1080;
      }
    } else {
      if (ratio === true) {
        return profile.size.height === 1440 && profile.size.width === 2560;
      } else {
        return profile.size.height === 1440 && profile.size.width === 1440;
      }
    }
  });
  let photoProfile: undefined | camera.Profile = photoProfilesArray.find((profile: camera.Profile) => {
    if (previewProfile) {
      return profile.size.width <= 4096 && profile.size.width >= 2448;
    }
    return undefined;
  });
  previewOutput = cameraManager.createPreviewOutput(previewProfile, surfaceId);
  if (previewOutput === undefined) {
    return [];
  }
  photoOutPut = cameraManager.createPhotoOutput(photoProfile);
  if (photoOutPut === undefined) {
    return [];
  }
  // Save Picture
  setPhotoOutputCb(photoOutPut);

  photoSession = cameraManager.createSession(camera.SceneMode.NORMAL_PHOTO) as camera.PhotoSession;
  if (photoSession === undefined) {
    return [];
  }
  photoSession.beginConfig();
  photoSession.addInput(cameraInput);
  photoSession.addOutput(previewOutput);
  photoSession.addOutput(photoOutPut);
  photoSession.setColorSpace(colorSpaceManager.ColorSpace.DISPLAY_P3);
  await photoSession.commitConfig();
  await photoSession.start();
  let flashStatus: boolean = photoSession.hasFlash();
  if (flashStatus) {
    photoSession.setFlashMode(camera.FlashMode.FLASH_MODE_CLOSE);
  }
  let focusModeStatus: boolean = photoSession.isFocusModeSupported(camera.FocusMode.FOCUS_MODE_CONTINUOUS_AUTO);
  if (focusModeStatus) {
    photoSession.setFocusMode(camera.FocusMode.FOCUS_MODE_CONTINUOUS_AUTO);
  }
  let zoomRatioRange = photoSession.getZoomRatioRange();
  return zoomRatioRange;
}

export function capture(isFront: boolean): void {
  let settings: camera.PhotoCaptureSetting = {
    quality: camera.QualityLevel.QUALITY_LEVEL_HIGH,
    rotation: camera.ImageRotation.ROTATION_0,
    mirror: isFront
  };
  photoOutPut.capture(settings);
}

export async function setPhotoFlashMode(flashMode: number): Promise<void> {
  photoSession.setFlashMode(flashMode);
}

export async function releaseCamera(): Promise<void> {
  if (photoSession) {
    photoSession.stop();
  }
  if (cameraInput) {
    cameraInput.close();
  }
  if (previewOutput) {
    previewOutput.release();
  }
  if (photoSession) {
    photoSession.release();
  }
  if (photoOutPut) {
    photoOutPut.release();
  }
}


function setPhotoOutputCb(photoOutput: camera.PhotoOutput): void {
  photoOutput.on('photoAssetAvailable',
    async (_err: BusinessError, photoAsset: photoAccessHelper.PhotoAsset): Promise<void> => {
      let accessHelper: photoAccessHelper.PhotoAccessHelper =
        photoAccessHelper.getPhotoAccessHelper(currentContext);
      let assetChangeRequest: photoAccessHelper.MediaAssetChangeRequest =
        new photoAccessHelper.MediaAssetChangeRequest(photoAsset);
      assetChangeRequest.saveCameraPhoto();
      await accessHelper.applyChanges(assetChangeRequest);
      uri = photoAsset.uri;
      // AppStorage.setOrCreate('photoUri', await photoAsset.getThumbnail());
      uriWaiters.forEach(fn => fn(uri));
      uriWaiters = [];
    });
}

export function getUriAsync(): Promise<string> {
  return new Promise(resolve => {
    uriWaiters.push(resolve);
  });
}

export function previewPhoto(context: Context): void {
  let photoContext = context as common.UIAbilityContext;
  photoContext.startAbility({
    parameters: { uri: uri },
    action: 'ohos.want.action.viewData',
    bundleName: 'com.huawei.hmos.photos',
    abilityName: 'com.huawei.hmos.photos.MainAbility'
  })
}


网站公告

今日签到

点亮在社区的每一天
去签到