QT Demo 之 imageelements

在學習了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:

    The corners (regions 1, 3, 7, and 9) are not scaled at all.
    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字段,整體恢復到開始狀態

總結

學到的知識:

  1. 學到了Behavior和Animation的配合使用
  2. 瞭解了Repeater的使用
  3. 學習Grid的排版方式
  4. 學習了通過contentX來控制Flickable顯示區域的方法
  5. 學習使用BorderImage組件
  6. 瞭解Image的fillMode字段的作用方式
  7. 學習使用AnimatedSprite組件
  8. 學習了使用SpriteSequence和Sprite組件

這一章示例的學習也耗費了好幾天的時間,尤其是在最後一個SpriteSequence組件的使用讓我剛開始簡直是無處下手的感覺。雖然和AnimatedSprite組件一樣,都是展示圖片中的動畫幀,但是AnimatedSprite組件的使用方式比較單一,只能完全按照圖片中定義的動畫幀順序進行顯示,而SpriteSequence組件則非常的靈活,通過自定義的Sprite列表(尤其是to字段),結合Animation動畫則會完成豐富多彩的視覺效果。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章