1# 相机基础动效(ArkTS)
2
3在使用相机过程中,如相机模式切换,前后置镜头切换等场景,不可避免出现预览流替换,为优化用户体验,可合理使用动效过渡。本文主要介绍如何使用预览流截图,并通过ArkUI提供的[显示动画能力](../../reference/apis-arkui/arkui-ts/ts-explicit-animatetoimmediately.md)实现下方三种核心场景动效:
4
5- 模式切换动效,使用预览流截图做模糊动效过渡。
6
7   图片为从录像模式切换为拍照模式的效果。
8
9   ![](figures/mode-switching.gif)
10
11- 前后置切换动效,使用预览流截图做翻转模糊动效过渡。
12
13   图片为从前置摄像头切换为后置摄像头的效果。
14
15   ![](figures/front-rear-switching.gif)
16
17- 拍照闪黑动效,使用闪黑组件覆盖预览流实现闪黑动效过渡。
18
19  图片为点击完成拍摄的效果。
20
21  ![](figures/flash-black.gif)
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