QML 动画选择:GIF与Lottie 的区别,以及 Qt 6.8 与 6.11 的 Lottie 实现差异

在QML里实现动态图的路径有两条:GIF 和 Lottie。

文章先介绍这两者的区别,然后对比 Qt 6.8 与 Qt 6.11 的 Lottie 实现差异,同一份 Lottie 素材,在这两个版本里可能画出完全不同的结果,这一点会直接决定你的工程该用哪个版本。

以下是Qt6.8.2中运行的效果:

只有第一张图基本展示了素材的动画,第二张图就缺少大量的信息,然后第三张和第四张就没有动。这个版本的lottie库还需要额外编译,很多特性不支持,导致了很多JSON格式的lottie动画都没法适用。

以下是Qt6.11中运行的效果:

所有的图都能动,但是第三张图的沙漏位置有点异常,我让AI帮我修复了一下(图中展示的是修复后的JSON文件)。

文中的素材来自:lottiefiles.com/

GIF 与 Lottie 的区别

GIF 的本质是逐帧位图:每一帧都是一张 8 位索引色的图片,靠 LZW 压缩串起来。

Lottie 的本质是矢量图形加关键帧动画的 JSON 描述:一个圆、一条路径、一次旋转都带关键帧,播放时由渲染器实时画出来。它来自 Adobe After Effects 的 Bodymovin 插件导出。

具体差异如下:

维度 GIF Lottie
数据本质 逐帧位图(LZW 压缩) 矢量图形 + 关键帧的 JSON
颜色 单帧最多 256 色 32 位真彩,支持渐变
透明度 只有 1 位(全透明 / 全不透明),边缘必带锯齿 完整 alpha 通道,半透明与柔和边缘
缩放 位图,放大模糊 矢量,等比缩放
体积 与画幅、时长强相关,几百 KB 到数 MB 同画幅通常 10~100 KB 量级
运行时控制 只能整体播 / 停 可指定帧、方向、循环次数、逐帧拖拽
单帧寻址 有,currentFrame 可读可写 有,gotoAndStop(frame)
渲染开销 每帧解码位图,内存 ≈ 帧数 × 画幅 实时光栅化,开销与素材固有尺寸相关
二次编辑 只能重新导出 JSON 可改,也能脚本批量改
Qt 里的类型 AnimatedImage(依赖 qgif.dll 图片插件) LottieAnimation(Qt.labs.lottieqt)

Qt 6.8 和 6.11 都自带 qgif.dll,所以 AnimatedImage 直接放 GIF 就能动:

qml 复制代码
import QtQuick

AnimatedImage {
    source: "loop.gif"
    playing: true
    fillMode: Image.PreserveAspectFit
}

AnimatedImage 的接口就三个关键点:playing 控制播停、currentFrame 能读能写、frameCount 拿总帧数。想做「拖拽到第 5 帧」也能做,但 GIF 的帧是整张位图,拖拽看到的仍然是 8 位色的锯齿边缘。

Lottie 的接口丰富得多,这是它真正的价值所在:

qml 复制代码
import Qt.labs.lottieqt

LottieAnimation {
    source: "animation.json"
    autoPlay: false
    loops: LottieAnimation.Infinite
    quality: LottieAnimation.MediumQuality
    direction: LottieAnimation.Forward

    onStatusChanged: {
        if (status === LottieAnimation.Ready)
            gotoAndPlay(startFrame)
    }
}

一次 gotoAndStop(87) 就能精确停在任意一帧,direction 一改动画就倒放,loops 换个数字就换循环次数------这些 GIF 都做不到。

选型上比较实用的判断:

  • 简单的无限循环装饰图,画幅小、颜色少、不需要控制 → GIF 够用,切图方便。
  • 需要半透明、渐变、放大不糊、指定帧 / 方向 / 循环,或者文件要瘦 → Lottie 更合适。
  • 素材里用了 Lottie 规范里相对冷门的能力(预合成层、矢量遮罩、Merge Paths、渐变描边、嵌套 Repeater、表达式)→ 先别急着上 Lottie,这些在 Qt 这边未必被支持,而且出问题时不报错,只是画得不对。下面第 2 节和第 3 篇会具体展开。

Qt 6.8 与 Qt 6.11 的 Lottie 实现差异

两版的导入语句完全一样:

qml 复制代码
import Qt.labs.lottieqt

也就是说升级 Qt 时 QML 代码一个字都不用改。但引擎换了,换得还挺彻底的。

