• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
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