当前位置: 首页 > news >正文

鸿蒙媒体开发【相机数据采集保存】拍照和图片

相机数据采集保存

介绍

本示例主要展示了相机的相关功能 接口实现相机的预览拍照功能。

效果预览

1

使用说明

  1. 弹出是否允许“CameraSample”使用相机?点击“允许”
  2. 弹出是否允许“CameraSample”使用麦克风?点击“允许”
  3. 进入预览界面,预览正常,点击拍照按钮,跳转到图片预览页面,跳转正常,图片预览页面显示当前所拍照的图片,显示正常
  4. 进入预览界面,预览正常,点击拍照按钮,跳转到图片预览页面,跳转正常,图片预览页面显示当前所拍照的图片,显示正常,退出应用并进入图库应用,第一张图片显示为刚刚拍照的图片,拍照正常
  5. 点击图片预览页面的左上角返回按钮,重新进入预览页面,预览正常
  6. 进入预览界面,预览正常,滑动变焦条,变焦条上方显示变焦值,显示正常,并且预览界面随着变焦条的滑动放大或缩小,预览正常
  7. 进入预览界面,预览正常,点击预览显示区域进行对焦,对焦正常
  8. 进入预览界面,预览正常,点击“拍照”旁边的“录像”切换到录像模式,预览正常,点击录像按钮,开始录像,录像正常,点击停止录像按钮,跳转到录像预览界面,跳转正常,点击视频播放按钮,播放正常

具体实现

  • 相机功能接口实现在CameraService.ets,源码参考:[CameraService.ets]
