注意:
最新的構(gòu)建時間:2016/03/21
這章的源代碼能夠在 Assets 找到。
到目前為止,我們主要關(guān)注的是簡單的圖形元素以及如何排列和操作它們。這一章講的是如何控制這些元素的變化,以一種屬性值的逐漸改變而不只是瞬間改變的方式,它更多的是隨著時間的變化而變化的,也就是:動畫。該技術(shù)是現(xiàn)代平滑用戶界面的關(guān)鍵基礎(chǔ)之一,可以通過使用狀態(tài)(states)和轉(zhuǎn)換(transitions)來描述用戶界面的系統(tǒng)進(jìn)行擴(kuò)展。每個狀態(tài)(state)定義一組屬性更改,并可以與狀態(tài)(state)更改的動畫組合在一起,稱為轉(zhuǎn)換(transition)。
5.1 動畫
動畫被應(yīng)用于屬性變化。當(dāng)屬性值發(fā)生變化時,動畫定義了插值曲線,以創(chuàng)建從一個值到另一個值的平滑的轉(zhuǎn)換效果。動畫是由一系列的目標(biāo)屬性來定義的,插值曲線具有緩動屬性(easing),在大多數(shù)情況下也會有一個持續(xù)時間(duration)屬性,它定義了屬性變化的時間。Qt Quick 中的所有動畫都是由相同的定時器控制的,因此它們是同步的。這有效地提高了動畫的性能和視覺質(zhì)量。
注意:
動畫控制屬性的變化,比如值的內(nèi)插。這是一個基本概念。QML 是基于元素、屬性和腳本編寫的。每個元素都提供了幾十個屬性,每個屬性都接受我們的自定義動畫。在本章中我們會發(fā)現(xiàn)這是一個瑰麗的秀場。我們會看到一些動畫,欣賞它們的美,當(dāng)然我們還可以用學(xué)到的知識嘗試我們自己的創(chuàng)意。請記?。?strong>動畫控制屬性的變化,每個元素都有幾十個屬性可以去處理。
開始表演!

