4. 动画控制与组合
在 Qt Quick 中,单个 PropertyAnimation 只能驱动一个属性在一段时间内变化。真实界面动效往往需要多个动画按顺序衔接、同时并行、中途停顿,甚至在特定时刻触发业务逻辑。Qt Quick 为此提供了四种“动画编排器”:SequentialAnimation(顺序)、ParallelAnimation(并行)、PauseAnimation(停顿)、ScriptAction(脚本动作)。它们本身不直接改变属性,而是作为容器或占位节点,把子动画组织成一条可预测的时间线。
一、核心原理:四种编排器的职责边界
理解它们的关键在于区分“时间轴控制”与“属性驱动”。PropertyAnimation、NumberAnimation 等是真正改变属性值的执行者;而 SequentialAnimation 和 ParallelAnimation 是时间轴容器,决定子动画是串行还是并行;PauseAnimation 是纯时间占位,不改变任何属性;ScriptAction 则在时间轴到达它的那一刻执行一段 JavaScript,常用于切换状态、打印日志或触发外部逻辑。
- SequentialAnimation:子动画依次执行,前一个结束后下一个才开始,总时长等于各子项之和。
- ParallelAnimation:所有子动画同时启动,总时长等于最长子项,适合“位移+缩放+透明度”同步变化。
- PauseAnimation:插入一段空白等待,duration 以毫秒计,常用于节奏控制。
- ScriptAction:在时间轴指定位置执行脚本,script 属性为要执行的 JS 代码。
四者可以任意嵌套:SequentialAnimation 里放 ParallelAnimation,ParallelAnimation 里放 PauseAnimation,从而构建出复杂但清晰的时间线。下面这张图展示了本节最典型的编排结构:一个顺序动画内部先并行执行位移与缩放,再停顿,最后触发脚本动作。
二、方法流程:如何组织一条可维护的时间线
编排动画时,建议遵循“先拆解、再分组、后嵌套”的流程。第一步,把动效拆成原子动作,例如“右移”“放大”“等待”“切换状态”。第二步,判断哪些动作必须同时发生,把它们放进 ParallelAnimation;哪些必须先后发生,放进 SequentialAnimation。第三步,用 PauseAnimation 调整节奏,用 ScriptAction 在关键节点插入逻辑。第四步,给动画设置 running 或 start()/stop(),并考虑是否使用 alwaysRunToEnd 保证停止时动画完整收尾。
SequentialAnimation {
id: cardAnim
running: false
ParallelAnimation {
NumberAnimation { target: card; property: "x"; to: 320; duration: 400; easing.type: Easing.OutCubic }
NumberAnimation { target: card; property: "scale"; to: 1.15; duration: 400 }
}
PauseAnimation { duration: 300 }
ScriptAction { script: root.state = "done" }
}
调用时只需 cardAnim.start()。若希望中途停止但让当前动画跑完,可设置 alwaysRunToEnd: true,这在用户快速点击、需要避免动画“跳变”的场景中非常有用。
三、实战案例:卡片展开动效
假设有一个卡片,点击后需要“向右移动并放大 → 停顿 300ms → 切换状态显示详情”。用 SequentialAnimation 包裹 ParallelAnimation、PauseAnimation 和 ScriptAction 即可清晰表达。相比用 Timer 手动串联,这种声明式编排更易读、易改、易复用。
Rectangle {
id: card
width: 120; height: 80; color: "#7fb3d5"
MouseArea {
anchors.fill: parent
onClicked: cardAnim.start()
}
SequentialAnimation {
id: cardAnim
ParallelAnimation {
NumberAnimation { target: card; property: "x"; to: 320; duration: 400; easing.type: Easing.OutCubic }
NumberAnimation { target: card; property: "scale"; to: 1.15; duration: 400 }
}
PauseAnimation { duration: 300 }
ScriptAction { script: console.log("动画完成,切换状态"); root.state = "done" }
}
}
四、误区与复盘
常见误区有三个。第一,把 SequentialAnimation 当成“同时执行”,实际上它是严格串行,若把两个位移动画放进去,会看到明显的先后顺序。第二,在 ParallelAnimation 中放入 PauseAnimation,误以为会“暂停整个并行组”,实际上它只是并行组中的一个占位分支,不会阻塞其他分支。第三,ScriptAction 中执行耗时操作,会阻塞渲染线程,导致动画卡顿,应只做轻量状态切换或信号发射。
| 编排器 | 执行方式 | 是否改变属性 | 典型用途 |
|---|---|---|---|
| SequentialAnimation | 串行 | 否(容器) | 多段动画依次衔接 |
| ParallelAnimation | 并行 | 否(容器) | 位移、缩放、透明度同步 |
| PauseAnimation | 占位等待 | 否 | 节奏控制、延迟触发 |
| ScriptAction | 瞬时执行 | 否 | 切换状态、发信号、打日志 |
复盘时可以用“时间轴三问”检查:总时长是否等于各段之和?并行段是否真的需要同时开始?脚本动作是否放在正确的时刻?把这三问答清楚,动画编排就从“能跑”走向“可控、可维护”。