HarmonyOS鸿蒙Next中播放lottie动画,连续播放的时候为啥不连续,会出现空白

HarmonyOS鸿蒙Next中播放lottie动画,连续播放的时候为啥不连续,会出现空白

Canvas(this.mainCanvasRenderingContext)
  .width('100%')
  .height('100%')
  .backgroundColor(Color.Transparent)
  .margin(0)
  .padding(0)
  .onReady(() => {
    // 抗锯齿设置
    this.mainCanvasRenderingContext.imageSmoothingEnabled = true;
    this.mainCanvasRenderingContext.imageSmoothingQuality = 'medium';

    // Lottie动画初始化
    this.animationItem = lottie.loadAnimation({
      container: this.mainCanvasRenderingContext,
      renderer: 'canvas',
      loop: true,
      autoplay: true,
      frameRate: 30,
      path: this.path,
      contentMode: 'Contain',
      initialSegment: [0, 200]
    });
    // 监听每一帧,记录当前帧
    this.animationItem.addEventListener('enterFrame', () => {
      if (this.animationItem) {
        this.currentFrame = Math.floor(this.animationItem.currentFrame);
      }
    });
  })
  .onDisAppear(() => {
    lottie.destroy();
  })

更多关于HarmonyOS鸿蒙Next中播放lottie动画,连续播放的时候为啥不连续,会出现空白的实战教程也可以访问 https://www.itying.com/category-93-b0.html

8 回复

尊敬的开发者您好!

动画播放过程中出现闪烁现象,由于存在lottie动画切换,怀疑是动画切换导致的闪烁问题。通过修改代码,使得上锁过程一次播放一个动画,可以发现并无闪烁现象,因此需要继续分析动画切换为何导致闪烁,主要排查以下方面:

  1. 动画切换时的资源加载和销毁。lottie从2.0.15版本开始,lottie动画的Canvas渲染模式每次只能加载一个动画,加载新动画时会自动清理旧动画资源,动画资源加载和清理有可能会导致动画播放的间隔;
  2. 动画加载和播放的时机。lottie动画分为加载和播放两个阶段,如果切换第二个动画时才进行动画加载,也可能导致动画播放的间隔。

【分析结论】

  1. lottie2.0.15动画的Canvas渲染模式每次只能加载一个动画,动画切换时,会清理动画资源后再加载播放第二个动画,导致动画播放出现间隔;
  2. 两个动画依次进行加载并播放,动画切换时,动画加载耗时导致动画播放出现间隔。

【修改建议】

  • lottie版本降低至2.0.14或更早版本(不推荐);
  • 多Canvas分层架构,即一个lottie动画使用一个Canvas加载(推荐方案)。
    1. 使用一个Canvas加载一个lottie动画,避免动画切换时进行资源清理;
    2. 在Canvas的onready中并行加载两个动画,在第一个动画播放完成后再启动第二个动画播放,避免动画切换时加载动画耗时。

针对问题代码,修改示例如下。经验证,修改后动画切换时无闪烁问题。

import lottie, { AnimationItem } from '@ohos/lottie';

@Entry
@Component
struct Index {
  private startSettings: RenderingContextSettings = new RenderingContextSettings(true);
  private startRenderingContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.startSettings);
  private loadSettings: RenderingContextSettings = new RenderingContextSettings(true);
  private loadRenderingContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.loadSettings);
  private timerId: number;
  private directionFlag: boolean;
  animateItemStart: AnimationItem = {} as AnimationItem
  animateItemEnd: AnimationItem = {} as AnimationItem
  @State isStopMove: boolean = false
  @State isStatic: boolean = true // 中间的锁开关是否是静态图
  @State isConvas: boolean = false;

  loadStartAnimation(mPath?: string, mName?: string, isLoop: boolean = false, afterPlayCallback?: (() => void) | null,
    completeCallback?: (() => void) | null) {
    this.animateItemStart = lottie.loadAnimation({
      container: this.startRenderingContext,
      renderer: 'Canvas',
      frameRate: 60, // 设置animator的刷帧率为30
      loop: isLoop,
      autoplay: false,
      name: mName,
      path: mPath,
    });
    this.animateItemStart.addEventListener('DOMLoaded', () => {
      this.animateItemStart.play()
      if (afterPlayCallback) {
        afterPlayCallback()
      }
    })
    this.animateItemStart.addEventListener('complete', () => {
      if (completeCallback) {
        completeCallback()
      }
    })
  }

  loadLoadAnimation(mPath?: string, mName?: string, isLoop: boolean = false, afterPlayCallback?: (() => void) | null,
    completeCallback?: (() => void) | null) {
    this.animateItemEnd = lottie.loadAnimation({
      container: this.loadRenderingContext,
      renderer: 'Canvas',
      frameRate: 60, // 设置animator的刷帧率为30
      loop: isLoop,
      autoplay: false,
      name: mName,
      path: mPath,
    });
    this.animateItemEnd.addEventListener('DOMLoaded', () => {
      if (afterPlayCallback) {
        afterPlayCallback()
      }
    })
    this.animateItemEnd.addEventListener('complete', () => {
      if (completeCallback) {
        completeCallback()
      }
    })
  }
  onChangeRight() {}
  onChangeLeft() {}
  build() {
    Stack() {
      Canvas(this.loadRenderingContext)
        .width(60)
        .height(60)
        .borderRadius(40)
        .backgroundColor($r('app.color.color_F7F7F7'))
        .onReady(() => {
          // 抗锯齿的设置
          this.loadRenderingContext.imageSmoothingEnabled = true;
          this.loadRenderingContext.imageSmoothingQuality = 'medium'
          this.loadLoadAnimation("common/lottie/loading.json", 'loading', false, () => {
            clearTimeout(this.timerId)
            // 请求接口,成功了回复静态图
            this.timerId = setTimeout(() => {
              this.isConvas = false
              this.isStopMove = false
              this.isStatic = true
              if (this.directionFlag) {
                this.onChangeRight()
              } else {
                this.onChangeLeft()
              }
            }, 2000)
          })
        })
        .visibility(this.isStatic ? Visibility.None : Visibility.Visible)
        .onDisAppear(() => {
          // 组件移除时,可销毁动画资源
          lottie.destroy('loading');
        })
      Canvas(this.startRenderingContext)
        .width(60)
        .height(60)
        .borderRadius(40)
        .backgroundColor($r('app.color.color_F7F7F7'))
        .onReady(() => {
          // 抗锯齿的设置
          this.startRenderingContext.imageSmoothingEnabled = true;
          this.startRenderingContext.imageSmoothingQuality = 'medium'
          this.loadStartAnimation('common/lottie/data.json', 'start', false, () => {
            this.isStopMove = true
          }, () => {
            this.isStatic = false;
            this.isConvas = true;
            this.animateItemEnd.play();
          })
        })
        .visibility((!this.isStatic && !this.isConvas) ? Visibility.Visible : Visibility.None)
        .onDisAppear(() => {
          // 组件移除时,可销毁动画资源
          lottie.destroy('start');
        })
      Image($r('app.media.home_clock_static'))
        .width(60)
        .aspectRatio(1)
        .height(60)
        .borderRadius(40)
        .visibility(this.isStatic ? Visibility.Visible : Visibility.Hidden)
    }
  }
}