模块结构:从一体插件到独立 Qt 模块

对比两版 Qt 安装目录里的东西:

Qt 6.8.2 Qt 6.11.1
QML 模块名 Qt.labs.lottieqt 同
QML 插件 lottieqtplugin.dll lottieplugin.dll
实际实现 插件自身(Bodymovin + QPainter ) Qt6Lottie.dll独立模块,另有 Qt6LottieVectorImageGenerator.dll、Qt6LottieVectorImageHelpers.dll
对外 C++ 模块 无 QtLottie、QtLottieVectorImageGenerator、QtLottieVectorImageHelpers
矢量渲染路线 无 lottietoqml 命令行、qt_target_qml_from_lottie() CMake 命令、VectorImage 运行时加载
帧缓存调节 无 QLOTTIE_RENDER_CACHE_SIZE 环境变量(默认 2)

6.8 的 290 KB 插件里塞着解析器和渲染器,6.11 的插件只剩 31 KB,实现放到了 Qt6Lottie.dll。Qt 6.11 的 LottieAnimation 类型原型仍然是 QQuickPaintedItem,但多出了一整套转成 Qt Quick 场景的能力。

日志分类换名是个隐形升级成本

如果你像我一样,用「数绘制帧」的方式做自动化验证:

powershell 复制代码
$env:QT_LOGGING_RULES = "qt.lottieqt.bodymovin.render=true"

这条在 6.11 下会一行日志都拿不到 。实测把 qt.lottieqt.* 全打开,6.11 下出现的分类是:

arduino 复制代码
qt.lottieqt.lottie.render          ← 绘制帧在这里
qt.lottieqt.lottie.render.thread
qt.lottieqt.lottie.parser
qt.lottieqt.lottie.update

bodymovin 这个名字在 6.11 里命中 0 行。消息体倒是没变,还是 Start to paint frame N,只是前面多了一串对象地址。改一个 QT_LOGGING_RULES 就能恢复,但不知道这件事的话很容易误判成「6.11 抓不到渲染日志」。

两条渲染路线

6.11 起,渲染 Lottie 有两种做法。

第一种是 LottieAnimation(两版都有):用 QPainter 渲染到中间缓冲。官方文档的说法是「这可能对插图的尺寸上限和目标硬件带来一些性能限制」。要注意一个反直觉的点:它的开销正比于素材固有尺寸,而不是显示尺寸------把 1200×1200 的素材缩到 177 px 显示,每帧仍然要光栅化 1200²。

第二种是 lottietoqml(6.11 新增):把 Lottie 转成用 Qt Quick(Shape 等)描述的 QML 场景,走硬件加速的 Scene Graph。官方原话是渲染可能更快、更省 CPU,而且 Qt Quick 支持可缩放矢量,转出来的 item 能平滑缩放。命令行直接转:

bash 复制代码
lottietoqml input.json output.qml

也可以挂进 CMake,编译期就转好:

cmake 复制代码
find_package(Qt6 REQUIRED COMPONENTS LottieTools)

qt_target_qml_from_lottie(myapp
    CURVE_RENDERER
    OPTIMIZE_PATHS
    FILES   original/fingerprint.json
    OUTPUTS generated/Fingerprint.qml
)

另外 6.11 的 VectorImage 支持在运行时直接加载 Lottie:

qml 复制代码
import QtQuick.VectorImage

VectorImage {
    source: "animation.json"
}

配套的 Qt.labs.lottieqt.VectorImageHelpers 模块(LayerItem 类型,提供组合了整条祖先链的 transformMatrix)是给这些生成代码用的,官方明确说「不打算当作通用组件使用」,所以平时不用碰它。

两版共有的限制清单

这部分很重要,它解释了为什么有些素材「能跑但看着不对」。官方限制清单在两版里都存在:

类别 不支持的项
通用 表达式;时间轴只支持帧模式,不支持时间模式
动画级 assets(可复用的文字与图片)、chars 文字
图层 ao 自动定向、bm 混合模式、maskProperties 矢量遮罩、sr 时间拉伸
形状 gstroke 渐变描边、嵌套 Repeater;多条 trim path 同时生效时行为不可预期
效果 只支持 Slide 与 Layer Fill

一个更隐蔽的差别:预合成层

上面的清单里没有「预合成层(precomp)」,但实测下来,这是两版差别最大的地方。