// animation.qml
import QtQuick 2.5
Image {
id: root
source: "assets/background.png"
property int padding: 40
property int duration: 400
property bool running: false
Image {
id: box
x: root.padding;
y: (root.height-height)/2
source: "assets/box_green.png"
NumberAnimation on x {
to: root.width - box.width - root.padding
duration: root.duration
running: root.running
}
RotationAnimation on rotation {
to: 360
duration: root.duration
running: root.running
}
}
MouseArea {
anchors.fill: parent
onClicked: root.running = true
}
}
上面的例子向我們展示了一個應(yīng)用于 x 和 rotation 屬性的簡單動畫。每個動畫的持續(xù)時間為 400 毫秒和并且一直執(zhí)行。x 屬性的動畫將 x 坐標(biāo)從原來位置逐漸移動到右側(cè)。旋轉(zhuǎn)的動畫從當(dāng)前角度到 360 度。這兩個動畫都是并行運(yùn)行的,并且在我們點(diǎn)擊 UI 界面時就會啟動。
現(xiàn)在你可以通過改變動畫和時間屬性來播放動畫或者你可以添加另一個屬性動畫例如不透明度屬性,或者是縮放屬性。把這些結(jié)合起來,就可以看到我們元素在深空中消失了。試一下!
5.1.1 動畫元素
有幾種類型的動畫元素,每一種都針對特定的場景進(jìn)行了優(yōu)化。以下是一些最常用的動畫:
- PropertyAnimation —— 屬性值改變動畫
- NumberAnimation —— 數(shù)量值改變動畫
- ColorAnimation —— 顏色值改變動畫
- RotationAnimation —— 旋轉(zhuǎn)值改變動畫
除了這些基本的和經(jīng)常使用的動畫元素之外,Qt Quick 還為特定的使用場景提供了更多的專門的動畫元素:
- PauseAnimation —— 為動畫提供暫停效果
- SequentialAnimation —— 使動畫按照先后順序運(yùn)行
- ParallelAnimation —— 使動畫并行運(yùn)行
- AnchorAnimation —— 對錨值的變化運(yùn)行動畫效果
- ParentAnimation —— 對父對象的值的變化運(yùn)行動畫效果
- SmoothedAnimation —— 允許一個屬性平滑地跟蹤一個值的變化
- SpringAnimation —— 允許一個屬性跟蹤一個在做類似 spring 運(yùn)動的值
- PathAnimation —— 沿著路徑對一個元素運(yùn)行動畫效果
- Vector3dAnimation —— QVector3d 值改變動畫
稍后我們將學(xué)習(xí)如何創(chuàng)建一個動畫序列。在處理更復(fù)雜的動畫時,需要更改一個屬性,或者在一個正在進(jìn)行的動畫中運(yùn)行腳本。為此,Qt Quick 提供了動作(Action)元素,動作(Action)元素可以在任何使用動畫元素的地方使用:
- PropertyAction —— 在動畫中隨時進(jìn)行屬性的更改
- ScriptAction —— 定義在動畫中運(yùn)行的腳本
主要的動畫類型將在本章中使用小巧緊湊的例子進(jìn)行討論。
5.1.2 應(yīng)用動畫
動畫可以用幾種方式來應(yīng)用:
- Animation on property (屬性動畫) —— 它在元素完全載入后自動運(yùn)行
- Behavior on property (行為動畫) —— 當(dāng)屬性值發(fā)生變化時自動運(yùn)行
- Standalone Animation (獨(dú)立動畫)—— 顯示地調(diào)用 start() 或 running 屬性設(shè)置為 true (例如,通過屬性綁定)時,動畫將運(yùn)行。
稍后我們還將看到如何在狀態(tài)轉(zhuǎn)換中使用動畫。
擴(kuò)展 ClickableImage 版本 2。
為了演示動畫的使用,我們重用了我們在前面的章節(jié)中使用的 ClickableImage 組件,并將其擴(kuò)展為一個文本元素。
// ClickableImageV2.qml
// Simple image which can be clicked
import QtQuick 2.5
Item {
id: root
width: container.childrenRect.width
height: container.childrenRect.height
property alias text: label.text
property alias source: image.source
signal clicked
Column {
id: container
Image {
id: image
}
Text {
id: label
width: image.width
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
color: "#ececec"
}
}
MouseArea {
anchors.fill: parent
onClicked: root.clicked()
}
}
為了在圖像下面組織元素,我們使用了一個列定位器(Column),并根據(jù)列的孩子區(qū)域(childrenRect)屬性計算了寬度和高度。我們公開了兩個屬性: text 和圖像的 source 屬性,以及點(diǎn)擊的信號(clicked)。我們還希望文本和圖像一樣寬,并且它應(yīng)該有換行的功能。我們通過設(shè)置文本元素 wrapMode 屬性的值為 Text.WordWrap 來實現(xiàn)后者。
注意:
由于幾何形狀依賴關(guān)系的反轉(zhuǎn)(父元素的幾何形狀依賴于子元素的幾何形狀),因此我們不能在 ClickableImageV2 上設(shè)置寬度和高度的值,否則會破壞原有的綁定關(guān)系。這是我們針對這個示例內(nèi)部設(shè)計的一個限制,但是作為一個組件設(shè)計師,我們應(yīng)該有意識地避免這種情況的出現(xiàn)。正常情況下,我們應(yīng)該設(shè)計的是子元素的幾何形狀依賴父元素的幾何形狀的正常組件。
提升的對象示例:

// animationtypes.qml
import QtQuick 2.5
Item {
id: root
width: background.width; height: background.height
Image {
id: background
source: "assets/background_medium.png"
}
MouseArea {
anchors.fill: parent
onClicked: {
greenBox.y = blueBox.y = redBox.y = 205
}
}
ClickableImageV2 {
id: greenBox
x: 40; y: root.height-height
source: "assets/box_green.png"
text: "animation on property"
NumberAnimation on y {
to: 40; duration: 4000
}
}
ClickableImageV2 {
id: blueBox
x: (root.width-width)/2; y: root.height-height
source: "assets/box_blue.png"
text: "behavior on property"
Behavior on y {
NumberAnimation { duration: 4000 }
}
onClicked: y = 40
}
ClickableImageV2 {
id: redBox
x: root.width-width-40; y: root.height-height
source: "assets/box_red.png"
onClicked: anim.start()
text: "standalone animation"
NumberAnimation {
id: anim
target: redBox
properties: "y"
to: 40
duration: 4000
}
}
}
這三個物體都處于相同的 y 位置 (y=root.height-height)。他們需要把所有的 y 都移動到 y=40。他們每個人都使用不同的方法來產(chǎn)生不同的作用效果和特征。
第一個對象
ClickableImageV2 {
id: greenBox
x: 40; y: root.height-height
source: "assets/box_green.png"
text: "animation on property"
NumberAnimation on y {
to: 40; duration: 4000
}
}
第一個對象使用的是 Animation on <property> 策略。動畫立即開始,當(dāng)一個對象被單擊時,它們的 y 位置被重置到開始位置,這適用于所有對象。在第一個對象上,只要動畫運(yùn)行,重置就不會有任何效果。這樣讓人很不爽,因為 y 位置在動畫開始前的一小段時間內(nèi)被設(shè)置為一個新的值。應(yīng)該避免這種相互競爭的屬性變化。
第二個對象
ClickableImageV2 {
id: blueBox
x: (root.width-width)/2; y: root.height-height
source: "assets/box_blue.png"
text: "behavior on property"
Behavior on y {
NumberAnimation { duration: 4000 }
}
onClicked: y = 40
// random y on each click
// onClicked: y = 40+Math.random()*(205-40)
}
第二個對象使用的是 Behavior on <property> 策略。這個行為告訴屬性,每次屬性值改變時,它都會通過這個動畫來改變??梢越迷撔袨橥ㄟ^在行為(Behavior)元素上使用 enabled : false 的屬性設(shè)置。當(dāng)我們點(diǎn)擊它時,對象將開始移動(y 位置將被設(shè)置為 40)。另一個點(diǎn)擊沒有影響,因為位置已經(jīng)設(shè)置好了。我們可以嘗試使用一個隨機(jī)值(例如 40+(math.random()(205-40)) 設(shè)置 y 位置。我們將看到,對象將始終對新位置進(jìn)行動畫,并調(diào)整其速度以匹配動畫持續(xù)時間所定義的 4 秒。
第三個對象
ClickableImageV2 {
id: redBox
x: root.width-width-40; y: root.height-height
source: "assets/box_red.png"
onClicked: anim.start()
// onClicked: anim.restart()
text: "standalone animation"
NumberAnimation {
id: anim
target: redBox
properties: "y"
to: 40
duration: 4000
}
}
第二個對象使用的是 standalone animation 策略。動畫被定義為它自己的元素的對象,并且可以放在文檔的任何地方。單擊的時候?qū)⒄{(diào)用動畫的 start() 函數(shù)啟動動畫。每個動畫都有一個 start()、stop()、resume()、restart() 函數(shù)。動畫自身的形式可以比其他類型的動畫更早的獲取到更多的相關(guān)信息。我們需要定義目標(biāo)(target)和屬性(properties)來聲明要激活的目標(biāo)元素和我們想要激活的屬性。我們需要定義一個 to 值,在上面的示例中,我們也可以定義一個 from 值來允許重新啟動動畫。

單擊背景將把所有對象重置為初始位置。第一個對象不能重新啟動,除非重新啟動觸發(fā)元素重新加載的程序。
注意:
啟動/停止動畫的另一種方法是將一個屬性(property)綁定到一個動畫的運(yùn)行(running)屬性。當(dāng)需要用戶輸入(user-input)控制屬性時,這一點(diǎn)特別有用:
NumberAnimation {
...
// animation runs when mouse is pressed
running: area.pressed
}
MouseArea {
id: area
}
5.1.3 緩沖曲線(Easing Curves)
我們已經(jīng)知道,屬性值的改變能夠通過動畫來控制。緩沖曲線(Easing Curves)屬性用來調(diào)整一個屬性值改變的插值算法。我們上面示例中已經(jīng)定義的動畫都是使用的線性的插值算法,因為動畫的默認(rèn)緩沖曲線類型是 Easing.Linear。它最好是用一個小的圖形來顯示,其 y 軸表示動畫的屬性,x 軸表示持續(xù)時間。線性插值可以從動畫開始時的 from 值到動畫結(jié)束時的 to 值繪制一條直線。因此,緩沖類型定義了變化曲線??梢酝ㄟ^選擇合適的緩沖類型使被移動對象的移動效果看起來更自然,例如當(dāng)頁面滑出時。一開始,頁面應(yīng)該慢慢地滑動,然后逐漸加速,最終以高速度滑出,類似于翻書時頁面呈現(xiàn)出來的效果。
注意:
動畫不應(yīng)該被過度地使用。在 UI 設(shè)計的其他方面,動畫也應(yīng)該精心設(shè)計,用于支持 UI 流,而不是控制它。眼睛對移動物體非常敏感,動畫會很容易干擾用戶的注意力。
在下一個例子中,我們將嘗試一些緩沖曲線。每個緩沖曲線都由一個可點(diǎn)擊的圖像顯示,當(dāng)點(diǎn)擊時,將在方塊(square)動畫中設(shè)置一個新的緩沖類型,然后觸發(fā)一個 restart() 以使用新的曲線來運(yùn)行動畫。

這個示例的代碼稍微復(fù)雜一些。我們首先創(chuàng)建一個緩沖類型(EasingTypes)的網(wǎng)格和一個由緩沖類型控制的框(box)。緩沖類型只是顯示了該框用于動畫的曲線。當(dāng)用戶點(diǎn)擊“緩沖曲線”時,盒子就會按照“放松曲線”的方向移動。動畫本身是一個標(biāo)準(zhǔn)的動畫,將目標(biāo)設(shè)置為 box,并為 x-property 動畫配置了一個持續(xù)時間為 2 秒的動畫。
注意:
EasingType 的內(nèi)部結(jié)構(gòu)會在實時呈現(xiàn)曲線,感興趣的讀者可以在 EasingTypesExample 示例中查看它。
// easingtypes.qml
import QtQuick 2.5
DarkSquare {
id: root
width: 600
height: 340
// A list of easing types
property variant easings : [
"Linear", "InQuad", "OutQuad", "InOutQuad",
"InCubic", "InSine", "InCirc", "InElastic",
"InBack", "InBounce" ]
Grid {
id: container
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
anchors.margins: 16
height: 200
columns: 5
spacing: 16
// iterates over the 'easings' list
Repeater {
model: easings
ClickableImageV3 {
framed: true
// the current data entry from 'easings' list
text: modelData
source: "curves/" + modelData + ".png"
onClicked: {
// set the easing type on the animation
anim.easing.type = modelData
// restart the animation
anim.restart()
}
}
}
}
// The square to be animated
GreenSquare {
id: square
x: 40; y: 260
}
// The animation to test the easing types
NumberAnimation {
id: anim
target: square
from: 40; to: root.width - 40 - square.width
properties: "x"
duration: 2000
}
}
上面實例中我們使用了 ClickableImageV3 其源代碼如下:
// ClickableImageV2.qml
// Simple image which can be clicked
import QtQuick 2.5
Item {
id: root
width: container.childrenRect.width + 16
height: container.childrenRect.height + 16
property alias text: label.text
property alias source: image.source
signal clicked
// M1>>
// ... add a framed rectangle as container
property bool framed : false
Rectangle {
anchors.fill: parent
color: "white"
visible: root.framed
}
// <<M1
Column {
x: 8; y: 8
id: container
Image {
id: image
}
Text {
id: label
width: image.width
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
color: "#e0e0e0"
}
}
MouseArea {
anchors.fill: parent
onClicked: root.clicked()
}
}
當(dāng)我們運(yùn)行這個實例的時候,請在動畫運(yùn)行時觀察方塊速度的變化。有些動畫效果對方塊來說更自然,有些則感覺很突兀。
除了 duration 和 easing.type 我們還可以對動畫進(jìn)行微調(diào)。例如 PropertyAnimation 動畫,它有大多數(shù)動畫都支持的 easing.amplitude(振幅)、easing.overshoo(溢出) 和 easing.period(周期) 屬性,允許我們對特定的緩沖曲線的行為進(jìn)行微調(diào)。當(dāng)然并不是所有的緩沖曲線都支持這些參數(shù)??梢圆榭?Qt PropertyAnimation 文檔來了解更詳細(xì)的內(nèi)容。
注意:
在用戶界面上下文中選擇適當(dāng)?shù)膭赢媽τ诮Y(jié)果是至關(guān)重要的。記住,動畫應(yīng)該支持 UI 效果流暢;而不是畫蛇添足使用戶厭煩。