HarmonyOS 鸿蒙Next中App设置了字体跟随系统,切换系统字体大小后,lottie动画出现问题

HarmonyOS 鸿蒙Next中App设置了字体跟随系统,切换系统字体大小后,lottie动画出现问题 ↓正常的状态

正常的状态

↓修改系统字体后,回到应用,lottie动画异常

修改系统字体后,回到应用,lottie动画异常

配置如下

AppScope/resources/base/profile/configuration.json

{
  "configuration": {
    "fontSizeScale": "followSystem"
  }
}

组件UI大致实现如下

// lottie动画在 Tab -> TabContent -> tabBar 里面

// tabBar 使用一个 @Builder 来构建

// 这个 build 内部实现如下:

Badge() {
	Stack() {
		Image()
		Canvas(this.canvasRenderingCtx)
			.width("24vp")
            .height("24vp")
            .onReady(() => {
              this.animateItem?.resize()
              this.loadLottieJson(tabIdx)
            })
            .onDisAppear(() => {
              lottie.destroy('tabIdx')

            })
	}
	...
}


// 其中 读取并运行lottie动画的函数 this.loadLottieJson() 实现如下

loadLottieJson(idx: number) {

    MyLottieDataList.forEach((item, idx) => {
      if (tabIdx == idx) {
      	let appId = item.appId
        lottie.destroy(appId)
        this.animateItem = lottie.loadAnimation({
          container: this.canvasRenderingCtx,
          renderer: 'canvas', 
          loop: false,
          autoplay: false,
          name: appId,
          autoSkip: false,
          path: `lottieJSON/${item.lottieJson}`
        })

        this.animateItem.addEventListener('DOMLoaded', (args: Object): void => {
          if (this.animateItem) {
            this.animateItem.play();
          }
        });
      }
    })
}

这个动画在平时使用过程中没有任何问题,但是一旦执行以下步骤 “应用退到后台 -> 修改系统字体大小 -> 应用回到前台”,tab上的 lottie 就变白了,只有切换tabbar 才会恢复正常

我尝试过在 onPageShow() 加一个延时,重新执行 loadLottieJson 函数,想让动画重新执行一遍。

在普通的应用“退到后台 -> 回到前台”时,该操作生效。

但是在 “应用退到后台 -> 修改系统字体大小 -> 应用回到前台”时,动画不生效。


更多关于HarmonyOS 鸿蒙Next中App设置了字体跟随系统,切换系统字体大小后,lottie动画出现问题的实战教程也可以访问 https://www.itying.com/category-93-b0.html

6 回复

开发者你好,当前根据您这边的场景描述进行了代码配置,在进行了 “应用退到后台 -> 修改系统字体大小 -> 应用回到前台”操作时,并未出现你这边截图中的异常现象,请问是否可以提供下最小复现问题的代码。

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

@Entry
@Component
struct PageDemo {
  private renderingSettings: RenderingContextSettings = new RenderingContextSettings(true);
  private canvasRenderingContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.renderingSettings);
  private animateItem: AnimationItem | null = null;
  private animateName: string = 'animation';

  build() {
      Column() {
        Tabs() {
          TabContent() {
            // app.string.homepage_content资源文件中的value值为“首页的内容”
            Text($r('app.string.homepage_content'))
              .fontSize(30)
          }
          // app.string.homepage资源文件中的value值为“首页”
          .tabBar($r('app.string.homepage'))

          TabContent() {
            // app.string.recommend_content资源文件中的value值为“推荐的内容”
            Text($r('app.string.recommend_content'))
              .fontSize(30)
          }
          // app.string.recommend资源文件中的value值为“推荐”
          .tabBar($r('app.string.recommend'))

          TabContent() {
            // app.string.discover_content资源文件中的value值为“发现的内容”
            Text($r('app.string.discover_content'))
              .fontSize(30)
          }
          // app.string.discover资源文件中的value值为“发现”
          .tabBar($r('app.string.discover'))

          TabContent() {
            // app.string.mine_content资源文件中的value值为“我的内容”
            Text($r('app.string.mine_content'))
              .fontSize(30)
          }
          // app.string.mine_content资源文件中的value值为“我的”
          .tabBar(this.tabBuilder(4))
        }
      }
  }

  @Builder
  tabBuilder(num:number) {
    Badge({count:num, style: { badgeSize: 6, badgeColor: '#FA2A2D' }}) {
      Stack() {
        Column(){
          Image($r("app.media.startIcon"))
            .width(30)
            .height(30)
          Text("我的")
        }
        Canvas(this.canvasRenderingContext)
          .width("24vp")
          .height("24vp")
          .onReady(() => {
            this.animateItem?.resize()
            this.loadAnimation()
          })
          .onDisAppear(() => {
            lottie.destroy('tabIdx')

          })
      }
    }
  }
  loadAnimation() {
    this.animateItem = lottie.loadAnimation({
      container: this.canvasRenderingContext,
      renderer: 'canvas', // canvas 渲染模式
      loop: false,
      autoplay: false,
      name: this.animateName,
      autoSkip:false,
      contentMode: 'Contain',
      path: 'common/animation.json',
    })
    // 因为动画是异步加载,所以对animateItem的操作需要放在动画加载完成回调里操作
    this.animateItem?.addEventListener('DOMLoaded', (args: Object): void => {
      this.animateItem?.changeColor([225, 25, 100, 1]);
      this.animateItem?.play();
    });
  }


}