Lottie 里 ty: 0 的图层是预合成层,它引用 assets 里的另一套图层,相当于「把一段动画当成一个整体引用进来」。6.8 的解析器只认两种图层类型:

cpp 复制代码
// qt 6.8.2  src/qtlottie/src/bodymovin/bmlayer.cpp
BMLayer *BMLayer::construct(QJsonObject definition, const QVersionNumber &version)
{
    BMLayer *layer = nullptr;
    int type = definition.value(QLatin1String("ty")).toInt();
    switch (type) {
    case 2:
        layer = new BMImageLayer(definition, version);   // 图片层
        break;
    case 4:
        layer = new BMShapeLayer(definition, version);   // 形状层
        break;
    default:
        qCWarning(lcLottieQtBodymovinParser) << "Unsupported layer type:" << type;
    }
    return layer;
}

ty = 0(预合成)、ty = 3(空对象)、ty = 5(文字)全落进 default 分支,返回 nullptr,整层被丢弃,也不会递归展开它引用的子合成。运行时会看到这样的警告:

bash 复制代码
qt.lottieqt.bodymovin.parser: Unsupported layer type: 0

6.11 重写后的解析器处理了预合成层,同一份素材不再有这条警告,渲染结果也补全了。这一点在本篇最后的实测里有直接体现。

顺带一提,6.11 的 LottieAnimation 文档里仍然写着「只支持 Shape 层」,与实测不符------文档没跟上重写。以实测为准。

实测:4 个素材在两版下的渲染差异

光看文档不够,我实际跑了一遍对照。

对着这张对照图看更直观(左边是 6.8,右边是 6.11):

  • success动画没有什么差别,6.8 和 6.11 视觉效果一致。
  • 文档扫描动画,6.8 只剩一条横线,6.11 才能完整展示,差别来自预合成层被丢弃。
  • 沙漏动画,6.8 中只剩外壳,动画完全丢失;6.11 大致正常,但沙仍然漏到漏斗下方,展示出来的json文件是经过AI修复后的。
  • 删除文件动画,6.8 里的动画完全没有动;6.11 才能展示完整的动画过程。

官方提供的判断手段

6.11 的文档里给了一条很实用的建议:把日志分类打开观察帧缓存。如果缓存长期是满的,说明动画太复杂、渲染跟不上,应该简化动画或优化 QML 场景。

powershell 复制代码
$env:QT_LOGGING_RULES = "qt.lottieqt.lottie.render=true;qt.lottieqt.lottie.render.thread=true"

帧缓存的容量用 QLOTTIE_RENDER_CACHE_SIZE 调(默认 2),素材复杂时可以适当加大,代价是内存。

结论

新工程直接用 Qt 6.11,或者直接用最新的6.12 。预合成层能正常展开,日志分类更清晰,还多了 lottietoqml 这条硬件加速的矢量路线。

如果用的还是Qt6,老工程推荐升级到 6.11,QML 代码不用改,但要顺手改两处:验证脚本里的日志分类名,以及如果素材里用了预合成层,升级后观感会变(大概率是变好,但也要重新确认一遍)。

不要指望升级 Qt 就能修好所有素材问题。矢量遮罩、Merge Paths、渐变描边这些在两版里都不支持,属于素材层面的问题,得改素材本身。

相关推荐
时间的拾荒人5 小时前
Qt 界面美化实战:QSS 样式表
开发语言·qt·面试
qq_401700416 小时前
Qt 串口/网口通信:Hex 与 ASCII 编码转换深度指南
开发语言·数据库·qt
Python图像识别15 小时前
30-【2027毕设】YOLO11管道缺陷检测识别系统 - Python完整源码+PyQt5界面+训练模型+数据集
python·qt·课程设计
道尔柯南16 小时前
【QT】从零认识 Qt:跨平台 C++ GUI 开发入门
开发语言·c++·qt
MayZork16 小时前
std::filesystem 文件操作详解
开发语言·c++·qt
kaixin_learn_qt_ing17 小时前
[银河麒麟]因为没有安装串口相关驱动,导致无法检测到对应的串口,后来安装后,我的qt的相关接口就可以识别到了
qt
Cx330❀1 天前
Qt 多线程深度解析:从底层原理到 UI 线程与同步实战
开发语言·qt·ui·搜索引擎·性能优化·图形渲染
西西弗Sisyphus1 天前
Qt6 实现 会动的界面像物理世界一样
c++·qt
MayZork1 天前
Qt 开发集成CMake + vcpkg
开发语言·c++·qt