在学习了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字段,整体恢复到开始状态
总结
学到的知识:
- 学到了Behavior和Animation的配合使用
- 了解了Repeater的使用
- 学习Grid的排版方式
- 学习了通过contentX来控制Flickable显示区域的方法
- 学习使用BorderImage组件
- 了解Image的fillMode字段的作用方式
- 学习使用AnimatedSprite组件
- 学习了使用SpriteSequence和Sprite组件
这一章示例的学习也耗费了好几天的时间,尤其是在最后一个SpriteSequence组件的使用让我刚开始简直是无处下手的感觉。虽然和AnimatedSprite组件一样,都是展示图片中的动画帧,但是AnimatedSprite组件的使用方式比较单一,只能完全按照图片中定义的动画帧顺序进行显示,而SpriteSequence组件则非常的灵活,通过自定义的Sprite列表(尤其是to字段),结合Animation动画则会完成丰富多彩的视觉效果。