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