5.1.4 分组动画
通常的动画会比一个属性的动画更加复杂。我们可能希望同时运行多个动画,或者一个接一个地运行动画,有时又希望在两个动画之间执行一个脚本什么的。此时,分组动画为我们的上述需求提供了一种可能。正如这个名字所表达的那样,我们可以对动画进行分组。分组可以通过两种方式进行:并行或顺序。我们可以使用 ParallelAnimation(并行动画) 或 SequentialAnimation(顺序动画) 元素,它们被用来充当其他动画元素的动画容器。这些组合的动画本身就是动画,并且也可以被独立地使用。
当启动时,并行动画的所有直接子动画将并行运行。这允许我们同时对不同的属性应用动画。
// parallelanimation.qml
import QtQuick 2.5
BrightSquare {
id: root
width: 600
height: 400
property int duration: 3000
property Item ufo: ufo
Image {
anchors.fill: parent
source: "assets/ufo_background.png"
}
ClickableImageV3 {
id: ufo
x: 20; y: root.height-height
text: 'ufo'
source: "assets/ufo.png"
onClicked: anim.restart()
}
ParallelAnimation {
id: anim
NumberAnimation {
target: ufo
properties: "y"
to: 20
duration: root.duration
}
NumberAnimation {
target: ufo
properties: "x"
to: 160
duration: root.duration
}
}
}
顺序动画将首先运行第一个子动画,然后继续执行接下来的每个子动画。
// sequentialanimation.qml
import QtQuick 2.5
BrightSquare {
id: root
width: 600
height: 400
property int duration: 3000
property Item ufo: ufo
Image {
anchors.fill: parent
source: "assets/ufo_background.png"
}
ClickableImageV3 {
id: ufo
x: 20; y: root.height-height
text: 'rocket'
source: "assets/ufo.png"
onClicked: anim.restart()
}
SequentialAnimation {
id: anim
NumberAnimation {
target: ufo
properties: "y"
to: 20
// 60% of time to travel up
duration: root.duration*0.6
}
NumberAnimation {
target: ufo
properties: "x"
to: 400
// 40% of time to travel sideways
duration: root.duration*0.4
}
}
}
分组动画也可以是嵌套的,例如,一个顺序的分组动画可以包含两个并行的分组动画作为子动画,等等。我们可以用一个足球的例子来把它形象化。它的思路是把球从左向右扔,并让它动起来的行为。
为了理解动画,我们需要将其分解为对象的整体转换。我们需要记住动画要做的是动态的属性改变。以下是不同的转换:
- 一个从左到右的 x 值的转换(到达 X1)
- 一个 y 值从下到上(到达 Y1)的转换,紧接着一个从上到下(到达 Y2)的跳跃转换
- 在整个动画持续时间内旋转 360 度(旋转到 ROT1)
动画的整个持续时间应该是 3 秒。
我们从一个空的 Item 元素开始,它是宽为 480 和高为 300 的根元素。
import QtQuick 2.5
Item {
id: root
width: 480
height: 300
property int duration: 3000
...
}
我们已经定义了整个动画的持续时间作为参考,以更好地同步动画部分。
下一步是添加背景,在我们的例子中是两个矩形,绿色和蓝色的渐变。
Rectangle {
id: sky
width: parent.width
height: 200
gradient: Gradient {
GradientStop { position: 0.0; color: "#0080FF" }
GradientStop { position: 1.0; color: "#66CCFF" }
}
}
Rectangle {
id: ground
anchors.top: sky.bottom
anchors.bottom: root.bottom
width: parent.width
gradient: Gradient {
GradientStop { position: 0.0; color: "#00FF00" }
GradientStop { position: 1.0; color: "#00803F" }
}
}
上面的蓝色矩形的高度为 200 像素,而下面的部分则是顶部被锚定在天空的底部并且底部被锚定在根元素上的。
让我们把球放到绿色的部分上。这个球是一个图像,存储在 “assets/soccer_ball.png” 中,大家也可以将上面的图片另存为我们自己的图像资源。开始的时候,我们把它放在左下角,靠近边缘的位置。
Image {
id: ball
x: 0; y: root.height-height
source: "assets/soccer_ball.png"
MouseArea {
anchors.fill: parent
onClicked: {
ball.x = 0;
ball.y = root.height-ball.height;
ball.rotation = 0;
anim.restart()
}
}
}
图像上有一个鼠标点击区域。如果球被点击,球的位置将被重置,动画重新启动。
让我们先从两个 y 值转换的顺序分组动画开始。
SequentialAnimation {
id: anim
NumberAnimation {
target: ball
properties: "y"
to: 20
duration: root.duration * 0.4
}
NumberAnimation {
target: ball
properties: "y"
to: 240
duration: root.duration * 0.6
}
}
上面的代码指定了前一个动画 40% 的动画持续时间和后一个动画 60% 的持续时间。一个接一个的动画执行作为一个顺序分组动画。转换是在线性的路径上动态执行的,但是目前还没有使用曲线效果。曲线效果将在稍后使用缓冲曲线属性添加,此时我们只专注于使转换动画。
接下来,我们需要添加 x 值的转换。 x 值的转换应该与 y 值的转换同时运行,所以我们需要将执行 y 值的转换的顺序分组动画与 x 值的转换动画一起封装到一个平行分组动画中去。
ParallelAnimation {
id: anim
SequentialAnimation {
// ... our Y1, Y2 animation
}
NumberAnimation { // X1 animation
target: ball
properties: "x"
to: 400
duration: root.duration
}
}
最后,我们希望球旋转起来。为此,我们需要向并行动画添加另一个动画。我们选择旋转动画(RotationAnimation),因为它是专门用于处理元素旋转的。
ParallelAnimation {
id: anim
SequentialAnimation {
// ... our Y1, Y2 animation
}
NumberAnimation { // X1 animation
// X1 animation
}
RotationAnimation {
target: ball
properties: "rotation"
to: 720
duration: root.duration
}
}
这就是整个动画序列。剩下的一件事就是为球的运动提供正确的缓冲曲线。对于 Y1 动画我们使用 Easing.OutCirc 曲线,这看起来更像一个圆形运动。Y2 的动画是一个增强效果,我们使用 Easing.OutBounce 曲线,使球可以在结束时发生反弹(使用 Easing.InBounce 曲线的话你会看到反弹效果在开始时就会发生)。剩下的 X1 和 ROT1 动画继续使用线性曲线即可。
ParallelAnimation {
id: anim
SequentialAnimation {
NumberAnimation {
target: ball
properties: "y"
to: 20
duration: root.duration * 0.4
easing.type: Easing.OutCirc
}
NumberAnimation {
target: ball
properties: "y"
to: root.height-ball.height
duration: root.duration * 0.6
easing.type: Easing.OutBounce
}
}
NumberAnimation {
target: ball
properties: "x"
to: root.width-ball.width
duration: root.duration
}
RotationAnimation {
target: ball
properties: "rotation"
to: 720
duration: root.duration
}
}
整个示例的完整代码如下所示:
import QtQuick 2.5
Item {
id: root
width: 640
height: 380
property int duration: 3000
Rectangle {
id: sky
width: parent.width
height: 200
gradient: Gradient {
GradientStop { position: 0.0; color: "#0080FF" }
GradientStop { position: 1.0; color: "#66CCFF" }
}
}
Rectangle {
id: ground
anchors.top: sky.bottom
anchors.bottom: root.bottom
width: parent.width
gradient: Gradient {
GradientStop { position: 0.0; color: "#00FF00" }
GradientStop { position: 1.0; color: "#00803F" }
}
}
Image {
id: ball
x: 0; y: root.height-height
source: "assets/soccer_ball.png"
MouseArea {
anchors.fill: parent
onClicked: {
ball.x = 0;
ball.y = root.height-ball.height;
ball.rotation = 0;
anim.restart()
}
}
}
ParallelAnimation {
id: anim
SequentialAnimation {
NumberAnimation {
target: ball
properties: "y"
to: 20
duration: root.duration * 0.4
easing.type: Easing.OutCirc
}
NumberAnimation {
target: ball
properties: "y"
to: root.height-ball.height
duration: root.duration * 0.6
easing.type: Easing.OutBounce
}
}
NumberAnimation {
target: ball
properties: "x"
to: root.width-ball.width
duration: root.duration
}
RotationAnimation {
target: ball
properties: "rotation"
to: 720
duration: root.duration
}
}
}