更多关于HarmonyOS 鸿蒙Next中App设置了字体跟随系统,切换系统字体大小后,lottie动画出现问题的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


你这个代码你真的运行过么?我这还是报错的,修改字体从后台回到前台之后,即使加上你的DOMLoaded依然是无法播放,都不会走监听回调,lottile库版本是

@ohos/lottie@2.0.30

cke_765.png

cke_5281.png

这是代码

onConfigurationUpdate

当系统全局配置(例如系统语言、深浅色等)发生变更时,会触发该回调。

// EntryAbility.ets

onConfigurationUpdate(config: Configuration): void {
    console.info(`EntryAbility.onConfigurationUpdate, colorMode: ${config.colorMode}`);
    console.info(`EntryAbility.onConfigurationUpdate, fontSizeScale: ${config.fontSizeScale}`);
    this.context.eventHub.emit("onConfigurationUpdate", config);
}

所在页面

@Entry
@ComponentV2
struct Demo {
    private onConfigurationUpdate = (config?: Configuration) => {
        //重新执行 loadLottieJson 函数
    };

    aboutToAppear(): void {
        this.getUIContext().getHostContext()?.eventHub.on("onConfigurationUpdate", this.onConfigurationUpdate);
    }

    aboutToDisappear(): void {
        this.getUIContext().getHostContext()?.eventHub.off("onConfigurationUpdate", this.onConfigurationUpdate);
    }

    build() {
            //...
    }
        
}

试试看行不行。

在鸿蒙Next中,系统字体缩放会动态调整应用的字体大小。Lottie动画若包含文本图层(如动态文本或SVG渲染),其尺寸计算依赖初始字体上下文,切换后可能未及时更新,导致布局偏移、闪烁或变形。另外,字体缩放可能影响Canvas或渲染管线中的矩阵变换,造成动画帧异常。,

你遇到的Lottie动画在系统字体变更后变白的问题,通常是因为系统配置变更触发了页面或组件的重建,导致 Canvas 绘图上下文失效,但 Lottie 仍在使用旧的、无效的渲染上下文

在你的场景中,“退后台->改字体->回前台”这个操作,系统为了适配新的字体缩放比例,可能会销毁并重建当前页面。Canvas 组件被重新创建,其持有的 canvasRenderingCtx 变成了新的对象,但 loadLottieJson 可能在旧上下文中被触发或依然持有旧上下文的引用,导致渲染为空白。

你尝试在 onPageShow 中重载动画,但普通前后台切换与配置变更导致的页面重建生命周期是不同的,所以单独延时重载在后者场景下未生效。

解决方法的核心是:当 Canvas 重建后,确保在它的 onReady 回调里重新加载 Lottie 动画,因为此时传入的 context 是崭新且有效的。

你可以按如下方式修改 TabBar 构建器内的 Canvas 代码:

// 在 TabBar 的 @Builder 中
Badge() {
  Stack() {
    Image()
    Canvas(this.canvasRenderingCtx) // 确保上下文引用已更新
      .width("24vp")
      .height("24vp")
      .onReady(() => {
        // Canvas 准备就绪,包括首次创建和配置变更后重建。
        // 在这里销毁旧动画并加载新动画,保证使用的是当前有效的 context。
        if (this.animateItem) {
          lottie.destroy(this.currentAppId); // 销毁可能存在的旧实例
        }
        // 关键:在此回调中加载动画,this 指向当前组件实例,上下文是最新的。
        this.loadLottieJson(this.currentTabIdx); 
      })
      .onDisAppear(() => {
        lottie.destroy(this.currentAppId);
      })
  }
}

同时,确保 loadLottieJson 函数直接使用传入的参数或组件当前状态,不再依赖外部的延时重试。这样,无论是正常创建还是因字体变更导致的重建,都能在 Canvas 就绪时正确加载动画,从根本上解决问题。

回到顶部