文档管理中心
您当前正在浏览新版开发者文档中心,目录分类和层级有所调整。点击左侧当前文档分类名称前的“☰”图标,可切换文档分类。 了解新版目录
指南与API参考指南媒体Camera Kit(相机服务)开发相机应用基础能力(ArkTS)相机基础动效(ArkTS)

相机基础动效(ArkTS)

在使用相机过程中,当遇到相机模式切换、前后置镜头切换等场景时,会不可避免地出现预览流替换。为优化用户体验,可合理使用动效过渡。本文主要介绍如何使用预览流截图,并通过ArkUI提供的animateToImmediately接口触发显式动画功能,实现下方三种核心场景动效。

  • 模式切换动效,使用预览流截图做模糊动效过渡。

    图片为从录像模式切换为拍照模式的效果。

  • 前后置切换动效,使用预览流截图做翻转模糊动效过渡。

    图片为从前置相机切换为后置相机的效果。

  • 拍照闪黑动效,使用闪黑组件覆盖预览流实现闪黑动效过渡。

    图片为点击完成拍摄的效果。

闪黑动效

使用组件覆盖的形式实现闪黑效果。

以下步骤中的示例代码均为自定义组件(即被@Component修饰的组件)的内部方法或逻辑。

  1. 导入依赖,需要导入相机框架、图片、ArkUI相关领域依赖。

    收起
    自动换行
    深色代码主题
    复制
    1. import { camera } from '@kit.CameraKit';
    2. import { image } from '@kit.ImageKit';
    3. import { curves } from '@kit.ArkUI';
  2. 构建闪黑组件。

    此处定义一个闪黑组件,在拍照闪黑及前后置切换时显示,用来遮挡XComponent组件。

    属性定义:

    收起
    自动换行
    深色代码主题
    复制
    1. @State isShowBlur: boolean = false;
    2. @State isShowBlack: boolean = false;
    3. @StorageLink('modeChange') @Watch('onModeChange') modeChangeFlag: number = 0;
    4. @StorageLink('switchCamera') @Watch('onSwitchCamera') switchCameraFlag: number = 0;
    5. @StorageLink('frameStart') @Watch('onFrameStart') frameStartFlag: number = 0;
    6. @StorageLink('captureClick') @Watch('onCaptureClick') captureClickFlag: number = 0;
    7. @StorageLink('surfaceShot') screenshotPixelMap: image.PixelMap | undefined = undefined; // 预览流截图。
    8. @StorageLink('curPosition') curPosition: number = 0; // 当前镜头前后置状态。
    9. @State shotImgBlur: number = 0;
    10. @State shotImgOpacity: number = 1;
    11. @State shotImgScale: ScaleOptions = { x: 1, y: 1 };
    12. @State shotImgRotation: RotateOptions = { y: BlurAnimateUtil.ROTATE_AXIS, angle: BlurAnimateUtil.IMG_FLIP_ANGLE_0 }
    13. @State flashBlackOpacity: number = 1;

    闪黑组件的实现逻辑参考:

    收起
    自动换行
    深色代码主题
    复制
    1. // 拍照闪黑及前后置切换时显示,用来遮挡XComponent组件。
    2. if (this.isShowBlack) {
    3. Column()
    4. .key('black')
    5. .width(this.getUIContext().px2vp(Constants.X_COMPONENT_SURFACE_HEIGHT))
    6. .height(this.getUIContext().px2vp(Constants.X_COMPONENT_SURFACE_WIDTH))
    7. .backgroundColor(Color.Black)
    8. .opacity(this.flashBlackOpacity)
    9. }
  3. 实现闪黑动效。

    收起
    自动换行
    深色代码主题
    复制
    1. private flashBlackAnim() {
    2. Logger.info(TAG, 'flashBlackAnim E');
    3. this.flashBlackOpacity = 1;
    4. this.isShowBlack = true;
    5. animateToImmediately({
    6. curve: curves.interpolatingSpring(1, 1, 410, 38),
    7. delay: 50,
    8. onFinish: () => {
    9. this.isShowBlack = false;
    10. this.flashBlackOpacity = 1;
    11. Logger.info(TAG, 'flashBlackAnim X');
    12. }
    13. }, () => {
    14. this.flashBlackOpacity = 0;
    15. })
    16. }
  4. 触发闪黑动效。

    点击或触控拍照按钮,更新@StorageLink绑定CaptureClick的值,触发onCaptureClick方法,动效开始播放。

    收起
    自动换行
    深色代码主题
    复制
    1. onCaptureClick(): void {
    2. Logger.info(TAG, 'onCaptureClick');
    3. this.flashBlackAnim();
    4. }

