** 注意: **
最新的构建时间:2016/03/21
这章的源代码能够在assetts folder找到。
到目前为止,我们主要关注的是简单的图形元素以及如何排列和操作它们。这一章讲的是如何控制这些元素的变化,以一种属性值的逐渐改变而不只是瞬间改变的方式,它更多的是随着时间的变化而变化的,也就是:动画。该技术是现代平滑用户界面的关键基础之一,可以通过使用状态(states)和转换(transitions)来描述用户界面的系统进行扩展。每个状态(state)定义一组属性更改,并可以与状态(state)更改的动画组合在一起,称为转换(transition)。
5.1 动画
动画被应用于属性变化。当属性值发生变化时,动画定义了插值曲线,以创建从一个值到另一个值的平滑的转换效果。动画是由一系列的目标属性来定义的,插值曲线具有缓动属性(easing),在大多数情况下也会有一个持续时间(duration)属性,它定义了属性变化的时间。Qt Quick 中的所有动画都是由相同的定时器控制的,因此它们是同步的。这有效地提高了动画的性能和视觉质量。
** 注意: **
动画控制属性的变化,比如值的内插。这是一个基本概念。QML 是基于元素、属性和脚本编写的。每个元素都提供了几十个属性,每个属性都接受我们的自定义动画。在本章中我们会发现这是一个瑰丽的秀场。我们会看到一些动画,欣赏它们的美,当然我们还可以用学到的知识尝试我们自己的创意。请记住:动画控制属性的变化,每个元素都有几十个属性可以去处理。
开始表演!
animation_sequence// 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
}
}
上面的例子向我们展示了一个应用于 x 和 rotation 属性的简单动画。每个动画的持续时间为 400 毫秒和并且一直执行。x 属性的动画将 x 坐标从原来位置逐渐移动到右侧。旋转的动画从当前角度到 360 度。这两个动画都是并行运行的,并且在我们点击 UI 界面时就会启动。
现在你可以通过改变动画和时间属性来播放动画或者你可以添加另一个属性动画例如不透明度属性,或者是缩放属性。把这些结合起来,就可以看到我们元素在深空中消失了。试一下!
5.1.1 动画元素
有几种类型的动画元素,每一种都针对特定的场景进行了优化。以下是一些最常用的动画:
- PropertyAnimation —— 属性值改变动画
- NumberAnimation —— 数量值改变动画
- ColorAnimation —— 颜色值改变动画
- RotationAnimation —— 旋转值改变动画
除了这些基本的和经常使用的动画元素之外,Qt Quick 还为特定的使用场景提供了更多的专门的动画元素:
- PauseAnimation —— 为动画提供暂停效果
- SequentialAnimation —— 使动画按照先后顺序运行
- ParallelAnimation —— 使动画并行运行
- AnchorAnimation —— 对锚值的变化运行动画效果
- ParentAnimation —— 对父对象的值的变化运行动画效果
- SmoothedAnimation —— 允许一个属性平滑地跟踪一个值的变化
- SpringAnimation —— 允许一个属性跟踪一个在做类似 spring 运动的值
- PathAnimation —— 沿着路径对一个元素运行动画效果
- Vector3dAnimation —— QVector3d 值改变动画
稍后我们将学习如何创建一个动画序列。在处理更复杂的动画时,需要更改一个属性,或者在一个正在进行的动画中运行脚本。为此,Qt Quick 提供了动作(Action)元素,动作(Action)元素可以在任何使用动画元素的地方使用:
- PropertyAction —— 在动画中随时进行属性的更改
- ScriptAction —— 定义在动画中运行的脚本
主要的动画类型将在本章中使用小巧紧凑的例子进行讨论。
5.1.2 应用动画
动画可以用几种方式来应用:
- Animation on property (属性动画) —— 它在元素完全载入后自动运行
- Behavior on property (行为动画) —— 当属性值发生变化时自动运行
- Standalone Animation (独立动画)—— 显示地调用 start() 或 running 属性设置为 true (例如,通过属性绑定)时,动画将运行。
稍后我们还将看到如何在状态转换中使用动画。
扩展 ClickableImage 版本 2。
为了演示动画的使用,我们重用了我们在前面的章节中使用的 ClickableImage 组件,并将其扩展为一个文本元素。
// 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),并根据列的孩子区域(childrenRect)属性计算了宽度和高度。我们公开了两个属性: text 和图像的 source 属性,以及点击的信号(clicked)。我们还希望文本和图像一样宽,并且它应该有换行的功能。我们通过设置文本元素 wrapMode 属性的值为 Text.WordWrap 来实现后者。
** 注意: **
由于几何形状依赖关系的反转(父元素的几何形状依赖于子元素的几何形状),因此我们不能在 ClickableImageV2 上设置宽度和高度的值,否则会破坏原有的绑定关系。这是我们针对这个示例内部设计的一个限制,但是作为一个组件设计师,我们应该有意识地避免这种情况的出现。正常情况下,我们应该设计的是子元素的几何形状依赖父元素的几何形状的正常组件。
提升的对象示例:
animationtypes_start// 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。他们每个人都使用不同的方法来产生不同的作用效果和特征。
** 第一个对象 **
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> 策略。动画立即开始,当一个对象被单击时,它们的 y 位置被重置到开始位置,这适用于所有对象。在第一个对象上,只要动画运行,重置就不会有任何效果。这样让人很不爽,因为 y 位置在动画开始前的一小段时间内被设置为一个新的值。应该避免这种相互竞争的属性变化。
** 第二个对象 **
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 的属性设置。当我们点击它时,对象将开始移动(y 位置将被设置为 40)。另一个点击没有影响,因为位置已经设置好了。我们可以尝试使用一个随机值(例如 40+(math.random()(205-40)) 设置 y 位置。我们将看到,对象将始终对新位置进行动画,并调整其速度以匹配动画持续时间所定义的 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 策略。动画被定义为它自己的元素的对象,并且可以放在文档的任何地方。单击的时候将调用动画的 start() 函数启动动画。每个动画都有一个 start()、stop()、resume()、restart() 函数。动画自身的形式可以比其他类型的动画更早的获取到更多的相关信息。我们需要定义目标(target)和属性(properties)来声明要激活的目标元素和我们想要激活的属性。我们需要定义一个 to 值,在上面的示例中,我们也可以定义一个 from 值来允许重新启动动画。
animationtypes单击背景将把所有对象重置为初始位置。第一个对象不能重新启动,除非重新启动触发元素重新加载的程序。
** 注意: **
启动/停止动画的另一种方法是将一个属性(property)绑定到一个动画的运行(running)属性。当需要用户输入(user-input)控制属性时,这一点特别有用:
NumberAnimation {
...
// animation runs when mouse is pressed
running: area.pressed
}
MouseArea {
id: area
}
5.1.3 缓冲曲线(Easing Curves)
我们已经知道,属性值的改变能够通过动画来控制。缓冲曲线(Easing Curves)属性用来调整一个属性值改变的插值算法。我们上面示例中已经定义的动画都是使用的线性的插值算法,因为动画的默认缓冲曲线类型是 Easing.Linear。它最好是用一个小的图形来显示,其 y 轴表示动画的属性,x 轴表示持续时间。线性插值可以从动画开始时的 from 值到动画结束时的 to 值绘制一条直线。因此,缓冲类型定义了变化曲线。可以通过选择合适的缓冲类型使被移动对象的移动效果看起来更自然,例如当页面滑出时。一开始,页面应该慢慢地滑动,然后逐渐加速,最终以高速度滑出,类似于翻书时页面呈现出来的效果。
** 注意: **
动画不应该被过度地使用。在 UI 设计的其他方面,动画也应该精心设计,用于支持 UI 流,而不是控制它。眼睛对移动物体非常敏感,动画会很容易干扰用户的注意力。
在下一个例子中,我们将尝试一些缓冲曲线。每个缓冲曲线都由一个可点击的图像显示,当点击时,将在方块(square)动画中设置一个新的缓冲类型,然后触发一个 restart() 以使用新的曲线来运行动画。
easingcurves这个示例的代码稍微复杂一些。我们首先创建一个缓冲类型(EasingTypes)的网格和一个由缓冲类型控制的框(box)。缓冲类型只是显示了该框用于动画的曲线。当用户点击“缓冲曲线”时,盒子就会按照“放松曲线”的方向移动。动画本身是一个标准的动画,将目标设置为 box,并为 x-property 动画配置了一个持续时间为 2 秒的动画。
** 注意: **
EasingType 的内部结构会在实时呈现曲线,感兴趣的读者可以在 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()
}
}
当我们运行这个实例的时候,请在动画运行时观察方块速度的变化。有些动画效果对方块来说更自然,有些则感觉很突兀。
除了 duration 和 easing.type 我们还可以对动画进行微调。例如 PropertyAnimation 动画,它有大多数动画都支持的 easing.amplitude(振幅)、easing.overshoo(溢出) 和 easing.period(周期) 属性,允许我们对特定的缓冲曲线的行为进行微调。当然并不是所有的缓冲曲线都支持这些参数。可以查看 Qt PropertyAnimation 文档来了解更详细的内容。
** 注意: **
在用户界面上下文中选择适当的动画对于结果是至关重要的。记住,动画应该支持 UI 效果流畅;而不是画蛇添足使用户厌烦。
网友评论