3. 动画系统概览

Qt Quick 的动画体系建立在“属性插值”这一核心思想之上:任何可被 QML 引擎识别的属性,只要具备数值、颜色或角度等可插值类型,就能在指定时间内从起始值平滑过渡到目标值。理解这一点,就能明白为什么 PropertyAnimation 是整套动画系统的“根”,而 NumberAnimation、ColorAnimation、RotationAnimation 都是它在特定数据类型上的专用化封装。

核心原理:从 PropertyAnimation 到专用动画

PropertyAnimation 是最通用的动画类型,它通过 target 指定目标对象,通过 property 指定要改变的属性名,再配合 from、to、duration、easing 等参数完成插值。它的优势是“什么属性都能动”,代价是属性名以字符串形式书写,编译器无法检查拼写错误,且对颜色、旋转等类型需要依赖运行时的类型转换。

NumberAnimation、ColorAnimation、RotationAnimation 则分别针对数值、颜色和旋转角度做了类型约束。它们继承自 PropertyAnimation,但把 property 的语义收窄到特定类型,从而在 IDE 提示、类型安全和可读性上更胜一筹。例如 ColorAnimation 内部按 RGBA 四通道分别插值,RotationAnimation 则额外支持 direction 参数来控制顺时针或逆时针旋转。

PropertyAnimation 通用属性插值 · target + property NumberAnimation 数值属性 · x / y / opacity / width ColorAnimation 颜色属性 · color / borderColor RotationAnimation 旋转属性 · angle / rotation 适用场景 位移、缩放、淡入淡出 进度条、数值滚动 适用场景 按钮状态切换 主题色渐变、告警闪烁 适用场景 加载指示器、仪表盘 图标旋转、翻牌效果 共同参数: duration · from · to · easing · loops · running · alwaysRunToEnd 专用参数: RotationAnimation.direction(顺时针 / 逆时针 / 最短路径)

基本用法:四种动画的典型写法

下面这段代码把四种动画放在同一个场景中:一个矩形在点击后向右移动、颜色变深、同时旋转 90 度。注意 NumberAnimation 与 PropertyAnimation 在写法上的差异,以及 ColorAnimation 和 RotationAnimation 如何通过专用属性名获得更好的可读性。

import QtQuick 2.15

Rectangle {
    id: box
    width: 120; height: 120
    color: "#4a90d9"
    radius: 12
    x: 40; y: 120

    // 数值动画:改变 x 坐标
    NumberAnimation on x {
        id: moveAnim
        to: 520
        duration: 800
        easing.type: Easing.InOutQuad
    }

    // 颜色动画:改变填充色
    ColorAnimation on color {
        id: colorAnim
        to: "#e05a3a"
        duration: 800
    }

    // 旋转动画:改变 rotation 属性
    RotationAnimation on rotation {
        id: rotAnim
        to: 90
        duration: 800
        direction: RotationAnimation.Clockwise
    }

    MouseArea {
        anchors.fill: parent
        onClicked: {
            moveAnim.start()
            colorAnim.start()
            rotAnim.start()
        }
    }
}

如果目标属性是自定义的数值属性,或者需要在运行时动态决定属性名,则可以使用 PropertyAnimation:

PropertyAnimation {
    target: box
    property: "opacity"
    from: 1.0
    to: 0.2
    duration: 400
    easing.type: Easing.OutCubic
}

适用场景与选型对照

选择哪种动画类型,本质上取决于目标属性的数据类型和语义。下表给出常见场景的推荐选型,可作为日常开发的速查清单。

动画类型 目标属性类型 典型场景 注意事项
PropertyAnimation 任意可插值属性 动态属性名、自定义属性、通用封装 属性名是字符串,拼写错误只在运行时暴露
NumberAnimation real / int 位移、缩放、透明度、进度值 整数属性插值会取整,可能产生跳变
ColorAnimation color 按钮状态、主题切换、告警闪烁 按 RGBA 通道插值,透明通道也会参与
RotationAnimation real(角度) 加载指示器、仪表盘、图标旋转 direction 决定旋转方向,默认走最短路径

案例:加载指示器的旋转动画

一个常见的加载指示器可以用 RotationAnimation 配合 loops: Animation.Infinite 实现。相比用 Timer 手动累加角度,RotationAnimation 由渲染线程驱动,帧率更稳定,也不会因为 JavaScript 执行阻塞而卡顿。

Item {
    width: 64; height: 64

    Rectangle {
        id: spinner
        width: 48; height: 48
        anchors.centerIn: parent
        radius: 24
        color: "transparent"
        border.width: 5
        border.color: "#2fa8a0"

        RotationAnimation on rotation {
            from: 0
            to: 360
            duration: 1000
            loops: Animation.Infinite
            running: true
        }
    }
}
当多个动画需要同时作用于同一对象时,推荐使用 ParallelAnimation 或 SequentialAnimation 进行组合,而不是分别调用 start()。组合动画能保证时序一致,也便于统一控制 running 和 loops。

误区与复盘

  • 误区一:用 NumberAnimation 动画颜色属性。颜色不是数值,NumberAnimation 无法正确插值,应改用 ColorAnimation。
  • 误区二:忽略 RotationAnimation 的 direction。默认行为是走最短路径,从 350 度到 10 度可能反向旋转,需要显式指定 Clockwise 或 Counterclockwise。
  • 误区三:在动画进行中频繁修改 from/to。这会导致插值基准被重置,产生跳变。应先停止动画,修改参数后再重新启动。
  • 误区四:对不可插值属性使用 PropertyAnimation。如 visible、enabled 等布尔属性无法平滑插值,动画会直接跳到终值。
动画的 duration 并非越短越好。过短的动画在低帧率设备上可能只渲染一两帧,用户感知为“闪一下”。建议交互反馈类动画保持在 150~300 毫秒,强调类动画可放宽到 400~800 毫秒。

掌握这四种动画类型的边界与配合方式,是后续学习状态机动画、Behavior 动画以及渲染性能优化的基础。在实际项目中,优先使用语义明确的专用动画类型,把 PropertyAnimation 留给真正需要动态属性名的场景,可以让代码更易读、更易维护。