模糊动效

通过预览流截图,实现模糊动效,从而完成模式切换,或是前后置切换的动效。

以下除了步骤2,其他步骤中的示例代码均为自定义组件(即被@Component修饰的组件)的内部方法或逻辑。

  1. 导入依赖,需要导入相机框架、图片、ArkUI相关领域依赖。

    收起
    自动换行
    深色代码主题
    复制
    1. import { camera } from '@kit.CameraKit';
    2. import { image } from '@kit.ImageKit';
    3. import { curves } from '@kit.ArkUI';
  2. 获取预览流截图。

    预览流截图通过图形提供的image.createPixelMapFromSurface接口实现,surfaceId为当前预览流的surfaceId,size为当前预览流profile的宽高。创建截图工具类(ts文件),导入依赖,导出获取截图方法供页面使用,截图工具类实现参考:

    收起
    自动换行
    深色代码主题
    复制
    1. export class BlurAnimateUtil {
    2. public static surfaceShot: image.PixelMap;
    3. // ...
    4. /**
    5. * 获取surface截图
    6. * @param surfaceId
    7. * @returns
    8. */
    9. public static async doSurfaceShot(surfaceId: string) {
    10. Logger.info(TAG, `doSurfaceShot surfaceId:${surfaceId}.`);
    11. if ('' === surfaceId) {
    12. Logger.error(TAG, 'surface not ready!');
    13. return;
    14. }
    15. try {
    16. if (this.surfaceShot) {
    17. await this.surfaceShot.release();
    18. }
    19. this.surfaceShot = await image.createPixelMapFromSurface(surfaceId, {
    20. size: { width: Constants.X_COMPONENT_SURFACE_WIDTH, height: Constants.X_COMPONENT_SURFACE_HEIGHT }, // 取预览流profile的宽高。
    21. x: 0,
    22. y: 0
    23. });
    24. let imageInfo: image.ImageInfo = await this.surfaceShot.getImageInfo();
    25. Logger.info('doSurfaceShot surfaceShot:' + JSON.stringify(imageInfo.size));
    26. } catch (err) {
    27. Logger.error(JSON.stringify(err))
    28. }
    29. }
    30. public static getSurfaceShot() {
    31. return this.surfaceShot;
    32. }
    33. }
  3. 构建截图组件。

    此处定义一个截图组件,置于预览流XComponent组件之上,用来遮挡XComponent组件。

    属性定义:

    收起
    自动换行
    深色代码主题
    复制
    1. @State isShowBlur: boolean = false;
    2. @State isShowBlack: boolean = false;
    3. @StorageLink('modeChange') @Watch('onModeChange') modeChangeFlag: number = 0;
    4. @StorageLink('switchCamera') @Watch('onSwitchCamera') switchCameraFlag: number = 0;
    5. @StorageLink('frameStart') @Watch('onFrameStart') frameStartFlag: number = 0;
    6. @StorageLink('captureClick') @Watch('onCaptureClick') captureClickFlag: number = 0;
    7. @StorageLink('surfaceShot') screenshotPixelMap: image.PixelMap | undefined = undefined; // 预览流截图。
    8. @StorageLink('curPosition') curPosition: number = 0; // 当前镜头前后置状态。
    9. @State shotImgBlur: number = 0;
    10. @State shotImgOpacity: number = 1;
    11. @State shotImgScale: ScaleOptions = { x: 1, y: 1 };
    12. @State shotImgRotation: RotateOptions = { y: BlurAnimateUtil.ROTATE_AXIS, angle: BlurAnimateUtil.IMG_FLIP_ANGLE_0 }
    13. @State flashBlackOpacity: number = 1;

    截图组件的实现参考:

    收起
    自动换行
    深色代码主题
    复制
    1. if (this.isShowBlur) {
    2. Column() {
    3. Image(this.screenshotPixelMap)
    4. .blur(this.shotImgBlur)
    5. .opacity(this.shotImgOpacity)
    6. .rotate(this.shotImgRotation)// ArkUI提供的旋转,用于组件沿指定坐标系进行旋转。
    7. .scale(this.shotImgScale)
    8. .width(this.getUIContext().px2vp(Constants.X_COMPONENT_SURFACE_HEIGHT))
    9. .height(this.getUIContext().px2vp(Constants.X_COMPONENT_SURFACE_WIDTH))
    10. .syncLoad(true)
    11. }
    12. .width(this.getUIContext().px2vp(Constants.X_COMPONENT_SURFACE_HEIGHT))
    13. .height(this.getUIContext().px2vp(Constants.X_COMPONENT_SURFACE_WIDTH))
    14. }
  4. (按实际情况选择)实现模糊出现动效。

    模式切换动效分两段实现,模糊出现动效和模糊消失动效。

    模糊出现动效:用户点击或触控事件触发预览流截图,显示截图组件,截图清晰到模糊,覆盖旧预览流。

    注意

    由于图形提供的image.createPixelMapFromSurface接口是通过截取surface内容获取PixelMap,其内容和XComponent组件绘制逻辑不同,需要根据前后置镜头做不同的图片内容旋转补偿和组件旋转补偿。

    收起
    自动换行
    深色代码主题
    复制
    1. private async showBlurAnim() {
    2. Logger.info(TAG, 'showBlurAnim E');
    3. // 获取已完成的surface截图。
    4. let shotPixel = BlurAnimateUtil.getSurfaceShot();
    5. // 后置。
    6. if (this.curPosition === 0) {
    7. Logger.info(TAG, 'showBlurAnim BACK');
    8. // 直板机后置截图旋转补偿90°。
    9. await shotPixel.rotate(BlurAnimateUtil.IMG_ROTATE_ANGLE_90);
    10. // 直板机后置截图初始翻转0°。
    11. this.shotImgRotation = { y: BlurAnimateUtil.ROTATE_AXIS, angle: BlurAnimateUtil.IMG_FLIP_ANGLE_0 };
    12. } else {
    13. Logger.info(TAG, 'showBlurAnim FRONT');
    14. // 直板机前置截图旋转补偿270°。
    15. await shotPixel.rotate(BlurAnimateUtil.IMG_ROTATE_ANGLE_270);
    16. // 直板机前置截图镜像补偿。
    17. this.shotImgRotation = { y: BlurAnimateUtil.ROTATE_AXIS, angle: BlurAnimateUtil.IMG_FLIP_ANGLE_180 };
    18. }
    19. this.screenshotPixelMap = shotPixel;
    20. // 初始化动效参数。
    21. this.shotImgBlur = 0; // 无模糊。
    22. this.shotImgOpacity = 1; // 不透明。
    23. // 触发页面渲染。
    24. this.isShowBlur = true;
    25. animateToImmediately(
    26. {
    27. duration: BlurAnimateUtil.SHOW_BLUR_DURATION,
    28. curve: Curve.Friction,
    29. onFinish: async () => {
    30. Logger.info(TAG, 'showBlurAnim X');
    31. }
    32. },
    33. () => {
    34. // 截图模糊度变化动效。
    35. this.shotImgBlur = BlurAnimateUtil.ANIM_MODE_SWITCH_BLUR;
    36. }
    37. );
    38. }
  5. 实现模糊消失动效。

    模糊消失动效:由新模式预览流首帧回调on('frameStart')触发,截图组件模糊到清晰,显示新预览流。

    收起
    自动换行
    深色代码主题
    复制
    1. private hideBlurAnim(): void {
    2. this.isShowBlack = false;
    3. Logger.info(TAG, 'hideBlurAnim E');
    4. animateToImmediately({
    5. duration: BlurAnimateUtil.HIDE_BLUR_DURATION,
    6. curve: Curve.FastOutSlowIn,
    7. onFinish: () => {
    8. // 模糊组件下树。
    9. this.isShowBlur = false;
    10. this.shotImgBlur = 0;
    11. this.shotImgOpacity = 1;
    12. Logger.info(TAG, 'hideBlurAnim X');
    13. }
    14. }, () => {
    15. // 截图透明度变化动效。
    16. this.shotImgOpacity = 0;
    17. });
    18. }
  6. (按实际情况选择)实现模糊翻转动效。

    模糊翻转动效分两段实现,模糊翻转动效和模糊消失动效,其中模糊消失动效同第5步。

    模糊翻转动效:分两段组件翻转实现,先向外翻转90°再向内翻转90°,同时还执行了模糊度、透明度、比例缩放等动效。

    为保证预览流在翻转时不露出,需要构建一个闪黑组件用于遮挡XComponent组件,构建方式参考闪黑动效-步骤2。

    收起
    自动换行
    深色代码主题
    复制
    1. /**
    2. * 先向外翻转90°,前后置切换触发
    3. */
    4. private async rotateFirstAnim() {
    5. Logger.info(TAG, 'rotateFirstAnim E');
    6. // 获取已完成的surface截图。
    7. let shotPixel = BlurAnimateUtil.getSurfaceShot();
    8. // 后置切前置。
    9. if (this.curPosition === 1) {
    10. Logger.info(TAG, 'rotateFirstAnim BACK');
    11. // 直板机后置切前置截图旋转补偿90°。
    12. await shotPixel.rotate(BlurAnimateUtil.IMG_ROTATE_ANGLE_90); // Image Kit提供的旋转,用于处理图片本身的旋转。
    13. // 直板机后置切前置截图初始翻转0°。
    14. this.shotImgRotation = { y: BlurAnimateUtil.ROTATE_AXIS, angle: BlurAnimateUtil.IMG_FLIP_ANGLE_0 };
    15. } else {
    16. Logger.info(TAG, 'rotateFirstAnim FRONT');
    17. // 直板机前置切后置截图旋转补偿270°。
    18. await shotPixel.rotate(BlurAnimateUtil.IMG_ROTATE_ANGLE_270);
    19. // 直板机前置切后置截图初始翻转180°。
    20. this.shotImgRotation = { y: BlurAnimateUtil.ROTATE_AXIS, angle: BlurAnimateUtil.IMG_FLIP_ANGLE_180 };
    21. }
    22. this.screenshotPixelMap = shotPixel;
    23. // 触发页面渲染。
    24. this.isShowBlack = true;
    25. this.isShowBlur = true;
    26. animateToImmediately(
    27. {
    28. duration: BlurAnimateUtil.ROTATION_DURATION,
    29. delay: BlurAnimateUtil.FLIP_DELAY, // 时延保证组件缩放模糊动效先行,再翻转后视觉效果更好。
    30. curve: curves.cubicBezierCurve(0.20, 0.00, 0.83, 1.00),
    31. onFinish: () => {
    32. Logger.info(TAG, 'rotateFirstAnim X');
    33. // 在onFinish后触发二段旋转。
    34. this.rotateSecondAnim();
    35. }
    36. },
    37. () => {
    38. // 截图向外翻转动效。
    39. if (this.curPosition === 1) {
    40. this.shotImgRotation = { y: BlurAnimateUtil.ROTATE_AXIS, angle: BlurAnimateUtil.IMG_FLIP_ANGLE_90 };
    41. } else {
    42. this.shotImgRotation = { y: BlurAnimateUtil.ROTATE_AXIS, angle: BlurAnimateUtil.IMG_FLIP_ANGLE_270 };
    43. }
    44. }
    45. )
    46. }
    47. /**
    48. * 再向内翻转90°
    49. */
    50. async rotateSecondAnim() {
    51. Logger.info(TAG, 'rotateSecondAnim E');
    52. // 获取已完成的surface截图。
    53. let shotPixel = BlurAnimateUtil.getSurfaceShot();
    54. // 后置。
    55. if (this.curPosition === 1) {
    56. // 直板机后置镜头旋转补偿90°。
    57. await shotPixel.rotate(BlurAnimateUtil.IMG_ROTATE_ANGLE_90); // Image Kit提供的旋转,用于处理图片本身的旋转。
    58. // 瞬时调整为-90°,保证二段旋转后,图片不是镜像的。
    59. this.shotImgRotation = { y: BlurAnimateUtil.ROTATE_AXIS, angle: BlurAnimateUtil.IMG_FLIP_ANGLE_MINUS_90 };
    60. } else { // 前置。
    61. // 直板机前置截图旋转补偿270°。
    62. await shotPixel.rotate(BlurAnimateUtil.IMG_ROTATE_ANGLE_270);
    63. // 直板机前置截图镜像补偿。
    64. this.shotImgRotation = { y: BlurAnimateUtil.ROTATE_AXIS, angle: BlurAnimateUtil.IMG_FLIP_ANGLE_180 };
    65. }
    66. this.screenshotPixelMap = shotPixel;
    67. animateToImmediately(
    68. {
    69. duration: BlurAnimateUtil.ROTATION_DURATION,
    70. curve: curves.cubicBezierCurve(0.17, 0.00, 0.20, 1.00),
    71. onFinish: () => {
    72. Logger.info(TAG, 'rotateSecondAnim X');
    73. }
    74. },
    75. () => {
    76. // 截图翻转为初始状态。
    77. if (this.curPosition === 1) {
    78. this.shotImgRotation = { y: BlurAnimateUtil.ROTATE_AXIS, angle: BlurAnimateUtil.IMG_FLIP_ANGLE_0 };
    79. } else {
    80. this.shotImgRotation = { y: BlurAnimateUtil.ROTATE_AXIS, angle: BlurAnimateUtil.IMG_FLIP_ANGLE_180 };
    81. }
    82. }
    83. )
    84. }
    85. /**
    86. * 向外翻转90°同时
    87. */
    88. blurFirstAnim() {
    89. Logger.info(TAG, 'blurFirstAnim E');
    90. // 初始化动效参数。
    91. this.shotImgBlur = 0; // 无模糊。
    92. this.shotImgOpacity = 1; // 不透明。
    93. this.shotImgScale = { x: 1, y: 1 };
    94. animateToImmediately(
    95. {
    96. duration: BlurAnimateUtil.ROTATION_DURATION,
    97. curve: Curve.Sharp,
    98. onFinish: () => {
    99. Logger.info(TAG, 'blurFirstAnim X');
    100. this.blurSecondAnim();
    101. }
    102. },
    103. () => {
    104. // 截图模糊度变化动效。
    105. this.shotImgBlur = BlurAnimateUtil.ANIM_MODE_SWITCH_BLUR;
    106. // 截图比例动效。
    107. this.shotImgScale = { x: BlurAnimateUtil.IMG_SCALE, y: BlurAnimateUtil.IMG_SCALE };
    108. }
    109. );
    110. }
    111. /**
    112. * 向内翻转90°同时
    113. */
    114. blurSecondAnim() {
    115. Logger.info(TAG, 'blurSecondAnim E');
    116. animateToImmediately(
    117. {
    118. duration: BlurAnimateUtil.ROTATION_DURATION,
    119. curve: Curve.Sharp,
    120. onFinish: () => {
    121. Logger.info(TAG, 'blurSecondAnim X');
    122. }
    123. },
    124. () => {
    125. this.shotImgScale = { x: 1, y: 1 };
    126. }
    127. )
    128. }
  7. 按需触发动效。

    模式切换动效触发:点击或触控模式按钮立即执行doSurfaceShot截图方法,更新@StorageLink绑定modeChange的值,触发onModeChange方法,开始动效。

    收起
    自动换行
    深色代码主题
    复制
    1. onModeChange(): void {
    2. Logger.info(TAG, 'onModeChange');
    3. this.showBlurAnim();
    4. }

    前后置切换动效触发:点击或触控前后置切换按钮立即执行doSurfaceShot截图方法,更新@StorageLink绑定switchCamera的值,触发onSwitchCamera方法,开始动效。

    收起
    自动换行
    深色代码主题
    复制
    1. onSwitchCamera(): void {
    2. Logger.info(TAG, 'onSwitchCamera');
    3. this.blurFirstAnim();
    4. this.rotateFirstAnim();
    5. }

    模糊消失动效触发:监听预览流首帧回调on('frameStart'),更新@StorageLink绑定frameStart的值,触发onFrameStart方法,开始动效。

    收起
    自动换行
    深色代码主题
    复制
    1. onFrameStart(): void {
    2. Logger.info(TAG, 'onFrameStart');
    3. this.hideBlurAnim();
    4. }