恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
HarmonyOS 应用开发《掌上英语》第93篇:相机模式切换过程中的基础动效
首页
资讯中心
/
HarmonyOS 应用开发《掌上英语》第93篇:相机模式切换过程中的基础动效
HarmonyOS 应用开发《掌上英语》第93篇:相机模式切换过程中的基础动效
发布时间:2026/8/4 18:26:29
相机基础动效(ArkTS)在使用相机过程中当遇到相机模式切换、前后置镜头切换等场景时会不可避免地出现预览流替换。为优化用户体验可合理使用动效过渡。本文主要介绍如何使用预览流截图并通过ArkUI提供的animateToImmediately接口触发显式动画功能实现下方三种核心场景动效。模式切换动效使用预览流截图做模糊动效过渡。图片为从录像模式切换为拍照模式的效果。前后置切换动效使用预览流截图做翻转模糊动效过渡。图片为从前置相机切换为后置相机的效果。拍照闪黑动效使用闪黑组件覆盖预览流实现闪黑动效过渡。图片为点击完成拍摄的效果。闪黑动效使用组件覆盖的形式实现闪黑效果。以下步骤中的示例代码均为自定义组件即被Component修饰的组件的内部方法或逻辑。导入依赖需要导入相机框架、图片、ArkUI相关领域依赖。import{ camera }fromkit.CameraKit;import{ image }fromkit.ImageKit;import{ curves }fromkit.ArkUI;构建闪黑组件。此处定义一个闪黑组件在拍照闪黑及前后置切换时显示用来遮挡XComponent组件。属性定义StateisShowBlur: boolean false;StateisShowBlack: boolean false;StorageLink(modeChange)Watch(onModeChange)modeChangeFlag: number 0;StorageLink(switchCamera)Watch(onSwitchCamera)switchCameraFlag: number 0;StorageLink(frameStart)Watch(onFrameStart)frameStartFlag: number 0;StorageLink(captureClick)Watch(onCaptureClick)captureClickFlag: number 0;StorageLink(surfaceShot)screenshotPixelMap: image.PixelMap | undefined undefined;// 预览流截图。StorageLink(curPosition)curPosition: number 0;// 当前镜头前后置状态。StateshotImgBlur: number 0;StateshotImgOpacity: number 1;StateshotImgScale: ScaleOptions { x: 1, y: 1 };StateshotImgRotation: RotateOptions { y: BlurAnimateUtil.ROTATE_AXIS, angle: BlurAnimateUtil.IMG_FLIP_ANGLE_0 }StateflashBlackOpacity: number 1;闪黑组件的实现逻辑参考// 拍照闪黑及前后置切换时显示用来遮挡XComponent组件。if(this.isShowBlack) { Column().key(black).width(this.getUIContext().px2vp(Constants.X_COMPONENT_SURFACE_HEIGHT)).height(this.getUIContext().px2vp(Constants.X_COMPONENT_SURFACE_WIDTH)).backgroundColor(Color.Black).opacity(this.flashBlackOpacity) }实现闪黑动效。privateflashBlackAnim() { Logger.info(TAG,flashBlackAnim E);this.flashBlackOpacity 1;this.isShowBlack true; animateToImmediately({ curve: curves.interpolatingSpring(1,1,410,38), delay:50, onFinish: () {this.isShowBlack false;this.flashBlackOpacity 1; Logger.info(TAG,flashBlackAnim X); } }, () {this.flashBlackOpacity 0; }) }触发闪黑动效。点击或触控拍照按钮更新https://devpress.csdn.net/application-dev/ui/state-management/arkts-appstorage.md#storagelinkStorageLink绑定CaptureClick的值触发onCaptureClick方法动效开始播放。onCaptureClick(): void { Logger.info(TAG,onCaptureClick); this.flashBlackAnim(); }模糊动效通过预览流截图实现模糊动效从而完成模式切换或是前后置切换的动效。以下除了步骤2其他步骤中的示例代码均为自定义组件即被Component修饰的组件的内部方法或逻辑。导入依赖需要导入相机框架、图片、ArkUI相关领域依赖。import{ camera }fromkit.CameraKit;import{ image }fromkit.ImageKit;import{ curves }fromkit.ArkUI;获取预览流截图。预览流截图通过图形提供的image.createPixelMapFromSurface接口实现surfaceId为当前预览流的surfaceIdsize为当前预览流profile的宽高。创建截图工具类ts文件导入依赖导出获取截图方法供页面使用截图工具类实现参考exportclassBlurAnimateUtil{publicstaticsurfaceShot: image.PixelMap;// .../** * 获取surface截图 *paramsurfaceId*returns*/publicstaticasyncdoSurfaceShot(surfaceId:string) {Logger.info(TAG,doSurfaceShot surfaceId:${surfaceId}.);if( surfaceId) {Logger.error(TAG,surface not ready!);return; }try{if(this.surfaceShot) {awaitthis.surfaceShot.release(); }this.surfaceShotawaitimage.createPixelMapFromSurface(surfaceId, {size: {width:Constants.X_COMPONENT_SURFACE_WIDTH,height:Constants.X_COMPONENT_SURFACE_HEIGHT},// 取预览流profile的宽高。x:0,y:0});letimageInfo: image.ImageInfoawaitthis.surfaceShot.getImageInfo();Logger.info(doSurfaceShot surfaceShot:JSON.stringify(imageInfo.size)); }catch(err) {Logger.error(JSON.stringify(err)) } }publicstaticgetSurfaceShot() {returnthis.surfaceShot; } }构建截图组件。此处定义一个截图组件置于预览流XComponent组件之上用来遮挡XComponent组件。属性定义StateisShowBlur: boolean false;StateisShowBlack: boolean false;StorageLink(modeChange)Watch(onModeChange)modeChangeFlag: number 0;StorageLink(switchCamera)Watch(onSwitchCamera)switchCameraFlag: number 0;StorageLink(frameStart)Watch(onFrameStart)frameStartFlag: number 0;StorageLink(captureClick)Watch(onCaptureClick)captureClickFlag: number 0;StorageLink(surfaceShot)screenshotPixelMap: image.PixelMap | undefined undefined;// 预览流截图。StorageLink(curPosition)curPosition: number 0;// 当前镜头前后置状态。StateshotImgBlur: number 0;StateshotImgOpacity: number 1;StateshotImgScale: ScaleOptions { x: 1, y: 1 };StateshotImgRotation: RotateOptions { y: BlurAnimateUtil.ROTATE_AXIS, angle: BlurAnimateUtil.IMG_FLIP_ANGLE_0 }StateflashBlackOpacity: number 1;截图组件的实现参考if(this.isShowBlur) { Column() { Image(this.screenshotPixelMap) .blur(this.shotImgBlur) .opacity(this.shotImgOpacity) .rotate(this.shotImgRotation)// ArkUI提供的旋转用于组件沿指定坐标系进行旋转。.scale(this.shotImgScale) .width(this.getUIContext().px2vp(Constants.X_COMPONENT_SURFACE_HEIGHT)) .height(this.getUIContext().px2vp(Constants.X_COMPONENT_SURFACE_WIDTH)) .syncLoad(true) } .width(this.getUIContext().px2vp(Constants.X_COMPONENT_SURFACE_HEIGHT)) .height(this.getUIContext().px2vp(Constants.X_COMPONENT_SURFACE_WIDTH)) }按实际情况选择实现模糊出现动效。模式切换动效分两段实现模糊出现动效和模糊消失动效。模糊出现动效用户点击或触控事件触发预览流截图显示截图组件截图清晰到模糊覆盖旧预览流。注意由于图形提供的image.createPixelMapFromSurface接口是通过截取surface内容获取PixelMap其内容和XComponent组件绘制逻辑不同需要根据前后置镜头做不同的图片内容旋转补偿和组件旋转补偿。privateasync showBlurAnim() { Logger.info(TAG,showBlurAnim E);// 获取已完成的surface截图。let shotPixel BlurAnimateUtil.getSurfaceShot();// 后置。if(this.curPosition 0) { Logger.info(TAG,showBlurAnim BACK);// 直板机后置截图旋转补偿90°。await shotPixel.rotate(BlurAnimateUtil.IMG_ROTATE_ANGLE_90);// 直板机后置截图初始翻转0°。this.shotImgRotation { y: BlurAnimateUtil.ROTATE_AXIS, angle: BlurAnimateUtil.IMG_FLIP_ANGLE_0 }; }else{ Logger.info(TAG,showBlurAnim FRONT);// 直板机前置截图旋转补偿270°。await shotPixel.rotate(BlurAnimateUtil.IMG_ROTATE_ANGLE_270);// 直板机前置截图镜像补偿。this.shotImgRotation { y: BlurAnimateUtil.ROTATE_AXIS, angle: BlurAnimateUtil.IMG_FLIP_ANGLE_180 }; }this.screenshotPixelMap shotPixel;// 初始化动效参数。this.shotImgBlur 0;// 无模糊。this.shotImgOpacity 1;// 不透明。// 触发页面渲染。this.isShowBlur true; animateToImmediately( { duration: BlurAnimateUtil.SHOW_BLUR_DURATION, curve: Curve.Friction, onFinish: async () { Logger.info(TAG,showBlurAnim X); } }, () {// 截图模糊度变化动效。this.shotImgBlur BlurAnimateUtil.ANIM_MODE_SWITCH_BLUR; } ); }实现模糊消失动效。模糊消失动效由新模式预览流首帧回调on(frameStart)触发截图组件模糊到清晰显示新预览流。privatehideBlurAnim(): void {this.isShowBlack false; Logger.info(TAG,hideBlurAnim E); animateToImmediately({ duration: BlurAnimateUtil.HIDE_BLUR_DURATION, curve: Curve.FastOutSlowIn, onFinish: () {// 模糊组件下树。this.isShowBlur false;this.shotImgBlur 0;this.shotImgOpacity 1; Logger.info(TAG,hideBlurAnim X); } }, () {// 截图透明度变化动效。this.shotImgOpacity 0; }); }按实际情况选择实现模糊翻转动效。模糊翻转动效分两段实现模糊翻转动效和模糊消失动效其中模糊消失动效同第5步。模糊翻转动效分两段组件翻转实现先向外翻转90°再向内翻转90°同时还执行了模糊度、透明度、比例缩放等动效。为保证预览流在翻转时不露出需要构建一个闪黑组件用于遮挡XComponent组件构建方式参考闪黑动效-步骤2。/** * 先向外翻转90°前后置切换触发 */privateasync rotateFirstAnim() { Logger.info(TAG,rotateFirstAnim E);// 获取已完成的surface截图。let shotPixel BlurAnimateUtil.getSurfaceShot();// 后置切前置。if(this.curPosition 1) { Logger.info(TAG,rotateFirstAnim BACK);// 直板机后置切前置截图旋转补偿90°。await shotPixel.rotate(BlurAnimateUtil.IMG_ROTATE_ANGLE_90);// Image Kit提供的旋转用于处理图片本身的旋转。// 直板机后置切前置截图初始翻转0°。this.shotImgRotation { y: BlurAnimateUtil.ROTATE_AXIS, angle: BlurAnimateUtil.IMG_FLIP_ANGLE_0 }; }else{ Logger.info(TAG,rotateFirstAnim FRONT);// 直板机前置切后置截图旋转补偿270°。await shotPixel.rotate(BlurAnimateUtil.IMG_ROTATE_ANGLE_270);// 直板机前置切后置截图初始翻转180°。this.shotImgRotation { y: BlurAnimateUtil.ROTATE_AXIS, angle: BlurAnimateUtil.IMG_FLIP_ANGLE_180 }; }this.screenshotPixelMap shotPixel;// 触发页面渲染。this.isShowBlack true;this.isShowBlur true; animateToImmediately( { duration: BlurAnimateUtil.ROTATION_DURATION, delay: BlurAnimateUtil.FLIP_DELAY,// 时延保证组件缩放模糊动效先行再翻转后视觉效果更好。curve: curves.cubicBezierCurve(0.20,0.00,0.83,1.00), onFinish: () { Logger.info(TAG,rotateFirstAnim X);// 在onFinish后触发二段旋转。this.rotateSecondAnim(); } }, () {// 截图向翻转动效。if(this.curPosition 1) {this.shotImgRotation { y: BlurAnimateUtil.ROTATE_AXIS, angle: BlurAnimateUtil.IMG_FLIP_ANGLE_90 }; }else{this.shotImgRotation { y: BlurAnimateUtil.ROTATE_AXIS, angle: BlurAnimateUtil.IMG_FLIP_ANGLE_270 }; } } ) }/** * 再向内翻转90° */async rotateSecondAnim() { Logger.info(TAG,rotateSecondAnim E);// 获取已完成的surface截图。let shotPixel BlurAnimateUtil.getSurfaceShot();// 后置。if(this.curPosition 1) {// 直板机后置镜头旋转补偿90°。await shotPixel.rotate(BlurAnimateUtil.IMG_ROTATE_ANGLE_90);// Image Kit提供的旋转用于处理图片本身的旋转。// 瞬时调整为-90°保证二段旋转后图片不是镜像的。this.shotImgRotation { y: BlurAnimateUtil.ROTATE_AXIS, angle: BlurAnimateUtil.IMG_FLIP_ANGLE_MINUS_90 }; }else{// 前置。// 直板机前置截图旋转补偿270°。await shotPixel.rotate(BlurAnimateUtil.IMG_ROTATE_ANGLE_270);// 直板机前置截图镜像补偿。this.shotImgRotation { y: BlurAnimateUtil.ROTATE_AXIS, angle: BlurAnimateUtil.IMG_FLIP_ANGLE_180 }; }this.screenshotPixelMap shotPixel; animateToImmediately( { duration: BlurAnimateUtil.ROTATION_DURATION, curve: curves.cubicBezierCurve(0.17,0.00,0.20,1.00), onFinish: () { Logger.info(TAG,rotateSecondAnim X); } }, () {// 截图翻转为初始状态。if(this.curPosition 1) {this.shotImgRotation { y: BlurAnimateUtil.ROTATE_AXIS, angle: BlurAnimateUtil.IMG_FLIP_ANGLE_0 }; }else{this.shotImgRotation { y: BlurAnimateUtil.ROTATE_AXIS, angle: BlurAnimateUtil.IMG_FLIP_ANGLE_180 }; } } ) }/** * 向外翻转90°同时 */blurFirstAnim() { Logger.info(TAG,blurFirstAnim E);// 初始化动效参数。this.shotImgBlur 0;// 无模糊。this.shotImgOpacity 1;// 不透明。this.shotImgScale { x:1, y:1}; animateToImmediately( { duration: BlurAnimateUtil.ROTATION_DURATION, curve: Curve.Sharp, onFinish: () { Logger.info(TAG,blurFirstAnim X);this.blurSecondAnim(); } }, () {// 截图模糊度变化动效。this.shotImgBlur BlurAnimateUtil.ANIM_MODE_SWITCH_BLUR;// 截图比例动效。this.shotImgScale { x: BlurAnimateUtil.IMG_SCALE, y: BlurAnimateUtil.IMG_SCALE }; } ); }/** * 向内翻转90°同时 */blurSecondAnim() { Logger.info(TAG,blurSecondAnim E); animateToImmediately( { duration: BlurAnimateUtil.ROTATION_DURATION, curve: Curve.Sharp, onFinish: () { Logger.info(TAG,blurSecondAnim X); } }, () {this.shotImgScale { x:1, y:1}; } ) }按需触发动效。模式切换动效触发点击或触控模式按钮立即执行doSurfaceShot截图方法更新https://devpress.csdn.net/application-dev/ui/state-management/arkts-appstorage.md#storagelinkStorageLink绑定modeChange的值触发onModeChange方法开始动效。onModeChange(): void { Logger.info(TAG,onModeChange); this.showBlurAnim(); }前后置切换动效触发点击或触控前后置切换按钮立即执行doSurfaceShot截图方法更新https://devpress.csdn.net/application-dev/ui/state-management/arkts-appstorage.md#storagelinkStorageLink绑定switchCamera的值触发onSwitchCamera方法开始动效。onSwitchCamera(): void { Logger.info(TAG,onSwitchCamera);this.blurFirstAnim();this.rotateFirstAnim(); }模糊消失动效触发监听预览流首帧回调on(frameStart)更新https://devpress.csdn.net/application-dev/ui/state-management/arkts-appstorage.md#storagelinkStorageLink绑定frameStart的值触发onFrameStart方法开始动效。onFrameStart(): void { Logger.info(TAG,onFrameStart); this.hideBlurAnim(); }