1# 相机基础动效(ArkTS) 2 3在使用相机过程中,如相机模式切换,前后置镜头切换等场景,不可避免出现预览流替换,为优化用户体验,可合理使用动效过渡。本文主要介绍如何使用预览流截图,并通过ArkUI提供的[显示动画能力](../../reference/apis-arkui/arkui-ts/ts-explicit-animatetoimmediately.md)实现下方三种核心场景动效: 4 5- 模式切换动效,使用预览流截图做模糊动效过渡。 6 7 图片为从录像模式切换为拍照模式的效果。 8 9  10 11- 前后置切换动效,使用预览流截图做翻转模糊动效过渡。 12 13 图片为从前置摄像头切换为后置摄像头的效果。 14 15  16 17- 拍照闪黑动效,使用闪黑组件覆盖预览流实现闪黑动效过渡。 18 19 图片为点击完成拍摄的效果。 20 21  22 23## 闪黑动效 24 25使用组件覆盖的形式实现闪黑效果。 26 271. 导入依赖,需要导入相机框架、图片、ArkUI相关领域依赖。 28 29 ```ts 30 import { curves } from '@kit.ArkUI'; 31 ``` 32 332. 构建闪黑组件。 34 35 此处定义一个闪黑组件,在拍照闪黑及前后置切换时显示,用来遮挡XComponent组件。 36 37 属性定义: 38 39 ```ts 40 @State isShowBlack: boolean = false; // 是否显示闪黑组件 41 @StorageLink('captureClick') @Watch('onCaptureClick') captureClickFlag: number = 0; // 拍照闪黑动效入口 42 @State flashBlackOpacity: number = 1; // 闪黑组件透明度 43 ``` 44 45 闪黑组件的实现逻辑参考: 46 47 ```ts 48 // 拍照闪黑及前后置切换时显示,用来遮挡XComponent组件 49 if (this.isShowBlack) { 50 Column() 51 .key('black') 52 .width(px2vp(1080)) // 与预览流XComponent宽高保持一致,图层在预览流之上,截图组件之下 53 .height(px2vp(1920)) 54 .backgroundColor(Color.Black) 55 .opacity(this.flashBlackOpacity) 56 } 57 ``` 58 593. 实现闪黑动效。 60 61 ```ts 62 function flashBlackAnim() { 63 console.info('flashBlackAnim E'); 64 this.flashBlackOpacity = 1; // 闪黑组件不透明 65 this.isShowBlack = true; // 显示闪黑组件 66 animateToImmediately({ 67 curve: curves.interpolatingSpring(1, 1, 410, 38), 68 delay: 50, // 延时50ms,实现黑屏 69 onFinish: () => { 70 this.isShowBlack = false; // 闪黑组件下树 71 this.flashBlackOpacity = 1; 72 console.info('flashBlackAnim X'); 73 } 74 }, () => { 75 this.flashBlackOpacity = 0; // 闪黑组件从不透明到透明 76 }) 77 } 78 ``` 79 804. 触发闪黑动效。 81 82 点击或触控拍照按钮,更新StorageLink绑定CaptureClick的值,触发onCaptureClick方法,动效开始播放。 83 84 ```ts 85 onCaptureClick(): void { 86 console.info('onCaptureClick'); 87 console.info('onCaptureClick'); 88 this.flashBlackAnim(); 89 } 90 ``` 91 92 93 94## 模糊动效 95 96通过预览流截图,实现模糊动效,从而完成模式切换,或是前后置切换的动效。 97 981. 导入依赖,需要导入相机框架、图片、ArkUI相关领域依赖。 99 100 ```ts 101 import { camera } from '@kit.CameraKit'; 102 import { image } from '@kit.ImageKit'; 103 import { curves } from '@kit.ArkUI'; 104 ``` 105 1062. 获取预览流截图。 107 108 预览流截图通过图形提供的[image.createPixelMapFromSurface](../../reference/apis-image-kit/js-apis-image.md#imagecreatepixelmapfromsurface11)接口实现,surfaceId为当前预览流的surfaceId,size为当前预览流profile的宽高。创建截图工具类(ts文件),导入依赖,导出获取截图方法供页面使用,截图工具类实现参考: 109 110 ```ts 111 import { image } from '@kit.ImageKit'; 112 113 export class BlurAnimateUtil { 114 public static surfaceShot: image.PixelMap; 115 116 /** 117 * 获取surface截图 118 * @param surfaceId 119 * @returns 120 */ 121 public static async doSurfaceShot(surfaceId: string) { 122 console.info(`doSurfaceShot surfaceId:${surfaceId}.`); 123 if (surfaceId === '') { 124 console.error('surface not ready!'); 125 return; 126 } 127 try { 128 if (this.surfaceShot) { 129 await this.surfaceShot.release(); 130 } 131 this.surfaceShot = await image.createPixelMapFromSurface(surfaceId, { 132 size: { width: 1920, height: 1080 }, // 取预览流profile的宽高 133 x: 0, 134 y: 0 135 }); 136 let imageInfo: image.ImageInfo = await this.surfaceShot.getImageInfo(); 137 console.info('doSurfaceShot surfaceShot:' + JSON.stringify(imageInfo.size)); 138 } catch (err) { 139 console.error(JSON.stringify(err)); 140 } 141 } 142 143 /** 144 * 获取doSurfaceShot得到的截图 145 * @returns 146 */ 147 public static getSurfaceShot(): image.PixelMap { 148 return this.surfaceShot; 149 } 150 } 151 ``` 152 1533. 构建截图组件。 154 155 此处定义一个截图组件,置于预览流XComponent组件之上,用来遮挡XComponent组件。 156 157 属性定义: 158 159 ```ts 160 @State isShowBlur: boolean = false; // 是否显示截图组件 161 @StorageLink('modeChange') @Watch('onModeChange') modeChangeFlag: number = 0; // 模式切换动效触发入口 162 @StorageLink('switchCamera') @Watch('onSwitchCamera') switchCameraFlag: number = 0;// 前后置切换动效触发入口 163 @StorageLink('frameStart') @Watch('onFrameStart') frameStartFlag: number = 0; // 动效消失入口 164 @State screenshotPixelMap: image.PixelMap | undefined = undefined; // 截图组件PixelMap 165 @State surfaceId: string = ''; // 当前预览流XComponent的surfaceId 166 @StorageLink('curPosition') curPosition: number = 0; // 当前镜头前后置状态 167 @State shotImgBlur: number = 0; // 截图组件模糊度 168 @State shotImgOpacity: number = 1; // 截图组件透明度 169 @State shotImgScale: ScaleOptions = { x: 1, y: 1 }; // 截图组件比例 170 @State shotImgRotation: RotateOptions = { y: 0.5, angle: 0 } // 截图组件旋转角度 171 ``` 172 173 截图组件的实现参考: 174 175 ```ts 176 // 截图组件,置于预览流XComponent组件之上 177 if (this.isShowBlur) { 178 Column() { 179 Image(this.screenshotPixelMap) 180 .blur(this.shotImgBlur) 181 .opacity(this.shotImgOpacity) 182 .rotate(this.shotImgRotation)// ArkUI提供,用于组件旋转 183 .scale(this.shotImgScale) 184 .width(px2vp(1080)) // 与预览流XComponent宽高保持一致,图层在预览流之上 185 .height(px2vp(1920)) 186 .syncLoad(true) 187 } 188 .width(px2vp(1080)) 189 .height(px2vp(1920)) 190 } 191 ``` 192 1934. (按实际情况选择)实现模糊出现动效。 194 195 模式切换动效分两段实现,模糊出现动效和模糊消失动效。 196 197 模糊出现动效:用户点击或触控事件触发预览流截图,显示截图组件,截图清晰到模糊,覆盖旧预览流。 198 199 > 注意:由于图形提供的image.createPixelMapFromSurface接口是截取surface内容获取PixelMap,其内容和XComponent组件绘制逻辑不同,需要根据**前后置**镜头做不同的**图片内容旋转补偿**和**组件旋转补偿**。 200 201 ```ts 202 async function showBlurAnim() { 203 console.info('showBlurAnim E'); 204 // 获取已完成的surface截图 205 let shotPixel = BlurAnimateUtil.getSurfaceShot(); 206 // 后置 207 if (this.curPosition === 0) { 208 console.info('showBlurAnim BACK'); 209 // 直板机后置截图初始内容旋转补偿90° 210 await shotPixel.rotate(90); //ImageKit提供,用于图片内容旋转 211 // 直板机后置截图初始组件旋转补偿0° 212 this.shotImgRotation = { y: 0.5, angle: 0 }; 213 } else { 214 console.info('showBlurAnim FRONT'); 215 // 直板机前置截图内容旋转补偿270° 216 await shotPixel.rotate(270); 217 // 直板机前置截图组件旋转补偿180° 218 this.shotImgRotation = { y: 0.5, angle: 180 }; 219 } 220 this.screenshotPixelMap = shotPixel; 221 // 初始化动效参数 222 this.shotImgBlur = 0; // 无模糊 223 this.shotImgOpacity = 1; // 不透明 224 this.isShowBlur = true; // 显示截图组件 225 animateToImmediately( 226 { 227 duration: 200, 228 curve: Curve.Friction, 229 onFinish: async () => { 230 console.info('showBlurAnim X'); 231 } 232 }, 233 () => { 234 this.shotImgBlur = 48; // 截图组件模糊度变化动效 235 } 236 ); 237 } 238 ``` 239 2405. 实现模糊消失动效。 241 242 模糊消失动效:由新模式预览流首帧回调[on('frameStart')](../../reference/apis-camera-kit/js-apis-camera.md#onframestart)触发,截图组件模糊到清晰,显示新预览流。 243 244 ```ts 245 function hideBlurAnim(): void { 246 this.isShowBlack = false; 247 console.info('hideBlurAnim E'); 248 animateToImmediately({ 249 duration: 200, 250 curve: Curve.FastOutSlowIn, 251 onFinish: () => { 252 this.isShowBlur = false; // 模糊组件下树 253 this.shotImgBlur = 0; 254 this.shotImgOpacity = 1; 255 console.info('hideBlurAnim X'); 256 } 257 }, () => { 258 // 截图透明度变化动效 259 this.shotImgOpacity = 0; // 截图组件透明度变化动效 260 }); 261 } 262 ``` 263 2646. (按实际情况选择)实现模糊翻转动效。 265 266 模糊翻转动效分两段实现,模糊翻转动效和模糊消失动效,其中模糊消失动效同第5步。 267 268 模糊翻转动效:分两段组件翻转实现,先向外翻转90°再向内翻转90°,同时还执行了模糊度、透明度、比例缩放等动效。 269 270 为保证预览流在翻转时不露出,需要构建一个闪黑组件用于遮挡XComponent组件,构建方式参考[闪黑动效](#闪黑动效)-步骤2。 271 272 ```ts 273 /** 274 * 先向外翻转90°,前后置切换触发 275 */ 276 async function rotateFirstAnim() { 277 console.info('rotateFirstAnim E'); 278 // 获取已完成的surface截图 279 let shotPixel = BlurAnimateUtil.getSurfaceShot(); 280 // 后置切前置 281 if (this.curPosition === 1) { 282 console.info('rotateFirstAnim BACK'); 283 // 直板机后置切前置截图初始内容旋转补偿90° 284 await shotPixel.rotate(90); //ImageKit提供,用于图片内容旋转 285 // 直板机后置切前置截图初始组件旋转补偿0° 286 this.shotImgRotation = { y: 0.5, angle: 0 }; 287 } else { 288 console.info('rotateFirstAnim FRONT'); 289 // 直板机前置切后置截图初始内容旋转补偿270° 290 await shotPixel.rotate(270); 291 // 直板机前置切后置截图初始组件旋转补偿180° 292 this.shotImgRotation = { y: 0.5, angle: 180 }; 293 } 294 this.screenshotPixelMap = shotPixel; 295 this.isShowBlack = true; // 显示闪黑组件,覆盖预览流保证视觉效果 296 this.isShowBlur = true; // 显示截图组件 297 animateToImmediately( 298 { 299 duration: 200, 300 delay: 50, // 时延保证组件缩放模糊动效先行,再翻转,视觉效果更好 301 curve: curves.cubicBezierCurve(0.20, 0.00, 0.83, 1.00), 302 onFinish: () => { 303 console.info('rotateFirstAnim X'); 304 // 在onFinish后触发二段翻转 305 this.rotateSecondAnim(); 306 } 307 }, 308 () => { 309 // 截图向外翻转动效 310 if (this.curPosition === 1) { 311 this.shotImgRotation = { y: 0.5, angle: 90 }; 312 } else { 313 this.shotImgRotation = { y: 0.5, angle: 270 }; 314 } 315 } 316 ) 317 } 318 319 /** 320 * 再向内翻转90° 321 */ 322 async function rotateSecondAnim() { 323 console.info('rotateSecondAnim E'); 324 // 获取已完成的surface截图 325 let shotPixel = BlurAnimateUtil.getSurfaceShot(); 326 // 后置 327 if (this.curPosition === 1) { 328 // 直板机后置镜头内容旋转补偿90° 329 await shotPixel.rotate(90); 330 // 组件旋转调整为-90°,保证二段翻转后,图片不是镜像的 331 this.shotImgRotation = { y: 0.5, angle: 90 }; 332 } else { // 前置 333 // 直板机前置截图内容旋转补偿270° 334 await shotPixel.rotate(270); 335 // 直板机前置截图组件旋转补偿180° 336 this.shotImgRotation = { y: 0.5, angle: 180 }; 337 } 338 this.screenshotPixelMap = shotPixel; 339 animateToImmediately( 340 { 341 duration: 200, 342 curve: curves.cubicBezierCurve(0.17, 0.00, 0.20, 1.00), 343 onFinish: () => { 344 console.info('rotateSecondAnim X'); 345 } 346 }, 347 () => { 348 // 截图向内翻转动效,翻转至初始状态 349 if (this.curPosition === 1) { 350 this.shotImgRotation = { y: 0.5, angle: 0 }; 351 } else { 352 this.shotImgRotation = { y: 0.5, angle: 180 }; 353 } 354 } 355 ) 356 } 357 358 /** 359 * 向外翻转90°同时 360 */ 361 function blurFirstAnim() { 362 console.info('blurFirstAnim E'); 363 // 初始化动效参数 364 this.shotImgBlur = 0; //无模糊 365 this.shotImgOpacity = 1; //不透明 366 this.shotImgScale = { x: 1, y: 1 }; 367 animateToImmediately( 368 { 369 duration: 200, 370 curve: Curve.Sharp, 371 onFinish: () => { 372 console.info('blurFirstAnim X'); 373 this.blurSecondAnim(); 374 } 375 }, 376 () => { 377 // 截图模糊动效 378 this.shotImgBlur = 48; 379 // 截图比例缩小动效 380 this.shotImgScale = { x: 0.75, y: 0.75 }; 381 } 382 ); 383 } 384 385 /** 386 * 向内翻转90°同时 387 */ 388 function blurSecondAnim() { 389 console.info('blurSecondAnim E'); 390 animateToImmediately( 391 { 392 duration: 200, 393 curve: Curve.Sharp, 394 onFinish: () => { 395 console.info('blurSecondAnim X'); 396 } 397 }, 398 () => { 399 // 截图比例恢复动效 400 this.shotImgScale = { x: 1, y: 1 }; 401 } 402 ) 403 } 404 ``` 405 4067. 按需触发动效。 407 408 模式切换动效触发:点击或触控模式按钮立即执行doSurfaceShot截图方法,更新StorageLink绑定modeChange的值,触发onModeChange方法,开始动效。 409 410 ```ts 411 onModeChange(): void { 412 console.info('onModeChange'); 413 this.showBlurAnim(); 414 } 415 ``` 416 417 前后置切换动效触发:点击或触控前后置切换按钮立即执行doSurfaceShot截图方法,更新StorageLink绑定switchCamera的值,触发onSwitchCamera方法,开始动效。 418 419 ```ts 420 onSwitchCamera(): void { 421 console.info('onSwitchCamera'); 422 this.blurFirstAnim(); 423 this.rotateFirstAnim(); 424 } 425 ``` 426 427 模糊消失动效触发:监听预览流首帧回调[on('frameStart')](../../reference/apis-camera-kit/js-apis-camera.md#onframestart),更新StorageLink绑定frameStart的值,触发onFrameStart方法,开始动效。 428 429 ```ts 430 onFrameStart(): void { 431 console.info('onFrameStart'); 432 this.hideBlurAnim(); 433 } 434 ``` 435