• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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   ![](figures/mode-switching.gif)
16
17- 前后置切换动效,使用预览流截图做翻转模糊动效过渡。
18
19   图片为从前置相机切换为后置相机的效果。
20
21   ![](figures/front-rear-switching.gif)
22
23- 拍照闪黑动效,使用闪黑组件覆盖预览流实现闪黑动效过渡。
24
25  图片为点击完成拍摄的效果。
26
27  ![](figures/flash-black.gif)
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