在學習了MouseArea和Text之後,這一節開始學習image相關的知識。
和上一節QT Demo 之 text一樣,imageelements的入口也是一個LauncherList,然後添加了5個子example,下面我就針對每一個子example進行詳細分析。
borderimage.qml
首先看到的是borderimage.qml的主體結構是由一個BorderImageSelector和Flickable組成的:
Rectangle {
id: page
width: 320
height: 480
BorderImageSelector {...}
Flickable {...}
}
BorderImageSelector
這裏的BorderImageSelector是一個自定義的Component,由content/BorderImageSelector.qml文件定義。
從Demo的運行效果圖上可以看到BorderImageSelector實際上是windows上部的左右導航按鈕,如下圖所示:
查看BorderImageSelector.qml的代碼,也可以看到有一個有兩個左右的按鈕圖片、一個按鈕相應函數和一個使用Repeater來顯示的文本:
Item {
id: selector
property int curIdx: 0
property int maxIdx: 3
property int gridWidth: 240
property Flickable flickable
width: parent.width
height: 64
function advance(steps) {...}
Image {...}
Image {...}
Repeater {...}
}
兩個左右的按鈕,使用的是同一張圖片,只不過一個做了鏡像;
按鈕的點擊操作也是一樣,一個向左一個向右(調用selector.advance()函數,傳遞不同的參數來實現);
至於兩個按鈕的透明度,則是根據當前的是否是最左(或最右)來判斷後,進行設置,以便給用戶進行操作提示;
Image {
source: "arrow.png"
MouseArea{
anchors.fill: parent
onClicked: selector.advance(-1)
}
anchors.left: parent.left
anchors.leftMargin: 8
anchors.verticalCenter: parent.verticalCenter
opacity: selector.curIdx == 0 ? 0.2 : 1.0
Behavior on opacity {NumberAnimation{}}
}
Image {
source: "arrow.png"
mirror: true
MouseArea{
anchors.fill: parent
onClicked: selector.advance(1)
}
opacity: selector.curIdx == selector.maxIdx ? 0.2 : 1.0
Behavior on opacity {NumberAnimation{}}
anchors.right: parent.right
anchors.rightMargin: 8
anchors.verticalCenter: parent.verticalCenter
}
advance()函數,則是通過改變curIdx的值影響兩個Image以及Repeater的顯示效果,通過改變flickable.contentX的值,改變外面的Flickable顯示效果:
function advance(steps) {
var nextIdx = curIdx + steps
if (nextIdx < 0 || nextIdx > maxIdx)
return;
flickable.contentX += gridWidth * steps;
curIdx += steps;
}
最後的Repeater則是通過給定的數據([ "Scale", "Repeat", "Scale/Repeat", "Round" ]),按照指定的方式(delegate屬性描述)進行多個item的顯示,這裏分別是設定了text的x座標以及透明度參數: Repeater {
model: [ "Scale", "Repeat", "Scale/Repeat", "Round" ]
delegate: Text {
text: model.modelData
anchors.verticalCenter: parent.verticalCenter
x: (index - selector.curIdx) * 80 + 140
Behavior on x { NumberAnimation{} }
opacity: selector.curIdx == index ? 1.0 : 0.0
Behavior on opacity { NumberAnimation{} }
}
}
注:在BorderImageSelector.qml中多次使用了類似下面的的動畫效果:
Behavior on opacity {NumberAnimation{}}
因爲這裏NumberAnimation{}動畫是定義在Behavior中,所以其默認的from就是其屬性變化的開始值(如上面的1.0),其默認的to就是其屬性變化的結束值(如上面的0.0),duration的默認值是250ms。
Flickable子元素
該示例中除了上面的導航條,剩餘的元素都放到了Flickable元素中,並且按照Grid的方式進行排列:
Flickable {
id: mainFlickable
width: parent.width
anchors.bottom: parent.bottom
anchors.top: selector.bottom
interactive: false //Animated through selector control
contentX: -120
Behavior on contentX { NumberAnimation {}}
contentWidth: 1030
contentHeight: 420
Grid {...}
}
注1:contentHeight的值是420,大小和整體的高度(480)減去導航條的高度(64)差不多,而contentWidth的值是1030,大概是整體寬度(320)的3倍多;
注2:在運行的程序中,我們發現無法使用鼠標進行水平方向的拖動,這與Flickable的特性衝突。這裏的訣竅就是interactive: false屬性,如註釋中所說,該Flickable通過selector來控制
Flickable中共有8個MyBorderImage排列在一個Grid組中:
Grid {
anchors.centerIn: parent; spacing: 20
MyBorderImage {}
MyBorderImage {}
MyBorderImage {}
MyBorderImage {}
MyBorderImage {}
MyBorderImage {}
MyBorderImage {}
MyBorderImage {}
}
Grid的寬度和父元素的寬度一致,各個MyBorderImage元素之間的間距是20
MyBorderImage組件
MyBorderImage定義在:/imageelements/content/MyBorderImage.qml文件中。
Item {
id: container
property alias horizontalMode: image.horizontalTileMode
property alias verticalMode: image.verticalTileMode
property alias source: image.source
property int minWidth
property int minHeight
property int maxWidth
property int maxHeight
property int margin
width: 240; height: 200
BorderImage {...}
}
從MyBorderImage的代碼中可以看出,一個MyBorderImage包含:最大/最小寬度、最大/最小高度、外邊距,以及水平/垂直拉伸/平鋪模式和圖片的數據源,而調用出的代碼也是分別設置不同的屬性來演示不同的拉伸/平鋪效果。
BorderImage {
id: image; anchors.centerIn: parent
SequentialAnimation on width {}
SequentialAnimation on height {}
border.top: container.margin
border.left: container.margin
border.bottom: container.margin
border.right: container.margin
}
BorderImage的官方說明如下:
The BorderImage element provides an image that can be used as a border.
The BorderImage element is used to create borders out of images by scaling or tiling parts of each image.
A BorderImage element breaks a source image, specified using the url property, into 9 regions, as shown below:
When the image is scaled, regions of the source image are scaled or tiled to create the displayed border image in the following way:
Regions 2 and 8 are scaled according to horizontalTileMode.
Regions 4 and 6 are scaled according to verticalTileMode.
The middle (region 5) is scaled according to both horizontalTileMode and verticalTileMode.
使用一個BorderImage,只需要設置width、height、source,以及border屬性和horizontalTileMode/verticalTileMode屬性即可,但是爲了演示BorderImage拉伸和平鋪的具體動畫效果,分別添加了在width和height上的動畫效果。
SequentialAnimation on width {
loops: Animation.Infinite
NumberAnimation {
from: container.minWidth; to: container.maxWidth
duration: 2000; easing.type: Easing.InOutQuad
}
NumberAnimation {
from: container.maxWidth; to: container.minWidth
duration: 2000; easing.type: Easing.InOutQuad
}
}
上述動畫的意思是,在2s的時間內,BorderImage的width從minWidth變到maxWidth,然後再使用2s的時間變回來。即完整的演示BorderImage改變拉伸/平鋪的具體過程,而在height上的動畫效果和在width上的類似,此處不再展開。BorderImage的horizontalTileMode/verticalTileMode屬性共有三種取值,分別如下:
- BorderImage.Stretch - Scales the image to fit to the available area.
- BorderImage.Repeat - Tile the image until there is no more space. May crop the last image.
- BorderImage.Round - Like Repeat, but scales the images down to ensure that the last image is not cropped.
分別的意思就是:拉伸、平鋪和完整的(就是通過拉伸和平鋪配合保證不出現裁邊,且效果OK)這三種方式。
注意:其中拉伸的處理和android中的.9.png格式的圖片渲染效果是一樣的,也就是說BorderImage中的拉伸和平鋪都是基於類似.9.png效果的、只針對於中間區域的拉伸/平鋪、而保證四個邊角不變的一種圖像處理。
image.qml
image.qml中的例子比較簡單,就是演示了圖片的幾種fillMode的不同效果:
Rectangle {
width: 320
height: 480
Grid {
property int cellWidth: (width - (spacing * (columns - 1))) / columns
property int cellHeight: (height - (spacing * (rows - 1))) / rows
anchors.fill: parent
anchors.margins: 30
columns: 2
rows: 3
spacing: 30
ImageCell { mode: Image.Stretch; caption: "Stretch" }
ImageCell { mode: Image.PreserveAspectFit; caption: "PreserveAspectFit" }
ImageCell { mode: Image.PreserveAspectCrop; caption: "PreserveAspectCrop" }
ImageCell { mode: Image.Tile; caption: "Tile" }
ImageCell { mode: Image.TileHorizontally; caption: "TileHorizontally" }
ImageCell { mode: Image.TileVertically; caption: "TileVertically" }
}
}
上面的cellWidth和cellHeight是通過設置的columns、rows和spacing來計算出來的,即Grid中每一個Item的大小,其值用於ImageCell的width和height中;而columns、rows和spacing則是Grid的屬性變量。
這裏的ImageCell也是一個自定義的Component,由:/imageelements/content/ImageCell.qml文件描述。
Item {
property alias mode: image.fillMode
property alias caption: captionItem.text
width: parent.cellWidth; height: parent.cellHeight
Image {
id: image
width: parent.width; height: parent.height - captionItem.height
source: "qt-logo.png"
clip: true // only makes a difference if mode is PreserveAspectCrop
}
Text {
id: captionItem
anchors.horizontalCenter: parent.horizontalCenter; anchors.bottom: parent.bottom
}
}
從代碼中可以看出,該ImageCell是由一個Image和一個Text組成,Image的fileMode和Text的text都是由外面的調用處指定,如:
ImageCell { mode: Image.Stretch; caption: "Stretch" }
ImageCell { mode: Image.PreserveAspectFit; caption: "PreserveAspectFit" }
ImageCell { mode: Image.PreserveAspectCrop; caption: "PreserveAspectCrop" }
Image的fillMode屬性是一個枚舉變量,支持以下數值:
- Image.Stretch - the image is scaled to fit(水平和垂直都放大縮小的外邊框)
- Image.PreserveAspectFit - the image is scaled uniformly to fit without cropping(保持比例進行縮放)
- Image.PreserveAspectCrop - the image is scaled uniformly to fill, cropping if necessary(保持比例進行縮放,但是爲了填滿空間,會對多出來的部分進行裁剪)
- Image.Tile - the image is duplicated horizontally and vertically(水平和垂直方向都會平鋪)
- Image.TileVertically - the image is stretched horizontally and tiled vertically(垂直方向平鋪)
- Image.TileHorizontally - the image is stretched vertically and tiled horizontally(水平方向平鋪)
- Image.Pad - the image is not transformed(保持不變)
上面的幾種填充模式,默認的是Image.Stretch,如果要保持圖片不變,需要自己設置爲Image.Pad。
shadows.qml
shadows示例中主要也是演示了BorderImage的特性,只不過通過設置anchors的Margin參數爲負值,來實現立體投影的效果:
//! [shadow]
BorderImage {
anchors.fill: rectangle
anchors { leftMargin: -6; topMargin: -6; rightMargin: -8; bottomMargin: -8 }
border { left: 10; top: 10; right: 10; bottom: 10 }
source: "shadow.png"
}
//! [shadow]
因爲這個示例中沒有什麼新鮮知識,不再詳述。animatedsprite.qml
這一個示例重點演示瞭如何使用AnimatedSprite來進行動畫演示,其中MouseArea裏面的操作也是針對AnimatedSprite的功能進行演示:
Item {
width: 320
height: 480
Rectangle {
anchors.fill: parent
color: "white"
}
//! [sprite]
AnimatedSprite {...}
//! [sprite]
MouseArea {...}
}
AnimatedSprite簡述
AnimatedSprite provides rendering and control over animations which are provided as multiple frames in the same image file. You can play it at a fixed speed, at the frame rate of your display, or manually advance and control the progress..
從官方說明中可以瞭解到,這是一種通過顯示圖片中不同部分(反過來看,即把一個動畫的所有幀都保存在一張圖片上)來顯示一個動畫效果的組件。如本例中的圖片(原圖片太大,此處只顯示了第一行的5幀):
示例中的AnimatedSprite代碼如下:
//! [sprite]
AnimatedSprite {
id: sprite
width: 170
height: 170
anchors.centerIn: parent
source: "content/speaker.png"
frameCount: 60
frameSync: true
frameWidth: 170
frameHeight: 170
loops: 3
}
//! [sprite]
通過查看sourece指定的content/speaker.png圖片,我們可以看到其長寬分別爲850*2040,對應與上面的frameWidth和frameHeight都是170,正好是5*12即frameCount的值。
frameSync的說明是這樣的:
If true, then the animation will have no duration. Instead, the animation will advance one frame each time a frame is rendered to the screen. This synchronizes it with the painting rate as opposed to elapsed time.
對於the animation will advance one frame each time a frame is rendered to the screen比較好理解,就是一幀渲染後緊接這渲染下一幀;但是對於This synchronizes it with the painting rate as opposed to elapsed time這句話就完全高度不懂了,正所謂,每個單詞的意思都懂,但是連在一起就不知道要表達什麼了。大概的意思,我理解的是繪畫的速度是相對於渲染速度,而不是按照時間,也就是說如果CPU計算能力強,那麼就顯示的時間短,如果CPU雲算能力太爛,那就慢慢渲染吧。
鼠標操作部分則是演示了AnimatedSprite的幾個屬性(running、paused)和幾個函數操作(start()、pause()、advance()):
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
if (!sprite.running)
sprite.start();
if (!sprite.paused)
sprite.pause();
if ( mouse.button == Qt.LeftButton ) {
sprite.advance(1);
} else {
sprite.advance(-1);
}
}
}
這裏的鼠標左右鍵操作分別是顯示向前和向後的一幀,如果我們需要完成左鍵正轉、右鍵反轉的效果,則可以修改代碼如下所示:
onClicked: {
if ( mouse.button == Qt.LeftButton ) {
sprite.reverse = false;
sprite.restart();
} else {
sprite.reverse = true;
sprite.restart();
}
}
spritesequence.qml
這個示例也是展示了一個動畫效果,不過採用的方法和上一個示例有所不同,先看一下源文件的主體結構:
Item {
width: 320
height: 480
MouseArea {...}
//! [animation]
SequentialAnimation {...}
//! [animation]
SpriteSequence {...}
}
其中SpriteSequence和上一個示例中學到的AnimatedSprite都是屬於QML Types中的Visual Types,但是它和AnimatedSprite的使用上有非常大的差異。
SpriteSequence簡述
SpriteSequence renders and controls a list of animations defined by Sprite types.
從官方說明上來看,一個SpriteSequence是有多個Sprite來組成的並通過一定的順序渲染到UI上,這裏的例子也可以看的出:
SpriteSequence {
id: image
width: 256
height: 256
anchors.horizontalCenter: parent.horizontalCenter
interpolate: false
goalSprite: ""
//! [still]
Sprite{}
//! [still]
Sprite{}
Sprite{}
Sprite{}
Sprite{}
}
其中,interpolate參數指定了在進行動畫渲染時不需要進行插值處理(如果設置爲true,實際效果並不好,此處是希望完全按照Sprite的方式進行動畫演示),goalSprite屬性設置爲空,但是在SequentialAnimation中會改變其值。
剩下的五個Sprite,則依次描述了幾個Sprite(可以翻譯成爲調皮鬼),然後再通過MouseArea的操作,啓動SequentialAnimation動畫,進一步通過修改SpriteSequence的屬性,完成整個動畫效果。分開來講就是:
第一個Sprite,frameCount是1,frameX和frameY屬性沒有設置,默認都是0,那麼取到的就是圖片中的第一幀:
Sprite{
name: "still"
source: "content/BearSheet.png"
frameCount: 1
frameWidth: 256
frameHeight: 256
frameDuration: 100
to: {"still":1, "blink":0.1, "floating":0}
}
第二個Sprite,frameCount是3,frameX和frameY分別是256和1536,那麼就是圖片中的最後三幀:
Sprite{
name: "blink"
source: "content/BearSheet.png"
frameCount: 3
frameX: 256
frameY: 1536
frameWidth: 256
frameHeight: 256
frameDuration: 100
to: {"still":1}
}
除了frameCount、frameX和frameY這三個參數,還有兩個參數需要重點關心:
frameDuration:still的Sprite只有1幀,而blink的Sprite卻有3幀,但是他們的frameDuration都是100,這樣動畫的效果就會是小熊眨眼的動作很快;
to:這個字段表示了當前Sprite動畫完成後,下一個進行的Sprite。blink的to字段只有still,則blink動畫完成後肯定會顯示still動畫,但是still的to字段是{"still":1, "blink":0.1, "floating":0},則表明有10/11的概率會繼續顯示still,而只有1/11的概率纔會顯示blink,這兩個Sprite配合起來的效果就是小熊半天才會眨一次眼,但是眨眼的動畫會很快,而且因爲這兩個的Sprite的to字段沒有跳出他們兩個,那麼如果沒有外界觸發,則會一直顯示這兩個動畫。
第三個Sprite的名字是floating,意思是飄動的,結合frameX和frameY都是0,frameCount是9,則是下述的幾幀動畫:
Sprite{
name: "floating"
source: "content/BearSheet.png"
frameCount: 9
frameX: 0
frameY: 0
frameWidth: 256
frameHeight: 256
frameDuration: 160
to: {"still":0, "flailing":1}
}
第四個Sprite,名稱是flailing,意思是揮舞,結合frameX和frameY分別是0和768,frameCount是8,則是下述的幾幀動畫:
Sprite{
name: "flailing"
source: "content/BearSheet.png"
frameCount: 8
frameX: 0
frameY: 768
frameWidth: 256
frameHeight: 256
frameDuration: 160
to: {"falling":1}
}
最後一個Sprite,其名稱是falling,意思是下落,結合frameX和frameY分別是0和1280,frameCount是5,則是下述的幾幀動畫:
Sprite{
name: "falling"
source: "content/BearSheet.png"
frameCount: 5
frameY: 1280
frameWidth: 256
frameHeight: 256
frameDuration: 160
to: {"falling":1}
}
在瞭解了每一個Sprite的具體圖像和意義後,我們需要仔細分析一下他們的to字段,它們之間的關係可以用下圖描述:
其中虛線表示,不會自動進行動畫跳轉,但是如果通過設置goalSprite屬性,則可以通過虛線路徑進行動畫跳轉,而本示例中也是利用這一點,在鼠標事件中進行了處理。
下面就看看鼠標事件和觸發的動畫的代碼:
MouseArea {
onClicked: anim.start();
anchors.fill: parent
}
//! [animation]
SequentialAnimation {
id: anim
ScriptAction { script: image.goalSprite = "falling"; }
NumberAnimation { target: image; property: "y"; to: 480; duration: 12000; }
ScriptAction { script: {image.goalSprite = ""; image.jumpTo("still");} }
PropertyAction { target: image; property: "y"; value: 0 }
}
//! [animation]
從代碼中可以看到,鼠標的點擊事件觸發了anim動畫的開始,而anim動畫是一個SequentialAnimation的動畫,從名字上也可以看出在這個動畫中的的子動畫一個個的順序執行,如官方說明的一般“Animations defined in a SequentialAnimation are run one after the other”。
anim動畫中有3個Action和一個Animation,按照順序執行,則是:
- 第一步,通過設置SpriteSequence的goalSprite屬性,讓整個SpriteSequence按照上面我繪製的Sprite關係圖進行動畫顯示,即(blink->)still->floating->flailing->failing
- 第二步,在整個動畫整體演示的過程中,通過改變y座標讓整個動畫不斷下降,即做到小熊下降的效果
- 第三步,當上一個NumberAnimation執行完畢後(需要12s的時間,此時y座標變到480),將SpriteSequence的goalSprite屬性置空,並且通過jumpTo接口跳轉到still的Sprite動畫部分,繼續演示(不過這個時候就仍然是開始的眨眼效果)
- 最後一步,將y座標調整爲0字段,整體恢復到開始狀態
總結
學到的知識:
- 學到了Behavior和Animation的配合使用
- 瞭解了Repeater的使用
- 學習Grid的排版方式
- 學習了通過contentX來控制Flickable顯示區域的方法
- 學習使用BorderImage組件
- 瞭解Image的fillMode字段的作用方式
- 學習使用AnimatedSprite組件
- 學習了使用SpriteSequence和Sprite組件
這一章示例的學習也耗費了好幾天的時間,尤其是在最後一個SpriteSequence組件的使用讓我剛開始簡直是無處下手的感覺。雖然和AnimatedSprite組件一樣,都是展示圖片中的動畫幀,但是AnimatedSprite組件的使用方式比較單一,只能完全按照圖片中定義的動畫幀順序進行顯示,而SpriteSequence組件則非常的靈活,通過自定義的Sprite列表(尤其是to字段),結合Animation動畫則會完成豐富多彩的視覺效果。