Введение
Lottie — популярный формат векторных анимаций (на основе JSON), экспортируемых из Adobe After Effects и широко используемых в мобильной и веб-разработке для сложных, профессионально созданных дизайнерами анимаций (анимированные иконки, иллюстрации, индикаторы состояния), которые было бы непрактично воссоздавать вручную через QML-примитивы. В статье разберём интеграцию Lottie-анимаций в Qt Quick приложение через сторонние библиотеки.
Концепция
Qt не предоставляет встроенной поддержки Lottie — для воспроизведения таких анимаций используются сторонние библиотеки, например rlottie (компактная, без зависимостей C++ библиотека рендеринга Lottie, разработанная изначально в Samsung) в сочетании с собственным тонким QML-компонентом-обёрткой, который использует rlottie для рендеринга каждого кадра анимации в QImage, а затем отображает результат как обычное QML-изображение, обновляемое по таймеру в соответствии с частотой кадров анимации.
Пример кода: Lottie-анимации в Qt Quick
# CMakeLists.txt — подключение rlottie как зависимости
find_package(rlottie REQUIRED)
target_link_libraries(MyApp PRIVATE rlottie::rlottie)
// lottieitem.h — QML-компонент, оборачивающий rlottie для использования в QML-сцене
#pragma once
#include <QQuickPaintedItem>
#include <QPainter>
#include <QTimer>
#include <rlottie.h>
#include <memory>
class LottieItem : public QQuickPaintedItem
{
Q_OBJECT
Q_PROPERTY(QString source READ source WRITE setSource NOTIFY sourceChanged)
Q_PROPERTY(bool playing READ isPlaying WRITE setPlaying NOTIFY playingChanged)
public:
explicit LottieItem(QQuickItem *parent = nullptr) : QQuickPaintedItem(parent)
{
connect(&m_frameTimer, &QTimer::timeout, this, &LottieItem::advanceFrame);
}
QString source() const { return m_source; }
void setSource(const QString &source)
{
m_source = source;
m_animation = rlottie::Animation::loadFromFile(source.toStdString());
if (m_animation) {
const double frameRate = m_animation->frameRate();
m_frameTimer.setInterval(static_cast<int>(1000.0 / frameRate));
m_totalFrames = static_cast<int>(m_animation->totalFrame());
}
emit sourceChanged();
update();
}
bool isPlaying() const { return m_playing; }
void setPlaying(bool playing)
{
m_playing = playing;
if (playing) {
m_frameTimer.start();
} else {
m_frameTimer.stop();
}
emit playingChanged();
}
void paint(QPainter *painter) override
{
if (!m_animation) return;
rlottie::Surface surface(
reinterpret_cast<uint32_t *>(m_buffer.bits()),
width(), height(), m_buffer.bytesPerLine());
m_animation->renderSync(m_currentFrame, surface);
painter->drawImage(0, 0, m_buffer);
}
private slots:
void advanceFrame()
{
m_currentFrame = (m_currentFrame + 1) % m_totalFrames; // цикличное воспроизведение
update(); // запрос на повторный вызов paint()
}
signals:
void sourceChanged();
void playingChanged();
private:
QString m_source;
std::unique_ptr<rlottie::Animation> m_animation;
QTimer m_frameTimer;
QImage m_buffer{width(), height(), QImage::Format_ARGB32_Premultiplied};
int m_currentFrame = 0;
int m_totalFrames = 1;
bool m_playing = false;
};
// Использование зарегистрированного C++ компонента в QML
import MyApp 1.0
LottieItem {
width: 200
height: 200
source: ":/animations/success-checkmark.json"
playing: true
}
Пояснения к коду
LottieItem наследует QQuickPaintedItem — базовый класс для QML-элементов с кастомной отрисовкой через QPainter, что подходит именно для случая, когда содержимое генерируется не стандартными QML-примитивами, а внешней библиотекой рендеринга. setSource() загружает Lottie-файл через rlottie::Animation::loadFromFile() и считывает частоту кадров и общее количество кадров анимации, используемые для настройки таймера и логики циклического воспроизведения.
paint() вызывается каждый раз, когда QML Scene Graph решает перерисовать элемент — внутри он создаёт rlottie::Surface, указывающую на буфер QImage, и просит rlottie синхронно отрендерить конкретный кадр анимации (m_currentFrame) непосредственно в этот буфер, после чего результат отрисовывается через обычный QPainter::drawImage(). advanceFrame(), вызываемый таймером с интервалом, соответствующим частоте кадров анимации, увеличивает текущий кадр (с цикличным переходом к началу через %) и вызывает update(), что запрашивает у Qt Quick повторный вызов paint() на следующем цикле рендеринга.
Подводные камни
- Синхронный рендеринг кадра (
renderSync) внутриpaint(), выполняемого в потоке рендеринга Qt Quick, может занимать заметное время для сложных Lottie-анимаций с большим количеством векторных слоёв — если рендеринг одного кадра занимает больше времени, чем доступный бюджет кадра (статья 274), это может привести к просадке FPS всего приложения, а не только самой анимации; для сложных Lottie-файлов стоит профилировать время рендеринга одного кадра отдельно. - Несовпадение частоты кадров Lottie-анимации (часто 24, 25 или 30 FPS, типичные для анимации, экспортированной из After Effects) с частотой обновления экрана (обычно 60 Гц) может создать едва заметную неравномерность темпа воспроизведения — для большинства декоративных анимаций эта разница незаметна, но для очень точных, синхронизированных с другими элементами интерфейса анимаций стоит явно учитывать эту разницу в частотах.
- Размер буфера
QImage, заданный единожды в инициализаторе члена класса (m_buffer{width(), height(), ...}) до того, как реальные размеры QML-элемента установлены через привязкиwidth/heightв QML, может привести к неверному, нулевому или некорректному начальному размеру буфера — для надёжной работы нужно явно пересоздавать буфер при изменении геометрии элемента (переопределяяgeometryChange()), а не полагаться только на единократную инициализацию в конструкторе. - Зависимость от стороннего, не входящего в сам Qt SDK пакета (
rlottie) требует отдельного управления этой зависимостью в системе сборки проекта (через vcpkg/Conan, основной цикл статей про CMake, статьи 33, 40) и означает, что обновления самой библиотекиrlottie(исправления багов, новые возможности формата Lottie) происходят независимо от цикла обновлений Qt, и команде нужно самостоятельно следить за этой зависимостью отдельно.