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 参数来控制顺时针或逆时针旋转。
基本用法:四种动画的典型写法
下面这段代码把四种动画放在同一个场景中:一个矩形在点击后向右移动、颜色变深、同时旋转 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 留给真正需要动态属性名的场景,可以让代码更易读、更易维护。