QML Book 第五章 动态元素 1

注意:
最新的构建时间:2016/03/21
这章的源代码能够在 Assets 找到。

到目前为止,我们主要关注的是简单的图形元素以及如何排列和操作它们。这一章讲的是如何控制这些元素的变化,以一种属性值的逐渐改变而不只是瞬间改变的方式,它更多的是随着时间的变化而变化的,也就是:动画。该技术是现代平滑用户界面的关键基础之一,可以通过使用状态(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 效果流畅;而不是画蛇添足使用户厌烦。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,186评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,858评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,620评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,888评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,009评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,149评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,204评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,956评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,385评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,698评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,863评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,544评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,185评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,899评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,141评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,684评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,750评论 2 351

推荐阅读更多精彩内容