更多关于HarmonyOS鸿蒙Next中播放lottie动画,连续播放的时候为啥不连续,会出现空白的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


我的问题是一个动画资源循环播放,第一次播放完成,播放第二次的时候,中间会出现空白,initialSegment: [0, 200] 这个值无论设置都会出现空白。

尊敬的开发者您好! lottie2.0.28版本,使用中心仓提供的代码未复现。 https://ohpm.openharmony.cn/#/cn/detail/@ohos%2Flottie 还烦请您提供可复现的demo或代码,以及对应的动画json。

试过了,还是不能够连续播放,会出现空白,

开发者您好!您看看二楼的答复是否能够解决您的问题呢?

在HarmonyOS Next中,Lottie动画连续播放出现空白帧,通常是因为动画资源未预加载或播放间隔导致渲染延迟。可检查是否在动画完成回调中立即重启播放,确保使用setRepeatCount设置无限循环而非手动重播。同时确认Lottie版本与HarmonyOS兼容,避免解码耗时引发断层。

在HarmonyOS Next中,Lottie动画播放出现不连续和空白帧,通常与以下几个核心机制有关:

  1. Canvas渲染与帧率同步问题CanvasonReady回调触发时,渲染上下文可能尚未完全就绪。当loop: trueautoplay: true时,Lottie动画会立即开始播放。若Canvas渲染线程与动画帧率(frameRate: 30)未完全同步,可能导致首帧或循环衔接帧绘制延迟,出现短暂空白。可尝试在onReady中延迟少量时间(如setTimeout)再启动动画,或确保Canvas尺寸和上下文完全稳定后加载。

  2. initialSegment设置与循环冲突:代码中initialSegment: [0, 200]指定了初始播放片段,但Lottie在循环播放时,若片段结束帧(200)与动画总帧数不匹配,或循环重置时帧索引计算有误差,可能造成片段跳转不连贯。建议检查动画总帧数,并确认initialSegment的结束帧是否合理。可尝试不设置initialSegment,或通过animationItem.playSegments([0,200], true)在加载后控制循环。

  3. 资源释放与重绘时机onDisAppear中调用lottie.destroy()会销毁动画实例,但若组件快速重新出现(如页面切换),新动画加载可能延迟。同时,Canvas在HarmonyOS Next中依赖GPU渲染管线,若动画帧率(30fps)与设备屏幕刷新率(通常60Hz或更高)不匹配,可能丢帧。可考虑将frameRate调整为60,或使用requestAnimationFrame同步。

  4. 内存与渲染上下文状态CanvasimageSmoothingEnabled等设置可能影响绘制效率。若动画资源较大,连续播放时内存回收或上下文重置可能导致帧丢失。建议监控animationItem.currentFrame在循环点的跳变情况,并确保backgroundColor(Color.Transparent)不引发额外重绘开销。

调试建议

  • enterFrame事件中打印currentFrame,观察循环点(如200帧附近)是否出现非连续值。
  • 尝试将renderer改为'svg'(若支持)排除Canvas特定问题。
  • 检查Lottie动画JSON文件本身是否包含空白关键帧或资源加载延迟。

以上因素可能单独或叠加导致不连续现象,需结合具体动画数据和运行环境进一步定位。

回到顶部