/** Copyright (c) 2024 Huawei Device Co., Ltd.* Licensed under the Apache License, Version 2.0 (the 'License');* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**     http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an 'AS IS' BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { camera } from '@kit.CameraKit';
import { media } from '@kit.MediaKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { JSON } from '@kit.ArkTS';
import { fileIo } from '@kit.CoreFileKit';
import { GlobalContext } from '../common/utils/GlobalContext';
import Logger from '../common/utils/Logger';
import { Constants } from '../common/Constants';const TAG: string = 'CameraService';export class SliderValue {min: number = 1;max: number = 6;step: number = 0.1;
}class CameraService {private cameraManager: camera.CameraManager | undefined = undefined;private cameras: Array<camera.CameraDevice> | Array<camera.CameraDevice> = [];private cameraInput: camera.CameraInput | undefined = undefined;private previewOutput: camera.PreviewOutput | undefined = undefined;private photoOutput: camera.PhotoOutput | undefined = undefined;private videoOutput: camera.VideoOutput | undefined = undefined;private avRecorder: media.AVRecorder | undefined = undefined;private session: camera.PhotoSession | camera.VideoSession | undefined = undefined;private handlePhotoAssetCb: (photoAsset: photoAccessHelper.PhotoAsset) => void = () => {};private curCameraDevice: camera.CameraDevice | undefined = undefined;private isRecording: boolean = false;// 推荐拍照分辨率之一private photoProfileObj: camera.Profile = {format: 2000,size: {width: 1920,height: 1080}};// 推荐预览分辨率之一private previewProfileObj: camera.Profile = {format: 1003,size: {width: 1920,height: 1080}};// 推荐录像分辨率之一private videoProfileObj: camera.VideoProfile = {format: 1003,size: {width: 1920,height: 1080},frameRateRange: {min: 30,max: 60}};private curSceneMode: camera.SceneMode = camera.SceneMode.NORMAL_PHOTO;constructor() {}setSavePictureCallback(callback: (photoAsset: photoAccessHelper.PhotoAsset) => void): void {this.handlePhotoAssetCb = callback;}setSceneMode(sceneMode: camera.SceneMode): void {this.curSceneMode = sceneMode;}getSceneMode(): camera.SceneMode {return this.curSceneMode;}getPreviewProfile(cameraOutputCapability: camera.CameraOutputCapability): camera.Profile | undefined {let previewProfiles = cameraOutputCapability.previewProfiles;if (previewProfiles.length < 1) {return undefined;}let index = previewProfiles.findIndex((previewProfile: camera.Profile) => {return previewProfile.size.width === this.previewProfileObj.size.width &&previewProfile.size.height === this.previewProfileObj.size.height &&previewProfile.format === this.previewProfileObj.format;});if (index === -1) {return undefined;}return previewProfiles[index];}getPhotoProfile(cameraOutputCapability: camera.CameraOutputCapability): camera.Profile | undefined {let photoProfiles = cameraOutputCapability.photoProfiles;if (photoProfiles.length < 1) {return undefined;}let index = photoProfiles.findIndex((photoProfile: camera.Profile) => {return photoProfile.size.width === this.photoProfileObj.size.width &&photoProfile.size.height === this.photoProfileObj.size.height &&photoProfile.format === this.photoProfileObj.format;});if (index === -1) {return undefined;}return photoProfiles[index];}getVideoProfile(cameraOutputCapability: camera.CameraOutputCapability): camera.VideoProfile | undefined {let videoProfiles = cameraOutputCapability.videoProfiles;if (videoProfiles.length < 1) {return undefined;}for (let i = 0; i < videoProfiles.length; i++) {Logger.info(TAG, `getVideoProfile: ${JSON.stringify(videoProfiles[i])}`);}let index = videoProfiles.findIndex((videoProfile: camera.VideoProfile) => {return videoProfile.size.width === this.videoProfileObj.size.width &&videoProfile.size.height === this.videoProfileObj.size.height &&videoProfile.format === this.videoProfileObj.format &&videoProfile.frameRateRange.min <= Constants.MAX_VIDEO_FRAME &&videoProfile.frameRateRange.max <= Constants.MAX_VIDEO_FRAME;});if (index === -1) {return undefined;}return videoProfiles[index];}isSupportedSceneMode(cameraManager: camera.CameraManager, cameraDevice: camera.CameraDevice): boolean {let sceneModes = cameraManager.getSupportedSceneModes(cameraDevice);if (sceneModes === undefined) {return false;}let index = sceneModes.findIndex((sceneMode: camera.SceneMode) => {return sceneMode === this.curSceneMode;});if (index === -1) {return false;}return true;}/*** 初始化相机功能* @param surfaceId - Surface 的 ID* @param cameraDeviceIndex - 相机设备索引* @returns 无返回值*/async initCamera(surfaceId: string, cameraDeviceIndex: number): Promise<void> {Logger.debug(TAG, `initCamera cameraDeviceIndex: ${cameraDeviceIndex}`);try {await this.releaseCamera();// 获取相机管理器实例this.cameraManager = this.getCameraManagerFn();if (this.cameraManager === undefined) {Logger.error(TAG, 'cameraManager is undefined');return;}// 获取支持指定的相机设备对象this.cameras = this.getSupportedCamerasFn(this.cameraManager);if (this.cameras.length < 1 || this.cameras.length < cameraDeviceIndex + 1) {return;}this.curCameraDevice = this.cameras[cameraDeviceIndex];let isSupported = this.isSupportedSceneMode(this.cameraManager, this.curCameraDevice);if (!isSupported) {Logger.error(TAG, 'The current scene mode is not supported.');return;}let cameraOutputCapability =this.cameraManager.getSupportedOutputCapability(this.curCameraDevice, this.curSceneMode);let previewProfile = this.getPreviewProfile(cameraOutputCapability);if (previewProfile === undefined) {Logger.error(TAG, 'The resolution of the current preview stream is not supported.');return;}this.previewProfileObj = previewProfile;// 创建previewOutput输出对象this.previewOutput = this.createPreviewOutputFn(this.cameraManager, this.previewProfileObj, surfaceId);if (this.previewOutput === undefined) {Logger.error(TAG, 'Failed to create the preview stream.');return;}// 监听预览事件this.previewOutputCallBack(this.previewOutput);if (this.curSceneMode === camera.SceneMode.NORMAL_PHOTO) {let photoProfile = this.getPhotoProfile(cameraOutputCapability);if (photoProfile === undefined) {Logger.error(TAG, 'The resolution of the current photo stream is not supported.');return;}this.photoProfileObj = photoProfile;// 创建photoOutPut输出对象this.photoOutput = this.createPhotoOutputFn(this.cameraManager, this.photoProfileObj);if (this.photoOutput === undefined) {Logger.error(TAG, 'Failed to create the photo stream.');return;}} else if (this.curSceneMode === camera.SceneMode.NORMAL_VIDEO) {let videoProfile = this.getVideoProfile(cameraOutputCapability);if (videoProfile === undefined) {Logger.error(TAG, 'The resolution of the current video stream is not supported.');return;}this.videoProfileObj = videoProfile;this.avRecorder = await this.createAVRecorder();if (this.avRecorder === undefined) {Logger.error(TAG, 'Failed to create the avRecorder.');return;}await this.prepareAVRecorder();let videoSurfaceId = await this.avRecorder.getInputSurface();// 创建videoOutPut输出对象this.videoOutput = this.createVideoOutputFn(this.cameraManager, this.videoProfileObj, videoSurfaceId);if (this.videoOutput === undefined) {Logger.error(TAG, 'Failed to create the video stream.');return;}}// 创建cameraInput输出对象this.cameraInput = this.createCameraInputFn(this.cameraManager, this.curCameraDevice);if (this.cameraInput === undefined) {Logger.error(TAG, 'Failed to create the camera input.');return;}// 打开相机let isOpenSuccess = await this.cameraInputOpenFn(this.cameraInput);if (!isOpenSuccess) {Logger.error(TAG, 'Failed to open the camera.');return;}// 镜头状态回调this.onCameraStatusChange(this.cameraManager);// 监听CameraInput的错误事件this.onCameraInputChange(this.cameraInput, this.curCameraDevice);// 会话流程await this.sessionFlowFn(this.cameraManager, this.cameraInput, this.previewOutput, this.photoOutput,this.videoOutput);} catch (error) {let err = error as BusinessError;Logger.error(TAG, `initCamera fail: ${JSON.stringify(err)}`);}}/*** 获取可变焦距范围*/getZoomRatioRange(): Array<number> {let zoomRatioRange: Array<number> = [];if (this.session !== undefined) {zoomRatioRange = this.session.getZoomRatioRange();}return zoomRatioRange;}/*** 变焦*/setZoomRatioFn(zoomRatio: number): void {Logger.info(TAG, `setZoomRatioFn value ${zoomRatio}`);// 获取支持的变焦范围try {let zoomRatioRange = this.getZoomRatioRange();Logger.info(TAG, `getZoomRatioRange success: ${JSON.stringify(zoomRatioRange)}`);} catch (error) {let err = error as BusinessError;Logger.error(TAG, `getZoomRatioRange fail: ${JSON.stringify(err)}`);}try {this.session?.setZoomRatio(zoomRatio);Logger.info(TAG, 'setZoomRatioFn success');} catch (error) {let err = error as BusinessError;Logger.error(TAG, `setZoomRatioFn fail: ${JSON.stringify(err)}`);}}/*** 以指定参数触发一次拍照*/async takePicture(): Promise<void> {Logger.info(TAG, 'takePicture start');let cameraDeviceIndex = GlobalContext.get().getT<number>('cameraDeviceIndex');let photoSettings: camera.PhotoCaptureSetting = {quality: camera.QualityLevel.QUALITY_LEVEL_HIGH,mirror: cameraDeviceIndex ? true : false};await this.photoOutput?.capture(photoSettings);Logger.info(TAG, 'takePicture end');}/*** 释放会话及其相关参数*/async releaseCamera(): Promise<void> {Logger.info(TAG, 'releaseCamera is called');try {await this.previewOutput?.release();} catch (error) {let err = error as BusinessError;Logger.error(TAG, `previewOutput release fail: error: ${JSON.stringify(err)}`);} finally {this.previewOutput = undefined;}try {await this.photoOutput?.release();} catch (error) {let err = error as BusinessError;Logger.error(TAG, `photoOutput release fail: error: ${JSON.stringify(err)}`);} finally {this.photoOutput = undefined;}try {await this.avRecorder?.release();} catch (error) {let err = error as BusinessError;Logger.error(TAG, `avRecorder release fail: error: ${JSON.stringify(err)}`);} finally {this.avRecorder = undefined;}try {await this.videoOutput?.release();} catch (error) {let err = error as BusinessError;Logger.error(TAG, `videoOutput release fail: error: ${JSON.stringify(err)}`);} finally {this.videoOutput = undefined;}try {await this.session?.release();} catch (error) {let err = error as BusinessError;Logger.error(TAG, `captureSession release fail: error: ${JSON.stringify(err)}`);} finally {this.session = undefined;}try {await this.cameraInput?.close();} catch (error) {let err = error as BusinessError;Logger.error(TAG, `cameraInput close fail: error: ${JSON.stringify(err)}`);} finally {this.cameraInput = undefined;}this.offCameraStatusChange();Logger.info(TAG, 'releaseCamera success');}/*** 获取相机管理器实例*/getCameraManagerFn(): camera.CameraManager | undefined {if (this.cameraManager) {return this.cameraManager;}let cameraManager: camera.CameraManager | undefined = undefined;try {cameraManager = camera.getCameraManager(GlobalContext.get().getCameraSettingContext());Logger.info(TAG, `getCameraManager success: ${cameraManager}`);} catch (error) {let err = error as BusinessError;Logger.error(TAG, `getCameraManager failed: ${JSON.stringify(err)}`);}return cameraManager;}/*** 获取支持指定的相机设备对象*/getSupportedCamerasFn(cameraManager: camera.CameraManager): Array<camera.CameraDevice> {let supportedCameras: Array<camera.CameraDevice> = [];try {supportedCameras = cameraManager.getSupportedCameras();Logger.info(TAG, `getSupportedCameras success: ${this.cameras}, length: ${this.cameras?.length}`);} catch (error) {let err = error as BusinessError;Logger.error(TAG, `getSupportedCameras failed: ${JSON.stringify(err)}`);}return supportedCameras;}/*** 创建previewOutput输出对象*/createPreviewOutputFn(cameraManager: camera.CameraManager, previewProfileObj: camera.Profile,surfaceId: string): camera.PreviewOutput | undefined {let previewOutput: camera.PreviewOutput | undefined = undefined;try {previewOutput = cameraManager.createPreviewOutput(previewProfileObj, surfaceId);Logger.info(TAG, `createPreviewOutput success: ${previewOutput}`);} catch (error) {let err = error as BusinessError;Logger.error(TAG, `createPreviewOutput failed: ${JSON.stringify(err)}`);}return previewOutput;}/*** 创建photoOutPut输出对象*/createPhotoOutputFn(cameraManager: camera.CameraManager,photoProfileObj: camera.Profile): camera.PhotoOutput | undefined {let photoOutput: camera.PhotoOutput | undefined = undefined;try {photoOutput = cameraManager.createPhotoOutput(photoProfileObj);Logger.info(TAG, `createPhotoOutputFn success: ${photoOutput}`);} catch (error) {let err = error as BusinessError;Logger.error(TAG, `createPhotoOutputFn failed: ${JSON.stringify(err)}`);}return photoOutput;}/*** 创建videoOutPut输出对象*/createVideoOutputFn(cameraManager: camera.CameraManager, videoProfileObj: camera.VideoProfile,surfaceId: string): camera.VideoOutput | undefined {let videoOutput: camera.VideoOutput | undefined = undefined;try {videoOutput = cameraManager.createVideoOutput(videoProfileObj, surfaceId);Logger.info(TAG, `createVideoOutputFn success: ${videoOutput}`);} catch (error) {let err = error as BusinessError;Logger.error(TAG, `createVideoOutputFn failed: ${JSON.stringify(err)}`);}return videoOutput;}/*** 创建cameraInput输出对象*/createCameraInputFn(cameraManager: camera.CameraManager,cameraDevice: camera.CameraDevice): camera.CameraInput | undefined {Logger.info(TAG, 'createCameraInputFn is called.');let cameraInput: camera.CameraInput | undefined = undefined;try {cameraInput = cameraManager.createCameraInput(cameraDevice);Logger.info(TAG, 'createCameraInputFn success');} catch (error) {let err = error as BusinessError;Logger.error(TAG, `createCameraInputFn failed: ${JSON.stringify(err)}`);}return cameraInput;}/*** 打开相机*/async cameraInputOpenFn(cameraInput: camera.CameraInput): Promise<boolean> {let isOpenSuccess = false;try {await cameraInput.open();isOpenSuccess = true;Logger.info(TAG, 'cameraInput open success');} catch (error) {let err = error as BusinessError;Logger.error(TAG, `createCameraInput failed : ${JSON.stringify(err)}`);}return isOpenSuccess;}/*** 会话流程*/async sessionFlowFn(cameraManager: camera.CameraManager, cameraInput: camera.CameraInput,previewOutput: camera.PreviewOutput, photoOutput: camera.PhotoOutput | undefined,videoOutput: camera.VideoOutput | undefined): Promise<void> {try {// 创建CaptureSession实例if (this.curSceneMode === camera.SceneMode.NORMAL_PHOTO) {this.session = cameraManager.createSession(this.curSceneMode) as camera.PhotoSession;} else if (this.curSceneMode === camera.SceneMode.NORMAL_VIDEO) {this.session = cameraManager.createSession(this.curSceneMode) as camera.VideoSession;}if (this.session === undefined) {return;}this.onSessionErrorChange(this.session);// 开始配置会话this.session.beginConfig();// 把CameraInput加入到会话this.session.addInput(cameraInput);// 把previewOutput加入到会话this.session.addOutput(previewOutput);if (this.curSceneMode === camera.SceneMode.NORMAL_PHOTO) {if (photoOutput === undefined) {return;}// 拍照监听事件this.photoOutputCallBack(photoOutput);// 把photoOutPut加入到会话this.session.addOutput(photoOutput);} else if (this.curSceneMode === camera.SceneMode.NORMAL_VIDEO) {if (videoOutput === undefined) {return;}// 把photoOutPut加入到会话this.session.addOutput(videoOutput);}// 提交配置信息await this.session.commitConfig();if (this.curSceneMode === camera.SceneMode.NORMAL_VIDEO) {this.setVideoStabilizationFn(this.session as camera.VideoSession, camera.VideoStabilizationMode.MIDDLE);}this.updateSliderValue();this.setFocusMode(camera.FocusMode.FOCUS_MODE_AUTO);// 开始会话工作await this.session.start();Logger.info(TAG, 'sessionFlowFn success');} catch (error) {let err = error as BusinessError;Logger.error(TAG, `sessionFlowFn fail : ${JSON.stringify(err)}`);}}setVideoStabilizationFn(session: camera.VideoSession, videoStabilizationMode: camera.VideoStabilizationMode): void {// 查询是否支持指定的视频防抖模式let isVideoStabilizationModeSupported: boolean = session.isVideoStabilizationModeSupported(videoStabilizationMode);if (isVideoStabilizationModeSupported) {session.setVideoStabilizationMode(videoStabilizationMode);}Logger.info(TAG, 'setVideoStabilizationFn success');}/*** 更新滑动条数据*/updateSliderValue(): void {let zoomRatioRange = this.getZoomRatioRange();if (zoomRatioRange.length !== 0) {let zoomRatioMin = zoomRatioRange[0];let zoomRatioMax = zoomRatioRange[1] > Constants.ZOOM_RADIO_MAX ? Constants.ZOOM_RADIO_MAX : zoomRatioRange[1];let sliderStep =zoomRatioRange[1] > Constants.ZOOM_RADIO_MAX ? Constants.ZOOM_RADIO_MAX_STEP : Constants.ZOOM_RADIO_MIN_STEP;AppStorage.set<SliderValue>('sliderValue', {min: zoomRatioMin,max: zoomRatioMax,step: sliderStep});}}/*** 监听拍照事件*/photoOutputCallBack(photoOutput: camera.PhotoOutput): void {try {// 监听拍照开始photoOutput.on('captureStartWithInfo', (err: BusinessError, captureStartInfo: camera.CaptureStartInfo): void => {Logger.info(TAG, `photoOutputCallBack captureStartWithInfo success: ${JSON.stringify(captureStartInfo)}`);});// 监听拍照帧输出捕获photoOutput.on('frameShutter', (err: BusinessError, frameShutterInfo: camera.FrameShutterInfo): void => {Logger.info(TAG, `photoOutputCallBack frameShutter captureId:${frameShutterInfo.captureId}, timestamp: ${frameShutterInfo.timestamp}`);});// 监听拍照结束photoOutput.on('captureEnd', (err: BusinessError, captureEndInfo: camera.CaptureEndInfo): void => {Logger.info(TAG, `photoOutputCallBack captureEnd captureId:${captureEndInfo.captureId}, frameCount: ${captureEndInfo.frameCount}`);});// 监听拍照异常photoOutput.on('error', (data: BusinessError): void => {Logger.info(TAG, `photoOutPut data: ${JSON.stringify(data)}`);});photoOutput.on('photoAssetAvailable', (err: BusinessError, photoAsset: photoAccessHelper.PhotoAsset) => {Logger.info(TAG, 'photoAssetAvailable begin');if (photoAsset === undefined) {Logger.error(TAG, 'photoAsset is undefined');return;}this.handlePhotoAssetCb(photoAsset);});} catch (err) {Logger.error(TAG, 'photoOutputCallBack error');}}/*** 监听预览事件*/previewOutputCallBack(previewOutput: camera.PreviewOutput): void {Logger.info(TAG, 'previewOutputCallBack is called');try {previewOutput.on('frameStart', (): void => {Logger.debug(TAG, 'Preview frame started');});previewOutput.on('frameEnd', (): void => {Logger.debug(TAG, 'Preview frame ended');});previewOutput.on('error', (previewOutputError: BusinessError): void => {Logger.info(TAG, `Preview output previewOutputError: ${JSON.stringify(previewOutputError)}`);});} catch (err) {Logger.error(TAG, 'previewOutputCallBack error');}}/*** 注册相机状态变化的回调函数* @param err - 错误信息(如果有)* @param cameraStatusInfo - 相机状态信息* @returns 无返回值*/registerCameraStatusChange(err: BusinessError, cameraStatusInfo: camera.CameraStatusInfo): void {Logger.info(TAG, `cameraId: ${cameraStatusInfo.camera.cameraId},status: ${cameraStatusInfo.status}`);}/*** 监听相机状态变化* @param cameraManager - 相机管理器对象* @returns 无返回值*/onCameraStatusChange(cameraManager: camera.CameraManager): void {Logger.info(TAG, 'onCameraStatusChange is called');try {cameraManager.on('cameraStatus', this.registerCameraStatusChange);} catch (error) {Logger.error(TAG, 'onCameraStatusChange error');}}/*** 停止监听相机状态变化* @returns 无返回值*/offCameraStatusChange(): void {Logger.info(TAG, 'offCameraStatusChange is called');this.cameraManager?.off('cameraStatus', this.registerCameraStatusChange);}/*** 监听相机输入变化* @param cameraInput - 相机输入对象* @param cameraDevice - 相机设备对象* @returns 无返回值*/onCameraInputChange(cameraInput: camera.CameraInput, cameraDevice: camera.CameraDevice): void {Logger.info(TAG, `onCameraInputChange is called`);try {cameraInput.on('error', cameraDevice, (cameraInputError: BusinessError): void => {Logger.info(TAG, `onCameraInputChange cameraInput error code: ${cameraInputError.code}`);});} catch (error) {Logger.error(TAG, 'onCameraInputChange error');}}/*** 监听捕获会话错误变化* @param session - 相机捕获会话对象* @returns 无返回值*/onSessionErrorChange(session: camera.PhotoSession | camera.VideoSession): void {try {session.on('error', (captureSessionError: BusinessError): void => {Logger.info(TAG,'onCaptureSessionErrorChange captureSession fail: ' + JSON.stringify(captureSessionError.code));});} catch (error) {Logger.error(TAG, 'onCaptureSessionErrorChange error');}}async createAVRecorder(): Promise<media.AVRecorder | undefined> {let avRecorder: media.AVRecorder | undefined = undefined;try {avRecorder = await media.createAVRecorder();} catch (error) {Logger.error(TAG, `createAVRecorder error: ${error}`);}return avRecorder;}initFd(): number {Logger.info(TAG, 'initFd is called');let filesDir = getContext().filesDir;let filePath = filesDir + `/${Date.now()}.mp4`;AppStorage.setOrCreate<string>('filePath', filePath);let file: fileIo.File = fileIo.openSync(filePath, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);return file.fd;}async prepareAVRecorder(): Promise<void> {Logger.info(TAG, 'prepareAVRecorder is called');let fd = this.initFd();let videoConfig: media.AVRecorderConfig = {audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC,videoSourceType: media.VideoSourceType.VIDEO_SOURCE_TYPE_SURFACE_YUV,profile: {audioBitrate: Constants.AUDIO_BITRATE,audioChannels: Constants.AUDIO_CHANNELS,audioCodec: media.CodecMimeType.AUDIO_AAC,audioSampleRate: Constants.AUDIO_SAMPLE_RATE,fileFormat: media.ContainerFormatType.CFT_MPEG_4,videoBitrate: Constants.VIDEO_BITRATE,videoCodec: media.CodecMimeType.VIDEO_AVC,videoFrameWidth: this.videoProfileObj.size.width,videoFrameHeight: this.videoProfileObj.size.height,videoFrameRate: this.videoProfileObj.frameRateRange.max},url: `fd://${fd.toString()}`,rotation: this.curCameraDevice?.cameraOrientation};Logger.info(TAG, `prepareAVRecorder videoConfig: ${JSON.stringify(videoConfig)}`);await this.avRecorder?.prepare(videoConfig).catch((err: BusinessError): void => {Logger.error(TAG, `prepareAVRecorder prepare err: ${JSON.stringify(err)}`);});}/*** 启动录制*/async startVideo(): Promise<void> {Logger.info(TAG, 'startVideo is called');try {await this.videoOutput?.start();await this.avRecorder?.start();this.isRecording = true;} catch (error) {let err = error as BusinessError;Logger.error(TAG, `startVideo err: ${JSON.stringify(err)}`);}Logger.info(TAG, 'startVideo End of call');}/*** 停止录制*/async stopVideo(): Promise<void> {Logger.info(TAG, 'stopVideo is called');if (!this.isRecording) {Logger.info(TAG, 'not in recording');return;}try {if (this.avRecorder) {await this.avRecorder.stop();}if (this.videoOutput) {await this.videoOutput.stop();}this.isRecording = false;} catch (error) {let err = error as BusinessError;Logger.error(TAG, `stopVideo err: ${JSON.stringify(err)}`);}Logger.info(TAG, 'stopVideo End of call');}/*** 闪关灯*/hasFlashFn(flashMode: camera.FlashMode): void {// 检测是否有闪关灯let hasFlash = this.session?.hasFlash();Logger.debug(TAG, `hasFlash success, hasFlash: ${hasFlash}`);// 检测闪光灯模式是否支持let isFlashModeSupported = this.session?.isFlashModeSupported(flashMode);Logger.debug(TAG, `isFlashModeSupported success, isFlashModeSupported: ${isFlashModeSupported}`);// 设置闪光灯模式this.session?.setFlashMode(flashMode);}/*** 焦点*/setFocusPoint(point: camera.Point): void {// 设置焦点this.session?.setFocusPoint(point);Logger.info(TAG, `setFocusPoint success point: ${JSON.stringify(point)}`);// 获取当前的焦点let nowPoint: camera.Point | undefined = undefined;nowPoint = this.session?.getFocusPoint();Logger.info(TAG, `getFocusPoint success, nowPoint: ${JSON.stringify(nowPoint)}`);}/*** 曝光区域*/isMeteringPoint(point: camera.Point): void {// 获取当前曝光模式let exposureMode: camera.ExposureMode | undefined = undefined;exposureMode = this.session?.getExposureMode();Logger.info(TAG, `getExposureMode success, exposureMode: ${exposureMode}`);this.session?.setMeteringPoint(point);let exposurePoint: camera.Point | undefined = undefined;exposurePoint = this.session?.getMeteringPoint();Logger.info(TAG, `getMeteringPoint exposurePoint: ${JSON.stringify(exposurePoint)}`);}/*** 曝光补偿*/isExposureBiasRange(exposureBias: number): void {Logger.debug(TAG, `setExposureBias value ${exposureBias}`);// 查询曝光补偿范围let biasRangeArray: Array<number> | undefined = [];biasRangeArray = this.session?.getExposureBiasRange();Logger.debug(TAG, `getExposureBiasRange success, biasRangeArray: ${JSON.stringify(biasRangeArray)}`);// 设置曝光补偿this.session?.setExposureBias(exposureBias);}/*** 对焦模式*/setFocusMode(focusMode: camera.FocusMode): void {// 检测对焦模式是否支持Logger.info(TAG, `setFocusMode is called`);let isSupported = this.session?.isFocusModeSupported(focusMode);Logger.info(TAG, `setFocusMode isSupported: ${isSupported}`);// 设置对焦模式if (!isSupported) {return;}this.session?.setFocusMode(focusMode);}
}export default new CameraService();
  • 在CameraService的initCamera函数里完成一个相机生命周期初始化的过程,包括调用getCameraMananger获取CameraMananger,调用getSupportedCameras获取支持的camera设备,调用getSupportedOutputCapability获取支持的camera设备能力集,调用createPreviewOutput创建预览输出,调用createCameraInput创建相机输入,调用CameraInput的open打开相机输入,调用onCameraStatusChange创建CameraManager注册回调,最后调用sessionFlowFn创建并开启Session。

  • 其中sessionFlowFn是一个创建session并开启预览的动作,主要流程包括:调用createSession创建Session,调用beginConfig开始配置会话,调用addInput把CameraInput加入到会话,调用addPreviewOutput把previewOutput加入到会话,调用commitConfig提交配置信息,调用start开始会话工作。

  • 在CameraService的releaseCamera函数里完成对相机生命周期释放的过程,调用output的release方法释放流,调用CameraInput的close方法关闭相机,再调用session的release释放当前会话。

  • 回调接口设置:

  • onCameraStatusChange:监听相机状态回调,在打开、退出相机,相机摄像头切换时会触发

  • onCameraInputChange:相机输入发生错误时触发回调

  • photoOutputCallBack:开启拍照时触发回调

  • previewOutputCallBack:开启预览时触发回调

  • onCaptureSessionErrorChange:session出现异常时触发回调

  • 相机预览、拍照,录像功能实现调用侧位于Index.ets,ModeComponent.ets中,

  • 源码参考:[Index.ets]

/** Copyright (c) 2024 Huawei Device Co., Ltd.* Licensed under the Apache License, Version 2.0 (the 'License');* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**     http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an 'AS IS' BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/import { camera } from '@kit.CameraKit';
import CameraService from '../mode/CameraService';
import Logger from '../common/utils/Logger';
import { ModeComponent } from '../views/ModeComponent';
import { SlideComponent } from '../views/SlideComponent';
import { GlobalContext } from '../common/utils/GlobalContext';
import { Constants } from '../common/Constants';
import { FocusAreaComponent } from '../views/FocusAreaComponent';
import { FocusComponent } from '../views/FocusComponent';
import { FlashingLightComponent } from '../views/FlashingLightComponent';const TAG = 'Index';@Entry
@Component
struct Index {// 主页面是否显示@StorageLink('isShow') isShow: boolean = false;@StorageLink('isOpenEditPage') isOpenEditPage: boolean = false;// Flash Mode@State flashMode: camera.FlashMode = camera.FlashMode.FLASH_MODE_CLOSE;@State focusPointBol: boolean = false;// 曝光区域手指点击坐标@State focusPointVal: Array<number> = [0, 0];@State xComponentAspectRatio: number = 1;private mXComponentController: XComponentController = new XComponentController();private defaultCameraDeviceIndex = 0;private surfaceId = '';aboutToAppear(): void {Logger.info(TAG, 'aboutToAppear');}async aboutToDisAppear(): Promise<void> {Logger.info(TAG, 'aboutToDisAppear');this.flashMode = camera.FlashMode.FLASH_MODE_CLOSE;await CameraService.releaseCamera();}async onPageShow(): Promise<void> {Logger.info(TAG, 'onPageShow');if (this.surfaceId !== '' && !this.isOpenEditPage) {await CameraService.initCamera(this.surfaceId, GlobalContext.get().getT<number>('cameraDeviceIndex'));}this.isOpenEditPage = false;}async onPageHide(): Promise<void> {Logger.info(TAG, 'onPageHide');}build() {Stack() {if (this.isShow) {XComponent({id: 'componentId',type: 'surface',controller: this.mXComponentController}).onLoad(async () => {Logger.info(TAG, 'onLoad is called');this.surfaceId = this.mXComponentController.getXComponentSurfaceId();GlobalContext.get().setObject('cameraDeviceIndex', this.defaultCameraDeviceIndex);GlobalContext.get().setObject('xComponentSurfaceId', this.surfaceId);let surfaceRect: SurfaceRect = {surfaceWidth: Constants.X_COMPONENT_SURFACE_HEIGHT,surfaceHeight: Constants.X_COMPONENT_SURFACE_WIDTH};this.mXComponentController.setXComponentSurfaceRect(surfaceRect);Logger.info(TAG, `onLoad surfaceId: ${this.surfaceId}`);await CameraService.initCamera(this.surfaceId, this.defaultCameraDeviceIndex);}).border({width: {top: Constants.X_COMPONENT_BORDER_WIDTH,bottom: Constants.X_COMPONENT_BORDER_WIDTH},color: Color.Black})// The width and height of the surface are opposite to those of the Xcomponent..width(px2vp(Constants.X_COMPONENT_SURFACE_HEIGHT)).height(px2vp(Constants.X_COMPONENT_SURFACE_WIDTH))}// 曝光框和对焦框FocusComponent({focusPointBol: $focusPointBol,focusPointVal: $focusPointVal})// 曝光对焦手指点击区域FocusAreaComponent({focusPointBol: $focusPointBol,focusPointVal: $focusPointVal,xComponentWidth: px2vp(Constants.X_COMPONENT_SURFACE_HEIGHT),xComponentHeight: px2vp(Constants.X_COMPONENT_SURFACE_WIDTH)})// slideSlideComponent()// 拍照ModeComponent()Row({ space: Constants.ROW_SPACE_24 }) {// 闪光灯FlashingLightComponent({flashMode: $flashMode})}.margin({ left: Constants.CAPTURE_BUTTON_COLUMN_MARGIN }).alignItems(VerticalAlign.Top).justifyContent(FlexAlign.Start).position({x: Constants.FLASH_POSITION_X,y: Constants.FLASH_POSITION_Y})}.size({width: Constants.FULL_PERCENT,height: Constants.FULL_PERCENT}).backgroundColor(Color.Black)}
}
  • 源码参考:[ModeComponent.ets]
/** Copyright (c) 2024 Huawei Device Co., Ltd.* Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**     http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/import { router } from '@kit.ArkUI';
import { camera } from '@kit.CameraKit';
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import CameraService from '../mode/CameraService';
import Logger from '../common/utils/Logger';
import { GlobalContext } from '../common/utils/GlobalContext';
import { Constants } from '../common/Constants';const TAG: string = 'ModeComponent';@Component
export struct ModeComponent {@StorageLink('isOpenEditPage') @Watch('changePageState') isOpenEditPage: boolean = false;@State sceneMode: camera.SceneMode = camera.SceneMode.NORMAL_PHOTO;@State isRecording: boolean = false;changePageState(): void {if (this.isOpenEditPage) {this.onJumpClick();}}aboutToAppear(): void {Logger.info(TAG, 'aboutToAppear');CameraService.setSavePictureCallback(this.handleSavePicture);}handleSavePicture = (photoAsset: photoAccessHelper.PhotoAsset): void => {Logger.info(TAG, 'handleSavePicture');this.setImageInfo(photoAsset);AppStorage.set<boolean>('isOpenEditPage', true);Logger.info(TAG, 'setImageInfo end');}setImageInfo(photoAsset: photoAccessHelper.PhotoAsset): void {Logger.info(TAG, 'setImageInfo');GlobalContext.get().setObject('photoAsset', photoAsset);}onJumpClick(): void {GlobalContext.get().setObject('sceneMode', this.sceneMode);// 目标urlrouter.pushUrl({ url: 'pages/EditPage' }, router.RouterMode.Single, (err) => {if (err) {Logger.error(TAG, `Invoke pushUrl failed, code is ${err.code}, message is ${err.message}`);return;}Logger.info(TAG, 'Invoke pushUrl succeeded.');});}build() {Column() {Row({ space: Constants.COLUMN_SPACE_24 }) {Column() {Text('拍照').fontSize(Constants.FONT_SIZE_14).fontColor(Color.White)}.width(Constants.CAPTURE_COLUMN_WIDTH).backgroundColor(this.sceneMode === camera.SceneMode.NORMAL_PHOTO ? $r('app.color.theme_color') : '').borderRadius(Constants.BORDER_RADIUS_14).onClick(async () => {if (this.sceneMode === camera.SceneMode.NORMAL_PHOTO) {return;}this.sceneMode = camera.SceneMode.NORMAL_PHOTO;CameraService.setSceneMode(this.sceneMode);let cameraDeviceIndex = GlobalContext.get().getT<number>('cameraDeviceIndex');let surfaceId = GlobalContext.get().getT<string>('xComponentSurfaceId');await CameraService.initCamera(surfaceId, cameraDeviceIndex);})// 录像Column() {Text('录像').fontSize(Constants.FONT_SIZE_14).fontColor(Color.White)}.width(Constants.CAPTURE_COLUMN_WIDTH).backgroundColor(this.sceneMode === camera.SceneMode.NORMAL_VIDEO ? $r('app.color.theme_color') : '').borderRadius(Constants.BORDER_RADIUS_14).onClick(async () => {if (this.sceneMode === camera.SceneMode.NORMAL_VIDEO) {return;}this.sceneMode = camera.SceneMode.NORMAL_VIDEO;CameraService.setSceneMode(this.sceneMode);let cameraDeviceIndex = GlobalContext.get().getT<number>('cameraDeviceIndex');let surfaceId = GlobalContext.get().getT<string>('xComponentSurfaceId');await CameraService.initCamera(surfaceId, cameraDeviceIndex);})}.height(Constants.CAPTURE_ROW_HEIGHT).width(Constants.FULL_PERCENT).justifyContent(FlexAlign.Center).alignItems(VerticalAlign.Center)Row() {Column() {}.width($r('app.string.200px'))// 拍照-录像 按键Column() {if (!this.isRecording) {Row() {Button() {Text().width($r('app.string.120px')).height($r('app.string.120px')).borderRadius($r('app.string.40px')).backgroundColor(this.sceneMode === camera.SceneMode.NORMAL_VIDEO ?$r('app.color.theme_color') : Color.White)}.border({width: Constants.CAPTURE_BUTTON_BORDER_WIDTH,color: $r('app.color.border_color'),radius: Constants.CAPTURE_BUTTON_BORDER_RADIUS}).width($r('app.string.200px')).height($r('app.string.200px')).backgroundColor(Color.Black).onClick(async () => {if (this.sceneMode === camera.SceneMode.NORMAL_PHOTO) {await CameraService.takePicture();} else {await CameraService.startVideo();this.isRecording = true;}})}} else {Row() {// 录像停止键Button() {Image($r('app.media.ic_camera_video_close')).size({ width: Constants.IMAGE_SIZE, height: Constants.IMAGE_SIZE })}.width($r('app.string.120px')).height($r('app.string.120px')).backgroundColor($r('app.color.theme_color')).onClick(() => {this.isRecording = !this.isRecording;CameraService.stopVideo().then(() => {this.isOpenEditPage = true;Logger.info(TAG, 'stopVideo success');})})}.width($r('app.string.200px')).height($r('app.string.200px')).borderRadius($r('app.string.60px')).backgroundColor($r('app.color.theme_color')).justifyContent(FlexAlign.SpaceAround)}}// 前后置摄像头切换Column() {Row() {Button() {Image($r('app.media.switch_camera')).width($r('app.string.120px')).height($r('app.string.120px'))}.width($r('app.string.200px')).height($r('app.string.200px')).backgroundColor($r('app.color.flash_background_color')).borderRadius($r('app.string.40px')).onClick(async () => {let cameraDeviceIndex = GlobalContext.get().getT<number>('cameraDeviceIndex');let surfaceId = GlobalContext.get().getT<string>('xComponentSurfaceId');cameraDeviceIndex ? cameraDeviceIndex = 0 : cameraDeviceIndex = 1;GlobalContext.get().setObject('cameraDeviceIndex', cameraDeviceIndex);await CameraService.initCamera(surfaceId, cameraDeviceIndex);})}}.visibility(this.isRecording ? Visibility.Hidden : Visibility.Visible)}.padding({left: Constants.CAPTURE_BUTTON_COLUMN_PADDING,right: Constants.CAPTURE_BUTTON_COLUMN_PADDING}).width(Constants.FULL_PERCENT).justifyContent(FlexAlign.SpaceBetween).alignItems(VerticalAlign.Center)}.justifyContent(FlexAlign.End).height(Constants.TEN_PERCENT).padding({left: Constants.CAPTURE_BUTTON_COLUMN_PADDING,right: Constants.CAPTURE_BUTTON_COLUMN_PADDING}).margin({ bottom: Constants.CAPTURE_BUTTON_COLUMN_MARGIN }).position({x: Constants.ZERO_PERCENT,y: Constants.EIGHTY_FIVE_PERCENT})}
}
  • 预览:开启预览位于Index.ets下的XComponent组件的onLoad接口中,其中调用CameraService.initCamera方法,将预览的surfaceId,摄像头设备作为入参啊传下去,完成开启相机的操作,开启预览。

  • 拍照:开启拍照位于ModeComponent.ets下的拍照按钮的onClick接口,调用CameraManager对象下的takePicture方法开启拍照操作。

  • 相机变焦功能实现调用侧位于SlideComponent.ets中,源码参考:[SlideComponent.ets]

/** Copyright (c) 2023 Huawei Device Co., Ltd.* Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**     http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/import CameraService, { SliderValue } from '../mode/CameraService';
import Logger from '../common/utils/Logger';
import { Constants } from '../common/Constants';const TAG: string = 'SlideComponent';// 变焦组件
@Component
export struct SlideComponent {// slide滑块@StorageLink('zoomRatio') zoomRatio: number = 1;@StorageLink('sliderValue') sliderValue: SliderValue | undefined = undefined;private fractionDigits = 2;private xString = 'x';aboutToDisappear(): void {}slideChange(value: number): void {CameraService.setZoomRatioFn(value);}build() {if (this.sliderValue) {Column() {Row() {Text(this.zoomRatio + this.xString).fontColor($r('app.color.slide_text_font_color')).width($r('app.string.120px')).height($r('app.string.50px')).borderRadius(Constants.TEXT_BORDER_RADIUS).backgroundColor(Color.White).fontSize(Constants.FONT_SIZE_14).textAlign(TextAlign.Center)}.justifyContent(FlexAlign.Center).width(Constants.FULL_PERCENT)Row() {Text(this.sliderValue?.min + this.xString).fontColor(Color.White)Text(this.sliderValue?.max + this.xString).fontColor(Color.White)}.justifyContent(FlexAlign.SpaceBetween).width(Constants.FULL_PERCENT)Row() {Slider({value: this.zoomRatio,min: this.sliderValue?.min,max: this.sliderValue?.max,step: this.sliderValue?.step,style: SliderStyle.OutSet}).showSteps(false).trackColor($r('app.color.slider_track_color')).selectedColor($r('app.color.theme_color')).onChange((value: number) => {Logger.info(TAG, 'onChange');let val = Number(value.toFixed(this.fractionDigits));this.slideChange(val);this.zoomRatio = val;})}.width(Constants.FULL_PERCENT)}.height($r('app.string.60px')).width(Constants.FORTY_PERCENT).position({x: Constants.THIRTY_PERCENT,y: Constants.SEVENTY_FIVE_PERCENT})}}
}
  • 变焦:变焦功能位于SlideComponent.ets,通过Slider组件的onChange接口将变焦率通过CameraService.setZoomRatioFn方法调整预览显示的画面。

  • 相机对焦功能实现调用侧位于FocusAreaComponent.ets,FocusComponent.ets中,

  • 源码参考:[FocusAreaComponent.ets]

/** Copyright (c) 2024 Huawei Device Co., Ltd.* Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**     http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/import Logger from '../common/utils/Logger';
import CameraService from '../mode/CameraService';const TAG: string = 'FocusAreaComponent';// 对焦区域
@Component
export struct FocusAreaComponent {@Link focusPointBol: boolean;@Link focusPointVal: Array<number>;@Prop xComponentWidth: number;@Prop xComponentHeight: number;// 对焦区域显示框定时器private areaTimer: number = -1;private focusFrameDisplayDuration: number = 3500;build() {Row() {}.width(this.xComponentWidth).height(this.xComponentHeight).opacity(1).onTouch((e: TouchEvent) => {if (e.type === TouchType.Down) {this.focusPointBol = true;this.focusPointVal[0] = e.touches[0].windowX;this.focusPointVal[1] = e.touches[0].windowY;// 归一化焦点。 设置的焦点与相机sensor角度和窗口方向有关(相机sensor角度可通过CameraDevice的cameraOrientation属性获取),下面焦点是以竖屏窗口,相机sensor角度为90度场景下的焦点设置CameraService.setFocusPoint({x: e.touches[0].y / this.xComponentHeight,y: 1 - (e.touches[0].x / this.xComponentWidth)});}if (e.type === TouchType.Up) {if (this.areaTimer) {clearTimeout(this.areaTimer);}this.areaTimer = setTimeout(() => {this.focusPointBol = false;}, this.focusFrameDisplayDuration);}}).onClick((event: ClickEvent) => {Logger.info(TAG, 'onClick is called');})}
}
  • 源码参考:[FocusComponent.ets]
/** Copyright (c) 2024 Huawei Device Co., Ltd.* Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**     http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/// 曝光选择
@Component
export struct FocusComponent {@Link focusPointBol: boolean;@Link focusPointVal: Array<number>;private mBorderWidth = 1.6;private mBorderRadius = 10;private mRowSize = 40;private mFocusPoint = 60;private focusFrameSize = 120;build() {if (this.focusPointBol) {Row() {// 对焦框Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.SpaceBetween }) {Flex({ justifyContent: FlexAlign.SpaceBetween }) {Row() {}.border({width: {left: this.mBorderWidth,top: this.mBorderWidth},color: Color.White,radius: { topLeft: this.mBorderRadius }}).size({ width: this.mRowSize, height: this.mRowSize })Row() {}.border({width: {right: this.mBorderWidth,top: this.mBorderWidth},color: Color.White,radius: { topRight: this.mBorderRadius }}).size({ width: this.mRowSize, height: this.mRowSize })}Flex({ justifyContent: FlexAlign.SpaceBetween }) {Row() {}.border({width: {left: this.mBorderWidth,bottom: this.mBorderWidth},color: Color.White,radius: { bottomLeft: this.mBorderRadius }}).size({ width: this.mRowSize, height: this.mRowSize })Row() {}.border({width: {right: this.mBorderWidth,bottom: this.mBorderWidth},color: Color.White,radius: { bottomRight: this.mBorderRadius }}).size({ width: this.mRowSize, height: this.mRowSize })}}.width(this.focusFrameSize).height(this.focusFrameSize).position({x: this.focusPointVal[0] - this.mFocusPoint,y: this.focusPointVal[1] - this.mFocusPoint})}.zIndex(1)}}
}
  • 对焦:对焦功能位于FocusAreaComponent.ets,通过FocusAreaComponent组件的onTouch接口将归一化焦点通过CameraService.setFocusPoint方法调整预览显示的对焦画面。
    以上就是本篇文章所带来的鸿蒙开发中一小部分技术讲解;想要学习完整的鸿蒙全栈技术。可以在结尾找我可全部拿到!
    下面是鸿蒙的完整学习路线,展示如下:
    1

除此之外,根据这个学习鸿蒙全栈学习路线,也附带一整套完整的学习【文档+视频】,内容包含如下

内容包含了:(ArkTS、ArkUI、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、鸿蒙南向开发、鸿蒙项目实战)等技术知识点。帮助大家在学习鸿蒙路上快速成长!

鸿蒙【北向应用开发+南向系统层开发】文档

鸿蒙【基础+实战项目】视频

鸿蒙面经

在这里插入图片描述

为了避免大家在学习过程中产生更多的时间成本,对比我把以上内容全部放在了↓↓↓想要的可以自拿喔!谢谢大家观看!

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 大模型术语表
  • 24年第五届“华数杯”数学建模竞赛浅析
  • 利用ffmpeg转码视频为gif图片,调整gif图片的大小
  • 全球氢燃料电池汽车市场规划预测:未来六年CAGR为44.4%
  • 前端-防抖代码
  • App推广新利器:Xinstall携带参数安装功能详解
  • FIR低通滤波器
  • 可验证随机函数 vrf 概述
  • Boost:asio网络编程从同步到异步
  • 【C++】函数重载
  • idea个人常用快捷键设置
  • 掌握PyCharm代码格式化秘籍:提升代码质量的终极指南
  • vue3: vuedraggable 的使用方法(正常数据的基本使用与树结构数据递归使用)
  • 【K8S】为什么需要Kubernetes?
  • 【Wireshark 抓 CAN 总线】Wireshark 抓取 CAN 总线数据的实现思路
  • 30天自制操作系统-2
  • Android系统模拟器绘制实现概述
  • JS数组方法汇总
  • mac修复ab及siege安装
  • MySQL-事务管理(基础)
  • Spring Security中异常上抛机制及对于转型处理的一些感悟
  • vue从创建到完整的饿了么(18)购物车详细信息的展示与删除
  • 官方新出的 Kotlin 扩展库 KTX,到底帮你干了什么?
  • 聊聊flink的TableFactory
  • 前端攻城师
  • 如何进阶一名有竞争力的程序员?
  • 深度解析利用ES6进行Promise封装总结
  • 用 vue 组件自定义 v-model, 实现一个 Tab 组件。
  • 正则学习笔记
  • AI算硅基生命吗,为什么?
  • 好程序员大数据教程Hadoop全分布安装(非HA)
  • ​软考-高级-系统架构设计师教程(清华第2版)【第20章 系统架构设计师论文写作要点(P717~728)-思维导图】​
  • # 透过事物看本质的能力怎么培养?
  • #FPGA(基础知识)
  • #Linux(Source Insight安装及工程建立)
  • #宝哥教你#查看jquery绑定的事件函数
  • #考研#计算机文化知识1(局域网及网络互联)
  • (4) PIVOT 和 UPIVOT 的使用
  • (附源码)spring boot网络空间安全实验教学示范中心网站 毕业设计 111454
  • (附源码)ssm高校升本考试管理系统 毕业设计 201631
  • (一)80c52学习之旅-起始篇
  • (转)C#开发微信门户及应用(1)--开始使用微信接口
  • .NET CLR基本术语
  • .NET CORE 第一节 创建基本的 asp.net core
  • .NET中使用Redis (二)
  • .net中应用SQL缓存(实例使用)
  • .sdf和.msp文件读取
  • /dev/VolGroup00/LogVol00:unexpected inconsistency;run fsck manually
  • :“Failed to access IIS metabase”解决方法
  • @modelattribute注解用postman测试怎么传参_接口测试之问题挖掘
  • [ MSF使用实例 ] 利用永恒之蓝(MS17-010)漏洞导致windows靶机蓝屏并获取靶机权限
  • []Telit UC864E 拨号上网
  • [2015][note]基于薄向列液晶层的可调谐THz fishnet超材料快速开关——
  • [Android] 240204批量生成联系人,短信,通话记录的APK
  • [BZOJ4566][HAOI2016]找相同字符(SAM)