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