5.1.4 分組動畫
通常的動畫會比一個屬性的動畫更加復(fù)雜。我們可能希望同時運行多個動畫,或者一個接一個地運行動畫,有時又希望在兩個動畫之間執(zhí)行一個腳本什么的。此時,分組動畫為我們的上述需求提供了一種可能。正如這個名字所表達的那樣,我們可以對動畫進行分組。分組可以通過兩種方式進行:并行或順序。我們可以使用 ParallelAnimation(并行動畫) 或 SequentialAnimation(順序動畫) 元素,它們被用來充當(dāng)其他動畫元素的動畫容器。這些組合的動畫本身就是動畫,并且也可以被獨立地使用。

當(dāng)啟動時,并行動畫的所有直接子動畫將并行運行。這允許我們同時對不同的屬性應(yīng)用動畫。
// 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
}
}
}

順序動畫將首先運行第一個子動畫,然后繼續(xù)執(zhí)行接下來的每個子動畫。
// 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
}
}
}

分組動畫也可以是嵌套的,例如,一個順序的分組動畫可以包含兩個并行的分組動畫作為子動畫,等等。我們可以用一個足球的例子來把它形象化。它的思路是把球從左向右扔,并讓它動起來的行為。

為了理解動畫,我們需要將其分解為對象的整體轉(zhuǎn)換。我們需要記住動畫要做的是動態(tài)的屬性改變。以下是不同的轉(zhuǎn)換:
- 一個從左到右的 x 值的轉(zhuǎn)換(到達 X1)
- 一個 y 值從下到上(到達 Y1)的轉(zhuǎn)換,緊接著一個從上到下(到達 Y2)的跳躍轉(zhuǎn)換
- 在整個動畫持續(xù)時間內(nèi)旋轉(zhuǎn) 360 度(旋轉(zhuǎn)到 ROT1)
動畫的整個持續(xù)時間應(yīng)該是 3 秒。

我們從一個空的 Item 元素開始,它是寬為 480 和高為 300 的根元素。
import QtQuick 2.5
Item {
id: root
width: 480
height: 300
property int duration: 3000
...
}
我們已經(jīng)定義了整個動畫的持續(xù)時間作為參考,以更好地同步動畫部分。
下一步是添加背景,在我們的例子中是兩個矩形,綠色和藍色的漸變。
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()
}
}
}

圖像上有一個鼠標點擊區(qū)域。如果球被點擊,球的位置將被重置,動畫重新啟動。
讓我們先從兩個 y 值轉(zhuǎn)換的順序分組動畫開始。
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% 的動畫持續(xù)時間和后一個動畫 60% 的持續(xù)時間。一個接一個的動畫執(zhí)行作為一個順序分組動畫。轉(zhuǎn)換是在線性的路徑上動態(tài)執(zhí)行的,但是目前還沒有使用曲線效果。曲線效果將在稍后使用緩沖曲線屬性添加,此時我們只專注于使轉(zhuǎn)換動畫。
接下來,我們需要添加 x 值的轉(zhuǎn)換。 x 值的轉(zhuǎn)換應(yīng)該與 y 值的轉(zhuǎn)換同時運行,所以我們需要將執(zhí)行 y 值的轉(zhuǎn)換的順序分組動畫與 x 值的轉(zhuǎn)換動畫一起封裝到一個平行分組動畫中去。
ParallelAnimation {
id: anim
SequentialAnimation {
// ... our Y1, Y2 animation
}
NumberAnimation { // X1 animation
target: ball
properties: "x"
to: 400
duration: root.duration
}
}

最后,我們希望球旋轉(zhuǎn)起來。為此,我們需要向并行動畫添加另一個動畫。我們選擇旋轉(zhuǎn)動畫(RotationAnimation),因為它是專門用于處理元素旋轉(zhuǎn)的。
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 曲線,使球可以在結(jié)束時發(fā)生反彈(使用 Easing.InBounce 曲線的話你會看到反彈效果在開始時就會發(fā)生)。剩下的 X1 和 ROT1 動畫繼續(xù)使用線性曲線即可。
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
}
}
}