QML

PathAnimation: анимация по произвольной траектории

Введение

Обычные анимации позиции изменяют координаты по прямой линии между начальным и конечным значением. PathAnimation позволяет анимировать движение элемента вдоль произвольной, заранее заданной кривой траектории — дугой, петлёй, сложной кривой — что необходимо для эффектов вроде летящих по дуге уведомлений, элементов, огибающих препятствия на экране, или декоративных круговых движений.

Концепция

Траектория задаётся через объект Path, состоящий из последовательности сегментов — PathLine (прямая линия к точке), PathQuad/PathCubic (квадратичная/кубическая кривая Безье с одной или двумя контрольными точками), PathArc (дуга окружности) — PathAnimation затем анимирует движение элемента вдоль этого пути за заданное время, опционально также поворачивая элемент в соответствии с направлением движения по кривой (orientation).

Пример кода: PathAnimation

import QtQuick

Item {
    width: 400
    height: 400

    Rectangle {
        id: movingItem
        width: 30
        height: 30
        radius: 15
        color: "#dc2626"
    }

    // Описание дугообразной траектории "взлёта" уведомления —
    // от нижней части экрана к верхней с изгибом в сторону
    Path {
        id: flightPath
        startX: 50; startY: 350

        PathCubic {
            x: 350; y: 50
            control1X: 50; control1Y: 50    // первая контрольная точка тянет начало кривой вверх
            control2X: 350; control2Y: 350  // вторая контрольная точка тянет конец кривой влево-вниз
        }
    }

    PathAnimation {
        id: flightAnimation
        target: movingItem
        path: flightPath
        duration: 1200
        easing.type: Easing.InOutQuad
        orientationEntryDuration: 200 // плавный, а не мгновенный поворот в начале движения
    }

    MouseArea {
        anchors.fill: parent
        onClicked: flightAnimation.start()
    }
}
// Круговая траектория через PathArc — типичный паттерн для орбитального движения
Path {
    id: circularPath
    startX: 200; startY: 100

    PathArc {
        x: 200; y: 300
        radiusX: 100; radiusY: 100
        direction: PathArc.Clockwise
    }
    PathArc {
        x: 200; y: 100
        radiusX: 100; radiusY: 100
        direction: PathArc.Clockwise
    }
}

PathAnimation {
    target: orbitingItem
    path: circularPath
    duration: 3000
    loops: Animation.Infinite // непрерывное круговое движение без остановки
}

Пояснения к коду

flightPath определяет S-образную кубическую кривую Безье от нижнего левого угла к верхнему правому — две контрольные точки (control1X/Y, control2X/Y) определяют форму изгиба кривой, не являясь сами по себе точками, через которые кривая обязательно проходит, а скорее «магнитами», притягивающими траекторию в свою сторону — подбор этих точек требует некоторого экспериментирования для достижения визуально желаемой формы кривой. PathAnimation с orientationEntryDuration показывает дополнительную возможность — постепенный, а не мгновенный поворот анимируемого элемента в направлении движения по кривой в момент начала анимации, что выглядит более плавно, чем резкий мгновенный разворот.

Круговая траектория через два последовательных PathArc (каждый — полукруг) формирует полную окружность, по которой элемент движется бесконечно (loops: Animation.Infinite) — этот паттерн типичен для декоративных, постоянно повторяющихся орбитальных анимаций (вращающийся индикатор загрузки, орбитальное движение элемента вокруг центра).

Подводные камни

  • Подбор контрольных точек кривой Безье для достижения конкретной визуально желаемой формы траектории требует экспериментирования методом проб, не существует прямого, аналитического способа вычислить «правильные» значения контрольных точек для произвольной желаемой формы — для сложных, специфичных траекторий разработчики часто используют визуальные инструменты редактирования кривых (например, Qt Design Studio) вместо ручного подбора координат напрямую в коде.
  • PathAnimation определяет только саму траекторию движения, но не синхронизирует с ней независимо анимируемые другие свойства (например, прозрачность, появляющуюся и исчезающую по ходу движения) — для комбинированных эффектов (движение по дуге + изменение прозрачности) нужна отдельная параллельная анимация прозрачности через NumberAnimation внутри ParallelAnimation, синхронизированная по времени с PathAnimation, а не единый встроенный механизм.
  • Бесконечно повторяющаяся PathAnimation (loops: Animation.Infinite), запущенная, но не остановленная при скрытии или удалении соответствующего экрана/компонента, продолжает выполняться в фоне, потребляя ресурсы на постоянный пересчёт позиции и перерисовку, даже когда анимируемый элемент не виден пользователю — стоит явно останавливать такие бесконечные анимации при скрытии содержащего их экрана (через Component.onDestruction или явную логику видимости), а не предполагать автоматическую остановку.
  • Производительность сложных, многосегментных путей с большим количеством PathCubic/PathArc сегментов при частом запуске анимации (например, множественные одновременные летящие по сложной траектории элементы) может заметно нагружать вычисление позиции на каждом кадре — для большого числа одновременных анимаций по сложным путям стоит профилировать производительность отдельно от простых, линейных или эллиптических траекторий.