QML动画

一、前言

很多应用软件配有各种各样的动画效果,除了让界面更酷更炫,更重要的是,动画可以:

  • 给用户一个可操作的暗示;
  • 平滑衔接界面与界面的切换,让界面感觉更流畅;
  • 产生拟物效果,让软件更容易理解和使用;

Qt Quick提供了比较丰富的动画类库,可以让我们的界面动起来。一般的动画都是通过操作Item的property来实现的,比如移动一个文本对象,你只需要确认起始位置和目标位置,两个位置中间的运动轨迹,PropertyAnimation或者NumberAnimation可以帮我们计算出来,他们有各种各样的算法产生不同的动画效果。


二、动画元素分类

Qt Quick针对不同的应用场景,提供了几个基本的动画对象

动画对象说明
PropertyAnimation可以改变各种类型的property来产生动画效果
NumberAnimationPropertyAnimation的派生类,专门改变数字类型的property来产生动画,效率相比PropertyAnimation更好
ColorAnimationPropertyAnimation的派生类,专门改变color类型的property来产生动画,效率相比PropertyAnimation更好
RotationAnimationPropertyAnimation的派生类,专门改变rotation值,效率相比PropertyAnimation更好
Vectoc3dAnimationPropertyAnimation的派生类,在一个Vectoc3d值发生改变时使用
PathAnimation让对象沿一个给定的路径运动
SmoothedAnimation允许一个property跟踪一个值,产生平滑动画
SpringAnimation允许一个property跟踪一个值,动画效果类似于弹簧运动

Qt Quick还提供了用于组合多个动画对象的分组动画对象

动画对象说明
SequentialAnimation顺序执行一系列动画
ParallelAnimation并行执行一系列动画

除了上面提到的动画对象,还有一些对象虽然本身不是直接的动画元素,但却是有些QML Item能够动起来的基础,我们称之为动画搭档,例如:

动画搭档说明
StateItem的状态,不同状态对应不同的界面效果和业务逻辑,可以将动画应用于不同状态间的迁移过程
Transition过渡,衔接Item的状态和动画,使状态变化过程平滑

还有一些动画元素,需要与其它动画对象结合才能产生较好的效果,我们称之为协同动画元素,例如:

协同动画元素说明
Behavior为Item的property变化绑定一个默认的动画对象
ParentAnimation在改变一个Item的parent时使用,使得该Item从旧parent移动到新parent的过程更平滑,通常与Transition、State、ParentChange联合使用
AnchorAnimation在改变一个Item的anchor时使用,平滑变化过程,通常与Transition、State、AnchorChange联合使用
PauseAnimation在动画过程中插入它,可以将动画过程暂停一段时间
PropertyAction在动画执行过程中立即改变某个属性
ScriptAction在动画执行过程中运行一段ECMAScript脚本

三、Animation

我们先看看Animation–Qt Quick抽象出来的动画元素接口,Animation是Qt Quick中所有动画类的基类,它具有下列属性:

属性类型说明
runningbool指示动画是否在运行。默认值为false。设置为true会启动动画,设置为false会停止动画。你可以读取它的值来判断动画当前是否运行,也可以给它绑定一个表达式,当表达式的值为true时,动画会自动执行。start()方法会置其为true,stop()方法会置其为false。
loopsint动画的执行次数,默认值是1;给它赋值Animation.Infinite会导致动画循环执行永不停歇
pausedbool指示动画是否被暂停,默认不暂停。pause()方法会置其为true,stop()方法会置其为false。
alwaysRunToEndbool默认值是false。它指示在显示地通知动画停止时是否把动画过程执行完。如果设为true,那么即使你调用stop()或者设置running为false,动画都会自顾自地执行完

Animation是Qt Quick中所有动画类的基类,它具有下列方法:

方法说明
start()启动一个动画,如果一个动画已经在执行,则它什么也不干
stop()终止一个动画,如果动画没执行完,那么动画操作的属性可能就是某个中间值,而不是目标值;如果设置了alwaysRunToEnd,那就肯定是目标值;调用stop()时没有动画在运行则什么也不干
restart()重新开始动画,等同于先stop()在start()
pause()暂停一个动画
resume()与pause()对应,让一个动画继续执行。如果一个动画没有被暂停或者不再运行状态,则它什么也不干
complete()完成一个动画。如果动画执行到某个中间步骤,这个调用会让动画直接跳到结束状态。动画要改变的property都会抵达目标状态。它会把running属性设置为false;如果动画不在运行状态,则它什么也不干

Animation是Qt Quick中所有动画类的基类,它具有下列信号:

信号说明
started()动画开始时触发。注意,只有单独的顶层动画对象才会触发这个信号,如果一个动画对象处于某个动画分组中、在一个Behavior中或者在一个Transition中,都不会触发这个信号
stopped()动画(手动或自动执行完毕)进入停止状态时触发。与started()信号一样,只有单独的顶层动画对象才会触发这个信号。

了解了Animation对象,启动一个动画:

  • 调用start()方法;
  • 设置running为true;
  • 为running绑定一个表达式,表达式求值结果为true;

停止一个动画:

  • 调用stop();
  • 设置running为false;
  • 触发running绑定的表达式重新求值且返回值为false;
  • 调用complete()方法;

四、基本动画元素

基本动画元素,都可以直接应用于Item来产生动画效果;

4.1、PropertyAnimation

PropertyAnimation是Animation的派生类,它通过改变对象的property来实现动画,基本上你能在Qt SDK中找到的Item property,它都可以改变。

定义一个PropertyAnimation有多种途径,Behavior和Transition还没介绍,暂时跳过,那么还有三种定义及使用PropertyAnimation的方式。

(1)单独使用

在这里插入图片描述

import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    id: root;
    width: 400;
    height: 400;
    visible: true;

    Rectangle {
        id: rootItem;
        width: 400;
        height: 360;
        color: "#EEEEEE";

        Rectangle {
            id: rect;
            width: 50;
            height: 150;
            anchors.centerIn: parent;
            color: "blue";

            PropertyAnimation {
                id: animation;
                target: rect;
                property: "width";
                to: 150;
                duration: 1000;
            }

            MouseArea {
                anchors.fill: parent;
                onClicked: animation.running =  true;
            }
        }
    }
}
  • 定义了一个PropertyAnimation对象,默认不启动,当鼠标左键点击蓝色矩形时,通过设置running为true来启动动画,把蓝色矩形的宽度设置为150,矩形变成正方形;
  • 使用target属性指定要操作的目标对象;
  • 使用property属性指定要改变目标对象的哪个属性;
  • 使用from数组指定目标属性的初始值;如果不指定,初始值等于当前值;
  • 使用to属性指定目标属性的目标值;
  • 使用duration属性指定动画完成的时间;

同时多个属性
如果你想同时改变多个属性,则可以使用PropertyAnimation的properties属性来指定,属性名之间用英文逗号分隔,而这些属性共用to属性指定的目标值,所以目标属性最好是同一个类型的property,例如:同时改变width和height
在这里插入图片描述

import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    id: root;
    width: 400;
    height: 400;
    visible: true;

    Rectangle {
        id: rootItem;
        width: 400;
        height: 360;
        color: "#EEEEEE";

        Rectangle {
            id: rect;
            width: 50;
            height: 150;
            anchors.centerIn: parent;
            color: "blue";

            PropertyAnimation {
                id: animation;
                target: rect;
                properties: "width,height";
                to: 300;
                duration: 1000;
            }

            MouseArea {
                anchors.fill: parent;
                onClicked: animation.running =  true;
            }
        }
    }
}

同时多个目标
如果想同时改变多个目标对象,则可设置targets属性,例如:同时改变rectA和rectB的width属性

PropertyAnimation {
	id: animation;
	targets: ;[rectA, rectB];
	properties: "width";
	to: 150;
	duration: 1000;
}

easing属性
最好要说的是easing属性,它指定动画的松弛曲线。为了使用松弛曲线,你至少要指定easing.type属性,而有一些easing.type可能还要配合指定easing.amplitude、easing.overshoot、easing.period才能有实际的效果;easing.type的默认值是Easing.Linear,即松弛曲线是线性的,可供使用的类型:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
请添加图片描述


使用:easing.type: Easing.OutElastic
在这里插入图片描述

import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    id: root;
    width: 400;
    height: 400;
    visible: true;

    Rectangle {
        id: rootItem;
        width: 400;
        height: 360;
        color: "#EEEEEE";

        Rectangle {
            id: rect;
            width: 50;
            height: 150;
            anchors.centerIn: parent;
            color: "blue";

            PropertyAnimation {
                id: animation;
                target: rect;
                properties: "width,height";
                to: 300;
                duration: 2000;
                easing.type: Easing.OutElastic
            }

            MouseArea {
                anchors.fill: parent;
                onClicked: animation.running =  true;
            }
        }
    }
}


(2)在信号处理器中使用

import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    id: root;
    width: 400;
    height: 400;
    visible: true;

    Rectangle {
        id: rootItem;
        width: 400;
        height: 360;
        color: "#EEEEEE";

        Rectangle {
            id: rect;
            width: 50;
            height: 150;
            anchors.centerIn: parent;
            color: "blue";

            MouseArea {
                anchors.fill: parent;
                onClicked: PropertyAnimation {
                    target: rect;
                    properties: "width,height";
                    to: 300;
                    duration: 2000;
                    easing.type: Easing.OutElastic
                }
            }

        }

    }

}


(3)使用Animation on

使用Animation on <property>这种语法将一个PropertyAnimation与一个属性关联起来。

import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    id: root;
    width: 400;
    height: 400;
    visible: true;

    Rectangle {
        id: rootItem;
        width: 400;
        height: 360;
        color: "#EEEEEE";

        Rectangle {
            id: rect;
            width: 50;
            height: 150;
            anchors.centerIn: parent;
            color: "blue";

            MouseArea {
                anchors.fill: parent;
                id: mouseArea;
            }

            PropertyAnimation on width {
                to: 150;
                duration: 1000;
                running: mouseArea.pressed;
            }
        }
    }
}
  • 上述代码中,将PropertyAnimation关联到width属性上,这种定义方法,不需要再设定target和property属性,使用这种方式定义动画,代码变得更加简单;
  • 如果不设置running属性,那么使用这种方式定义的动画对象,在Item加载完毕后会立即执行,这点与单独定义或者在信号处理器中定义略有不同;
  • PropertyAnimation的running绑定了一个ECMAScript表达式mouseArea.pressed;,只有按下鼠标左键时动画才执行,一旦释放鼠标左键,动画就会停止;

信号使用

请添加图片描述

import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    id: root;
    width: 400;
    height: 400;
    visible: true;

    Rectangle {
        id: rootItem;
        width: 400;
        height: 360;
        color: "#EEEEEE";

        Rectangle {
            id: rect;
            width: 50;
            height: 150;
            anchors.centerIn: parent;
            color: "blue";
            property var animation;

           PropertyAnimation {
                id: toSquare;
                target: rect;
                property: "width";
                to: 150;
                duration: 1000;
                onStarted: {
                    rect.animation = toSquare;
                    rect.color = "red";
                }
                onStopped: {
                    rect.color = "blue";
                }
           }

           PropertyAnimation {
                id: toRect;
                target: rect;
                property: "width";
                to: 50;
                duration: 1000;
                onStarted: {
                    rect.animation = toRect;
                    rect.color = "red";
                }
                onStopped: {
                    rect.color = "blue";
                }
           }

           MouseArea {
               anchors.fill: parent;
               onClicked: {
                   if(rect.animation == toRect || rect.animation == undefined) {
                       toSquare.start();
                   }else {
                       toRect.start()
                   }
               }
           }
        }
    }
}


4.2、NumberAnimation

NumberAnimation是PropertyAnimation的派生类,专门处理数字类型的property,它重写了fromto属性,将其类型设置为real
在这里插入图片描述

import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    id: root;
    width: 400;
    height: 400;
    visible: true;

    Rectangle {
        id: rootItem;
        width: 400;
        height: 360;
        color: "#EEEEEE";

        Rectangle {
            id: rect;
            width: 50;
            height: 50;
            color: "blue";
            x: 0;
            y: 95;

           MouseArea {
               id: mouseArea;
               anchors.fill: parent;
               onClicked: {
                    animationX.start();
                   animationRotation.running = true;
                   animationRadius.start();
               }
           }

           NumberAnimation {
               id: animationX;
               target: rect;
               property: "x";
               to: 310;
               duration: 3000;
               easing.type: Easing.OutCubic;
           }

           NumberAnimation {
               id: animationRotation;
               target: rect;
               property: "rotation";
               to: 1080;
               duration: 3000;
               running: false;
               easing.type: Easing.OutInQuad;
           }

           NumberAnimation on radius {
               id: animationRadius;
               to: 25;
               duration: 3000;
               running: false;
           }
        }
    }
}

4.3、ColorAnimation

ColorAnimation是PropertyAnimation的派生类,专门处理color类型的property,它重写了fromto属性,将其类型设置为color
在这里插入图片描述

import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    id: root;
    width: 400;
    height: 400;
    visible: true;

    Rectangle {
        id: rootItem;
        width: 400;
        height: 360;
        color: "#EEEEEE";

        Rectangle {
            id: rect;
            width: 60;
            height: 60;
            color: "red";
            radius: 30;
            anchors.centerIn: parent;

           MouseArea {
               id: mouseArea;
               anchors.fill: parent;
               onClicked: ColorAnimation {
                   target: rect;
                    property: "color";
                    to: "green";
                    duration: 1500;
               }
           }
        }


    }
}

4.4、RotationAnimation

RotationAnimation是PropertyAnimation的派生类,专门处理rotation和angle两个属性,它重写了fromto属性,将其类型设置为real

  • 使用RotationAnimation时不需要指定property属性;
  • RotationAnimation在旋转一个Item时以Item的transformOrigin属性指定的点为中心,这个属性为枚举类型,可以取值:
    • Item.Center
    • Item.Top
    • Item.TopRight
    • Item.TopLeft
    • Item.Bottom
    • Item.BottomRight
    • Item.BottomLeft
    • Item.Left
    • Item.Right

RotationAnimation新增了一个direction属性,可以取下列值:

  • RotationAnimation.Numerical:默认值,在from和to两个角度之间做线性插值进行旋转,比如from=10,to=100,那么就顺时针旋转90°;
  • RotationAnimation.Clockwise:在两个角度之间顺时针旋转;
  • RotationAnimation.Counterclockwise:在两个角度之间逆时针旋转;
  • RotationAnimation.Shortest:选取两个角度之间的最短路径进行旋转,比如from=10,to=350,那么就逆时针旋转20°;

在这里插入图片描述

import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    id: root;
    width: 400;
    height: 400;
    visible: true;

    Rectangle {
        id: rootItem;
        width: 400;
        height: 360;
        color: "#EEEEEE";

        Rectangle {
            id: rect;
            width: 120;
            height: 60;
            color: "red";
            anchors.left: parent.left;
            anchors.leftMargin: 20;
            anchors.verticalCenter: parent.verticalCenter;

           MouseArea {
               id: mouseArea;
               anchors.fill: parent;
               onClicked: RotationAnimator {
                    target: rect;
                    to: 90;
                    duration: 1500;
                    direction: RotationAnimator.Counterclockwise;
               }
           }
        }

        Rectangle {
            id: blueRect;
            width: 120;
            height: 60;
            anchors.right: parent.right;
            anchors.rightMargin: 40;
            anchors.verticalCenter: parent.verticalCenter;
            transformOrigin: Item.TopRight;

            MouseArea {
                anchors.fill: parent;
                onClicked: anim.start();
            }

            RotationAnimator {
                id: anim;
                target: blueRect;
                to: 60;
                duration: 1500;
            }
        }


    }
}

4.5、PathAnimation

PathAnimation是从Animation继承而来,它的目标对象沿着一个既定的路径运动;像PropertyAnimation一样,它也有一个easing属性;

anchorPoint属性描述目标对象的哪个点锚定在路径上,你可以设置中心点或者左上角与路径锚定,默认是左上角;你可以使用“x,y”或者Qt.point()构造一个Point对象赋值给anchorPoint;

orientation属性控制目标对象沿着路径运动时的旋转策略,它可以取值:

  • PathAnimaion.Fixed,默认值,在运动过程中保持物体方位不旋转;
  • PathAnimation.RightFirst,旋转目标对象时努力是目标对象右侧贴合路径;
  • PathAnimation.LeftFirst,旋转目标对象时努力是目标对象左侧贴合路径;
  • PathAnimation.BottomFirst,旋转目标对象时努力是目标对象底部贴合路径;
  • PathAnimation.TopFirst,旋转目标对象时努力是目标对象顶部贴合路径;

如果你指定了orientation属性,而目标对象在到达路径末端时的旋转角度和你期望的不符,则可以设置endRotation属性来指定一个角度,那么当目标对象抵达路径末端时会自动调整旋转角度为指定的endRotation
如果你设置了orientationExitDuration属性,旋转过程就会以动画的形式完成,否则就会发生一个跳变;

path属性,类型是Path;

在这里插入图片描述

import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    id: root;
    width: 400;
    height: 400;
    visible: true;

    Rectangle {
        id: rootItem;
        width: 400;
        height: 360;
        color: "#EEEEEE";

        Canvas {
            width: 400;
            height: 240;

            onPaint: {
                var ctx = getContext("2d");
                ctx.lineWidth = 4;
                ctx.strokeStyle = "red";
                ctx.beginPath();
                ctx.arc(200,0,160,Math.PI*2,0,false);
                ctx.stroke();
            }

            Rectangle {
                id: rect;
                width: 40;
                height: 40;
                color: "blue";
                x: 20;
                y: 0;

               MouseArea {
                   id: mouseArea;
                   anchors.fill: parent;
                   onClicked: pathAnim.start()
               }

               PathAnimation {
                    id: pathAnim;
                    target: rect;
                    duration: 6000;
                    anchorPoint: "20,20";
                    orientationEntryDuration: 200;
                    orientationExitDuration: 200;
                    easing.type: Easing.InOutCubic;
                    orientation: PathAnimation.TopFirst;
                    path: Path {
                        startX: 40;
                        startY: 0;
                        PathArc {
                            x: 360;
                            y: 0;
                            useLargeArc: true;
                            radiusX: 160;
                            radiusY: 160;
                            direction: PathArc.Counterclockwise;
                        }
                    }
               }

            }
        }


    }
}

4.6、SmoothedAnimation

SmoothedAnimation是NumberAnimation的派生类,它默认将easing.type设置为Easing.InOutQuad,在from和to之间产生平滑的动画效果。

duration属性设置动画周期,单位是毫秒;默认值是-1,禁用duration模式。

velocity设置速率,默认速率是200units/秒;将velocity设置为-1禁用速率;如果from和to的距离很短,SmoothedAnimation会自行调整velocity来适应。

当duration和velocity同时设置时,SmoothedAnimation会根据from、to之间的距离和速率计算出按照速率完成动画所需的时间,拿这个时间与duration比较,如果duration短就用duration,否则使用velocity。

在这里插入图片描述

import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    id: root;
    width: 400;
    height: 400;
    visible: true;

    Rectangle {
        id: rootItem;
        width: 400;
        height: 360;
        color: "#EEEEEE";

        Rectangle {
            id: rect;
            width: 80;
            height: 60;
            color: "red";
            x: 20;
            y: 20;
        }

        SmoothedAnimation {
            id: smoothX;
            target: rect;
            property: "x";
            duration: 1000;
            velocity: -1;
        }

        SmoothedAnimation {
            id: smoothY;
            target: rect;
            property: "y";
            velocity: 100;
        }

        MouseArea {
            anchors.fill: parent;
            onClicked: {
                smoothX.from = rect.x;
                smoothX.to = mouse.x + 4;
                smoothX.start();
                smoothY.from = rect.y;
                smoothY.to = mouse.y + 4;
                smoothY.start();
            }
        }

    }
}

4.7、SpringAnimation

SpringAnimation模仿弹簧的震荡行为;

spring属性用来控制动画的加速度,0-5.0之间的取值是有意义的,默认值为0;

damping属性代表衰减系数,其值越大震荡会越快平复,0-1.0之间的值比较有意义,默认值为0;

epsilon允许你设定一个最接近0的阈值来代表0,如果是基于像素位置的动画,0.25是一个比较合适的值;如果是基于scale的动画,那可能0.005比较合适;默认值是0.01;调整epsilon可能会带来一定的性能提升;

velocity属性设定动画的最大速率,默认值为0,没有限制;

在这里插入图片描述

import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    id: root;
    width: 400;
    height: 400;
    visible: true;

    Rectangle {
        id: rootItem;
        width: 400;
        height: 360;
        color: "#EEEEEE";

        Rectangle {
            id: rect;
            width: 80;
            height: 60;
            color: "red";
            x: 20;
            y: 20;
        }

        SpringAnimation {
            id: springX;
            target: rect;
            property: "x";
            spring: 3;
            damping: 0.06
            epsilon: 0.25;
        }

        SpringAnimation {
            id: springY;
            target: rect;
            property: "y";
            spring: 3;
            damping: 0.06
            epsilon: 0.25;
        }

        MouseArea {
            anchors.fill: parent;
            onClicked: {
                springX.from = rect.x;
                springX.to = mouse.x - 20;
                springX.start();
                springY.from = rect.y;
                springY.to = mouse.y - 20;
                springY.start();
            }
        }

    }
}

五、分组动画元素

ParallelAnimationSequentialAnimation允许我们把多个动画元素组合在一起来执行,ParallelAnimation中定义的多个动画对象会并行执行,而SequentialAnimation中定义的多个动画对象会一个个顺序执行。

5.1、ParallelAnimation

ParallelAnimation从Animation继承而来,没有添加额外的属性。它本身但是使用没有意义,不产生动画效果。

你只需要在声明ParallelAnimation对象时在其中定义多个子动画对象,ParallelAnimation开始运行时就会并行执行它们。

在这里插入图片描述

import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    id: root;
    width: 400;
    height: 400;
    visible: true;

    Rectangle {
        id: rootItem;
        width: 400;
        height: 360;
        color: "#EEEEEE";

        Rectangle {
            id: rect;
            width: 50;
            height: 50;
            color: "red";
            x: 20;
            y: 95;

            MouseArea {
                id: mouseArea;
                anchors.fill: parent;
                onClicked: {
                    if(anim.paused) {
                        anim.resume();
                    }else if(anim.running) {
                        anim.pause();
                    }else {
                        anim.start();
                    }
                }
            }

            ParallelAnimation {
                id: anim;
                loops: Animation.Infinite;

                NumberAnimation {
                    target: rect;
                    property: "x";
                    to: 310;
                    duration: 3000;
                }
                NumberAnimation {
                    target: rect;
                    property: "rotation";
                    to: 360;
                    duration: 1000;
                    loops: 3;
                }
                NumberAnimation {
                    target: rect;
                    property: "radius";
                    to: 25;
                    duration: 3000;
                }
            }
        }

    }
}
  • 上面代码中的ParallelAnimation会无限循环运行,它内部定义了三个NumberAnimation,分别改变rect对象的x、rotation、radius属性;
  • MouseArea的onClicked方法,根据anim对象的paused、running两个属性来判断鼠标左键按下时如何改变anim对象的状态,最终的效果是动画可以启动、暂停、继续;

5.2、SequentialAnimation

SequentialAnimation与ParallelAnimation类似,但是它的子动画对象是一个个顺序执行的。

在这里插入图片描述

import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    id: root;
    width: 400;
    height: 400;
    visible: true;

    Rectangle {
        id: rootItem;
        width: 400;
        height: 360;
        color: "#EEEEEE";

        Rectangle {
            id: rect;
            width: 50;
            height: 50;
            color: "red";
            x: 20;
            y: 95;

            MouseArea {
                id: mouseArea;
                anchors.fill: parent;
                onClicked: {
                    if(anim.paused) {
                        anim.resume();
                    }else if(anim.running) {
                        anim.pause();
                    }else {
                    	rect.radius = 0;
                        rect.x = 0;
                        rect.rotation = 0;
                        anim.start();
                    }
                }
            }

            SequentialAnimation {
                id: anim;

                NumberAnimation {
                    target: rect;
                    property: "x";
                    to: 310;
                    duration: 3000;
                }
                NumberAnimation {
                    target: rect;
                    property: "rotation";
                    to: 360;
                    duration: 1000;
                    loops: 3;
                }
                NumberAnimation {
                    target: rect;
                    property: "radius";
                    to: 25;
                    duration: 3000;
                }
            }
        }

    }
}

六、动画搭档

6.1、State

很多用户界面由状态驱动,根据应用场景,针对特定的状态显示不同的界面。

在QML中,状态是定义在State类型中的一系列属性配置。不同的配置可能有不同的作用:

  • 显示一些UI组件,隐藏另一些;
  • 向用户呈现不同的操作和功能;
  • 启动、暂停、停止动画;
  • 在某种新的状态下执行某些脚本;
  • 改变某个特定Item的property的值;
  • 显示一个不同的view或screen;

Item有一个state属性,是字符串类型,它保存Item的当前状态的名字,如果你没有设置过Item的状态,它默认就是空串。
你可以设置state的值来改变Item的状态,如果给它一个空串,Item就会返回默认状态。

Item还有一个states属性,保存为这个Item定义的所有状态,它的类型是list<State>,列表中的每个State对象代表一种状态;

State类型对应的C++类型是QQuickState,它有这么几个属性:

属性类型说明
namestring保存状态的名字;为某个Item定义的每种状态的名字,在Item范围内都应该是唯一的
whenbool它描述状态在什么时候应用;它应该被绑定到一个ECMAScript表达式上,党政表达式返回结果为true时应用本状态
extendstring指向当前状态的“基态”的名字;所谓基态,类比C++中的基类概念。基态的所有变化都会被牌生态继承
changeslist<Change>一个列表,保存应用于这种状态的所有变化;这个是State的默认属性;Change对应于C++类QQuickStateOperation,当进入一种状态后,这个列表中的Change对象会顺次执行;

在这里插入图片描述

import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    id: root;
    width: 400;
    height: 400;
    visible: true;

    Rectangle {
        id: rootItem;
        width: 400;
        height: 360;
        color: "#EEEEEE";

        Text {
            id: centerText;
            text: "A Single Text.";
            anchors.centerIn: parent;
            font.pixelSize: 24;

            MouseArea {
                id: mouseArea;
                anchors.fill: parent;
                onReleased: {
                    centerText.state = "redText";
                }
            }

            states: [
                State {
                    name: "redText";
                    changes:[
                        PropertyChanges {
                            target: centerText;
                            color: "red";
                        }
                    ]
                },
                State {
                    name: "blueText";
                    when: mouseArea.pressed;
                    PropertyChanges {
                        target: centerText;
                        color: "blue";
                        font.bold: true;
                        font.pixelSize: 32;
                    }
                }
            ]

            state: "redText";
        }

    }
}

  • 我给State起了个名字“redText”,当鼠标左键释放时在MouseArea的onReleased信号处理器内显示将state属性设置为“redText”状态;
  • 因为changes是列表属性,虽然redText状态只有一个PropertyChanges,还是显示地使用了[]符号,如果有多个PropertyChanges,代码可能会变成下面的样子:
State {
	name: "redText";
    changes:[
    	PropertyChanges {
        	target: centerText;
            color: "red";
        },
        PropertyChanges {
        	target: centerText;
            font.bold: true;
            font.italic: true;
        }
    ]
}
  • 两个PropertyChanges都会执行;
  • 使用when和mouseArea.pressed绑定,当按下鼠标左键时blueText会自动应用;

总结:应用一种状态有两种方式:

  • 显示改变Item的state属性;
  • 将State的when属性绑定到一个表达式上;

State对象必须与它的搭档Change对象一起使用才有实际意义;Qt Quick提供了下面几种可用于State的Change对象:

  • PropertyChanges:用来改变一个对象的属性,对应的C++类为QQuickPropertyChanges,是QQuickStateOperation的派生类;
  • ParentChange:用来改变一个对象的父,对应的C++类为QQuickParentChange,是QQuickStateOperation的派生类;
  • AnchorChanges:用来改变一个对象的锚布局参数,对应的C++类为QQuickAnchorChanges,是QQuickStateOperation的派生类;
  • StateChangeScript:用来执行一个ECMAScript脚本,对应的C++类为QQuickStateChangeScript,是QQuickStateOperation的派生类;

6.1.1、PropertyChanges

State {
	name: "blueText";
    when: mouseArea.pressed;
    PropertyChanges {
    	target: centerText;
        color: "blue";
        font.bold: true;
        font.pixelSize: 32;
    }
}
  • 这个名为“blueText”的对象内声明了一个PropertyChanges对象,用来改变centerText的某些属性;
  • PropertyChanges有一个target属性,指向要改变的目标对象(上述代码是指向id为centerText的Text对象);
  • PropertyChanges还有一个布尔值类型的restoreEntryValues属性,用于指定离开本状态时是否将本状态改变的那些属性的值重置为进入本状态之前的值;默认值为true,假设你设置这个属性为false,那么这种状态对目标对象的改变将是持久的;
  • PropertyChanges设定目标对象的属性时,可以使用静态的值,也可以使用表达式。如果使用表达式,则默认会将表达式和属性绑定;explicit可以修改这种行为,它的默认值是false,如果你把它设置为true,那么任何可能的表达式绑定都将被视为一次性的赋值行为,例如:

在这里插入图片描述

import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    id: root;
    width: 400;
    height: 400;
    visible: true;

    Rectangle {
        id: rootItem;
        width: 400;
        height: 360;
        color: "#EEEEEE";

        Rectangle {
            id: rect;
            color: "blue";
            width: 200;
            height: 200;
            anchors.centerIn: parent;

            MouseArea {
                id: mouseArea;
                anchors.fill: parent;
            }

            states: [
                State {
                    name: "resetwidth";
                    when: mouseArea.pressed;
                    PropertyChanges {
                        target: rect;
                        restoreEntryValues: false;
                        color: "red";
                        width: parent.width;
                    }
                }
            ]
        }

    }
}

  • 声明PropertyChanges对象的restoreEntryValues为false,这样“resetwidth”对目标对象的改变将是持久的;
  • 当你松开鼠标左键时,原本处在中间的矩形并不会回到原来的样子(蓝色、居中);
  • PropertyChanges对象给rect的width属性赋值parent.width,而parent.width本身是一个ECMAScript表达式,由于PropertyChanges的explicit属性默认为false,于是rect.width绑定到了parent.width这个表达式上,当改变窗口尺寸时,parent.width的返回值就会发生变化,于是rect的宽度也变了,始终与它的父Rectangle的宽度保持一致;
  • 如果给PropertyChanges对象声明添加一行代码“explicit: true”,再执行,进入“resetwidth”状态时,只是一次性地将parent.width计算出来作为一个静态的值传递给rect.width,没有发生表达式绑定行为;

6.1.2、ParentChange

ParentChange用来改变一个对象的parent,它具有下列属性:

  • target:指定要操作的目标对象;
  • parent:指定目标对象的新parent;
  • x:指定目标对象相对于新parent的x位置;
  • y:指定目标对象相对于新parent的y位置;
  • width:指定目标对象的宽度;
  • height:指定目标对象的高度;
  • rotation:指定目标对象的旋转角度;
  • scale:指定目标对象的放大系数;

上面这些属性,除了target和parent的类型是Item,其他的都是real。

在这里插入图片描述

import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    id: root;
    width: 400;
    height: 400;
    visible: true;

    Rectangle {
        id: rootItem;
        width: 400;
        height: 360;
        color: "#EEEEEE";

        Rectangle {
            id: blueRect;
            width: 200;
            height: 200;
            color: "blue";
            x: 8;
            y: 8;
        }

        Rectangle {
            id: redRect;
            width: 100;
            height: 100;
            color: "red";
            x: blueRect.x + blueRect.width + 8;
            y: blueRect.y;

            MouseArea {
                id: mouseArea;
                anchors.fill: parent;
                onClicked: {
                    if(redRect.state == "" || redRect.state == "default") {
                        redRect.state = "reparent";
                    }else {
                        redRect.state = "default";
                    }
                }
            }

            states: [
                State {
                    name: "reparent";
                    PropertyChanges {
                        target: redRect;
                        parent: blueRect;
                        width: 50;
                        height: 50;
                        x: 30;
                        y: 30;
                        rotation: 45;
                    }
                },
                State {
                    name: "default";
                    PropertyChanges {
                        target: redRect;
                        parent: rootItem;
                        width: 100;
                        height: 100;
                        x: blueRect.x + blueRect.width + 8;
                        y: blueRect.y;
                    }
                }
            ]

        }

    }
}

上述代码定义了红、蓝两个矩形,id分别为redRect和blueRect,在初始状态下,两个矩形并排,红色矩形在蓝色矩形右边。我给红色矩形定义了两个状态,即“reparent”和“default”。reparent状态修改红色矩形的父、大小、位置、旋转角度等属性;default状态则将红色矩形恢复到初始状态。

注意:对于ParentChange对象,你只能使用它定义的那几个属性,否则会报错。PropertyChanges虽然只定义了少数几个属性,但你却可以设定Item支持的大多数属性。另外,ParentChange可以做到的事情,PropertyChanges同样可以做到,例如,可以将default状态修改为下面的样子:

PropertyChanges {
	target: redRect;
	parent: rootItem;
	width: 100;
	height: 100;
	x: blueRect.x + blueRect.width + 8;
	y: blueRect.y;
	rotation: 60;
}

6.1.3、AnchorChanges

AnchorChanges用来改变一个Item的锚布局属性,它支持下列属性:

  • target:指向目标对象;
  • anchors.left;
  • anchors.right;
  • anchors.top;
  • anchors.bottom;
  • anchors.horizontalCenter;
  • anchors.verticalCenter;
  • anchors.baseline;

AnchorChanges不能改变一个Item的锚布局留白,不过你可以使用PropertyChanges来改变它们。

在这里插入图片描述

import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    id: root;
    width: 400;
    height: 400;
    visible: true;

    Rectangle {
        id: rootItem;
        width: 400;
        height: 360;
        color: "#EEEEEE";

        Rectangle {
            id: blueRect;
            width: 200;
            height: 180;
            color: "blue";
            x: 8;
            y: 8;
        }

        Rectangle {
            id: redRect;
            width: 100;
            height: 100;
            color: "red";
            anchors.leftMargin: 10;
            anchors.top: blueRect.top;

            MouseArea {
                id: mouseArea;
                anchors.fill: parent;
                onClicked: {
                    if(redRect.state == "" || redRect.state == "default") {
                        redRect.state = "reanchor";
                    }else {
                        redRect.state = "default";
                    }
                }
            }

            states: [
                State {
                    name: "reanchor";
                    changes: [
                        AnchorChanges {
                            target: redRect;
                            anchors.top: blueRect.bottom;
                            anchors.left: rootItem.left;
                        },
                        PropertyChanges {
                            target: redRect;
                            height: 40;
                            anchors.topMargin: 4;
                        }
                    ]
                },
                State {
                    name: "default";
                    AnchorChanges {
                        target: redRect;
                        anchors.left: blueRect.right;
                        anchors.top: blueRect.top;
                    }
                }
            ]
        }

    }
}

在定义“reanchor”状态时,我提供了两个Change,一个使用AnchorChanges改变红色矩形的anchors.top和anchors.left属性,一个使用PropertyChanges改变红色矩形的height和anchors.topMargin属性,以便进入reanchor状态后红色矩形还能正常显示。


6.1.4、StateChangeScript

StateChangeScript允许你在状态变化时执行ECMAScript脚本。它有两个属性:

  • 一个是name,表示脚本的名字,这个名字可以被ScriptAction对象应用,以便复用这里的脚本代码;
  • 一个是script,代表实际的脚本代码;

在这里插入图片描述

import QtQuick 2.12
import QtQuick.Window 2.12
import "colorMaker.js" as ColorMaker

Window {
    id: root;
    width: 400;
    height: 400;
    visible: true;

    Rectangle {
        id: rootItem;
        width: 400;
        height: 360;
        color: "#EEEEEE";

        Rectangle {
            id: colorRect;
            color: red;
            width: 150;
            height: 130;
            anchors.centerIn: parent;

            MouseArea {
                id: mouseArea;
                anchors.fill: parent;
            }

            states: [
                State {
                    name: "default";
                    when: mouseArea.pressed;
                    StateChangeScript {
                        name: "changeColor";
                        script: ColorMaker.changeColor(colorRect);
                    }
                }
            ]
        }

    }
}


6.2、Transition

通过前面的学习,State对Item的改变,是“瞬时的”,像这样的突变,用户体验感很不好,容易吓出心脏病来,而Transition就是消除这种突变的。

简而言之,Transition(过渡)将动画引入到两种状态之间,消除状态突变。

当一个Item从一个State切换到另一个State时,Transition定义的动画会自动在两个State之间运行,从而消除状态间的突变,使得状态迁移更加平滑。

Item的transitions属性是个列表,保存为这个Item定义的所有Transition。你可以为Item定义一个或多个Transition,只需这·样:

Item {
	...
	transitions: Transition {
		NumberAnimation{properties: "x,y"; duration: 2000;}
	}
	...
}

或者这样:

Item  {
	...
	transitions: {
		Transition {
			from: "stateA";
			to: "stateB";
			NumberAnimation{properties: "x,y"; duration: 2000;}
		},
		Transition {
			from: "stateB";
			to: "stateA";
			NumberAnimation{properties: "x,y"; duration: 2000;}
		}
	}
	...
}

Transition的enabled属性设置一个Transition是否使能,默认值为true;

Transition的from属性用来指定触发过渡的状态(的名字),其默认值为“*”,匹配所有状态;

Transition的to属性用来指定过渡的目标状态(的名字),其默认值为“*”,匹配所有状态;

如果不设置from和to属性,那么Transition就会匹配所有的状态变化,不管Item的状态从哪个变到哪个,只要Transition的enabled为true,Transition就会执行。

你可以通过设定from和to来控制一个Transition的触发条件,达到这样的效果:只有Item从状态A迁移到状态B时才执行Transition。

通过这样的设定,你就可以为Item定义多个Transition,每个Transition匹配不同的状态迁移路径。

如果你想知道一个Transition是否在运行,可以读取它的只读属性running,true代表Transition正在运行。

annimations列表属性保存为一个Transition定义的所有Animation。你可以在Transition内使用基本的Animation对象,也可以使用SequentialAnimation、ParallelAnimation等分组Animation对象。

在介绍PropertyAnimation时讲了Animation的三种定义与使用方式,在Transition中使用就是第四种方式。

reversible属性指定触发transition的条件反转时Transition是否自动翻转,默认值是false。如果没有指定Transition的from和to属性,那么多个transition是并发执行的,而且会应用到所有状态变化路径上;此时不需要设置reversible属性,因为当Item的State反转时也会触发transition。但是如果你使用了SequentialAnimation或者设置了from、to属性,那么在某些场景下你可能需要设置reversible属性才能达到预期的效果。比如你为一个按钮定义了一个Transition(先放大后变色)来响应鼠标左键按下这个动作,当鼠标左键释放时需要恢复按钮的状态,此时你可能想先变小再缩小,那你就要设置reversible属性。

当你为一个Transition定义动画时,不需要为Animation指定from和to属性。from属性默认会被设置为Item对应属性的当前值,而to属性则会被设置为目标状态内为该属性设定的目标值。当然,手动设置它们会覆盖默认值。target属性也不用指定,结合State和Transition,target是显而易见的。

Transition示例:定义一个灰色矩形,鼠标左键按下时颜色变为绿色,放大2倍,鼠标左键释放时恢复原样;

在这里插入图片描述

import QtQuick 2.12
import QtQuick.Window 2.12
import "colorMaker.js" as ColorMaker

Window {
    id: root;
    width: 400;
    height: 400;
    visible: true;

    Rectangle {
        id: rootItem;
        width: 400;
        height: 360;
        color: "#EEEEEE";

        Rectangle {
            id: rect;
            color: "gray";
            width: 300;
            height: 300;
            anchors.centerIn: parent;

            MouseArea {
                id: mouseArea;
                anchors.fill: parent;
            }

            states: [
                State {
                    name: "pressed";
                    when: mouseArea.pressed;
                    PropertyChanges {
                        target: rect;
                        color: "green";
                        scale: "2.0";
                    }
                }
            ]

            transitions: [
                Transition {
                    NumberAnimation {
                        property: "scale";
                        easing.type: Easing.InOutQuad;
                        duration: 1000;
                    }

                    ColorAnimation {
                        duration: 600;
                    }
                }
            ]

        }


    }
}
  • 定义了一个Transition对象,其内部定义了一个NumberAnimation来变换rect的scale属性,定义了一个ColorAnimation对象来变换rect的color属性;
  • Transition的animations属性是默认属性,所以在为其定义动画对象时没有使用显式的初始化语句animations.Type{}
  • 因为没有指定目标的对象声明,都会传递给默认属性;
  • 虽然这里定义的两个动画没有使用ParallelAnimation来分组,但他们依然是并行执行的;

使用SequentialAnimation来组织放大和颜色两个动画,让它们顺序执行:

在这里插入图片描述

import QtQuick 2.12
import QtQuick.Window 2.12
import "colorMaker.js" as ColorMaker

Window {
    id: root;
    width: 400;
    height: 400;
    visible: true;

    Rectangle {
        id: rootItem;
        width: 400;
        height: 360;
        color: "#EEEEEE";

        Rectangle {
            id: rect;
            color: "gray";
            width: 100;
            height: 100;
            anchors.centerIn: parent;

            MouseArea {
                id: mouseArea;
                anchors.fill: parent;
            }

            states: [
                State {
                    id: pressState;
                    name: "pressed";
                    when: mouseArea.pressed;
                    PropertyChanges {
                        target: rect;
                        color: "green";
                        scale: "2.0";
                    }
                }
            ]

            transitions: [
                Transition {
                    SequentialAnimation {
                        NumberAnimation {
                            property: "scale";
                            easing.type: Easing.InOutQuad;
                            duration: 1000;
                        }

                        ColorAnimation {
                            duration: 600;
                        }
                    }
                }
            ]

        }


    }
}
  • 鼠标左键单击rect保持不放,rect先平滑放大后逐渐变为绿色;释放左键,先平滑缩小再逐渐变为灰色。

现在要实现这样的效果:按住左键时,先变大后变色,释放左键时,先变色后缩小。这就要用到Transition的reversible属性和to属性;

在这里插入图片描述

import QtQuick 2.12
import QtQuick.Window 2.12
import "colorMaker.js" as ColorMaker

Window {
    id: root;
    width: 400;
    height: 400;
    visible: true;

    Rectangle {
        id: rootItem;
        width: 400;
        height: 360;
        color: "#EEEEEE";

        Rectangle {
            id: rect;
            color: "gray";
            width: 100;
            height: 100;
            anchors.centerIn: parent;

            MouseArea {
                id: mouseArea;
                anchors.fill: parent;
            }

            states: [
                State {
                    id: pressState;
                    name: "pressed";
                    when: mouseArea.pressed;
                    PropertyChanges {
                        target: rect;
                        color: "green";
                        scale: "2.0";
                    }
                }
            ]

            transitions: [
                Transition {
                    to: "pressed"
                    reversible: true;
                    SequentialAnimation {
                        NumberAnimation {
                            property: "scale";
                            easing.type: Easing.InOutQuad;
                            duration: 1000;
                        }

                        ColorAnimation {
                            duration: 600;
                        }
                    }
                }
            ]

        }


    }
}

Transition的各个属性的用法,基本都演示过了,现在我们来设计一个新的示例。有的网页,其中的链接未点击时是蓝色,鼠标悬停在链接上时浮出下划线并且颜色变淡,鼠标点击后下划线固定在那里且文本颜色变为棕色。如下所示:

在这里插入图片描述

import QtQuick 2.12
import QtQuick.Window 2.12
import "colorMaker.js" as ColorMaker

Window {
    id: root;
    width: 400;
    height: 400;
    visible: true;

    Rectangle {
        id: rootItem;
        width: 400;
        height: 360;
        color: "#EEEEEE";

        Text {
            id: linkText;
            text: qsTr("I\'m web link.");
            anchors.centerIn: parent;
            font.pixelSize: 24;
            property var hadClicked: false;

            MouseArea {
                id: mouseArea;
                anchors.fill: parent;
                hoverEnabled: true;

                onEntered: {
                    linkText.state = linkText.hadClicked == true ? "clickedHover" : "hover";
                }
                onExited: {
                    linkText.state = linkText.hadClicked == true ? "clicked" : "initial";
                }
                onClicked: {
                    if(linkText.hadClicked == false) {
                        linkText.hadClicked == true;
                    }
                    linkText.state = "clicked";
                }
            }

            states: [
                State {
                    name: "initial";
                    changes: [
                        PropertyChanges {
                            target: linkText;
                            color: "blue";
                        }
                    ]
                },
                State {
                    name: "hover"
                    PropertyChanges {
                        target: linkText;
                        color: "#87CEFA";
                        font {
                            italic: true;
                            pixelSize: 36;
                            underline: true;
                        }
                    }
                },
                State {
                    name: "clicked"
                    PropertyChanges {
                        target: linkText;
                        color: "#8B4513";
                        font {
                            pixelSize: 24;
                        }
                    }
                },
                State {
                    name: "clickedHover";
                    PropertyChanges {
                        target: linkText;
                        color: "#D2691E";
                        font {
                            italic: true;
                            pixelSize: 36;
                            underline: true;
                        }
                    }
                }
            ]

            state: "initial";

            transitions: [
                Transition {
                    from: "initial";
                    to: "hover";
                    reversible: true;
                    NumberAnimation {
                        property: "font.pixelSize";
                        duration: 800;
                    }

                    ColorAnimation {
                        duration: 800;
                    }
                },
                Transition {
                    from: "hover";
                    to: "clicked";
                    NumberAnimation {
                        property: "font.pixelSize";
                        duration: 800;
                    }

                    ColorAnimation {
                        duration: 800;
                    }
                },
                Transition {
                    from: "clicked";
                    to: "clickedHover";
                    reversible: true;
                    SequentialAnimation {
                        NumberAnimation {
                            property: "font.pixelSize";
                            duration: 800;
                        }

                        ColorAnimation {
                            duration: 800;
                        }
                    }
                }
            ]

        }


    }
}

我设置了4种状态:

  • initial:初始状态,文本颜色为蓝色;
  • hover:未点击时鼠标进入,文本变为浅蓝色,字体放大、倾斜、加下划线;
  • clicked:点击后颜色变为马鞍棕色,字体恢复初始大小,没有下划线,不倾斜;
  • clickedHover:点击之后鼠标进入,文本变为巧克力色,字体放大、倾斜、加下划线;

有5条状态迁移路线:

  • initial与hover的互相转换:对应的Transition对象的from为“initial”,to为“hover”,reversible为true。虽然这里我们没有使用SequentialAnimation,但依然设置reversible为true,其目的是为了匹配hover到Initial的状态迁移,如果不设置,当你移开鼠标时文本大小、颜色就会刷地跳变一下。
  • clicked与clickedHover的互相转换:对应的Transition对象的from为“clicked”,to为“clickedHover”,reversible为true。为了演示reversible对SequentialAnimation的影响,这里使用SequentialAnimation组合NumberAnimation和ColorAnimation。
  • hover转换为clicked:对应的Transition对象的from为“hover”,to为“clicked”,未设置reversible,默认为false;

MouseArea对象的hoverEnabled属性设置为true,处理鼠标经过的事件,使entered、exited信号生效、然后在onEntered、onExited、onClicked三个信号处理器中显示地给linkText.state赋值来改变状态触发Transition。


七、协同动画元素

除了基本的动画元素和分组动画元素,还有一些需要和其他的动画辅助类(如Behavior、Transition)或者动画类结合使用才更有实际意义的动画元素,称之为协同动画元素:

  • Behavior:用于给Item的某个属性绑定默认动画;
  • ParentAnimation、AnchorAnimation通常需要和Transition、State联合使用;
  • PauseAnimation:可以插入在多个动画之间产生暂停效果;
  • PropertyAction:可以插入在多个动画之间来立即改变某个属性;
  • ScriptAction:用于在动画执行过程中运行一段ECMAScript脚本;

7.1、Behavior

Behavior用来给一个property定义默认动画,当该property变化时执行该动画。一个property只能绑定一个Behavior,一个Behavior内只能有一个顶层动画(因为其animation属性的类型是Animation而非list<Animation>),如果你想在一个property变化时执行多个动画,则可以使用ParallelAnimation或SequentialAnimation。

如果你给Item定义了State,而State变化时触发了Transition,Transition要改变的property上绑定了Behavior,那么Transition会覆盖Behavior;

我们在介绍PropertyAnimation时已经讲了Animation的三种定义与使用方式,在介绍Transition时讲了第四种方式,而在Behavior中使用,是第五种方式。至此位置,五种使用动画的方式就全覆盖了。

在这里插入图片描述

import QtQuick 2.12
import QtQuick.Window 2.12
import "colorMaker.js" as ColorMaker

Window {
    id: root;
    width: 400;
    height: 400;
    visible: true;

    Rectangle {
        id: rootItem;
        width: 400;
        height: 360;
        color: "#EEEEEE";

        Rectangle {
            id: rect;
            width: 160;
            height: 100;
            color: "red";
            anchors.centerIn: parent;

            Behavior on width {
                NumberAnimation {
                    duration: 1000;
                }
            }

            Behavior on height {
                NumberAnimation {
                    duration: 1000;
                    easing.type: Easing.InCubic;
                }
            }

            MouseArea {
                anchors.fill: parent;
                onClicked: {
                    rect.width = Math.random() * rootItem.width;
                    rect.height = Math.min(Math.random() * rootItem.height,rect.height * 1.5);
                }
            }

        }

    }
}

上面的代码使用Behavior给rect的width、height属性各自绑定了一个NumberAnimation动画。在MouseArea的onClicked信号处理器中随机改变rect的width和height,动画随之执行。

使用Behavior定义动画时,动画对象不需要设置target、property、from、to等属性,非常方便。因为animation是Behavior的默认属性,也不需要显示地初始化,只要直接在Behavior内声明动画对象即可。


7.2、ParentAnimation

ParentAnimation在改变一个Item的parent时使用,使得该Item从旧parent移动到新parent的过程更平滑。它是Animation的派生类,其newParent属性用来指定目标对象的新parent;target属性指定目标对象;via属性指定动画过程中参考的其他对象。比如要改变parent的Item,其旧父、新父都可能被别的Item遮住,此时移动过程可能是不可见的,而给via设置一个Z序最大、处于顶层的Item,就可以确保动画过程可见。

ParentAnimation可以包含一个或多个其他的动画对象,这些动画会并发执行。如果不包含其他动画,ParentAnimation体现不出来任何效果。

在这里插入图片描述

import QtQuick 2.12
import QtQuick.Window 2.12
import "colorMaker.js" as ColorMaker

Window {
    id: root;
    width: 400;
    height: 400;
    visible: true;

    Rectangle {
        id: rootItem;
        width: 400;
        height: 360;
        color: "#EEEEEE";

        Rectangle {
            id: blueRect;
            width: 200;
            height: 200;
            color: "blue";
            x: 8;
            y: 8;
        }

        Rectangle {
            id: redRect;
            color: "red";
            state: "default";

            MouseArea {
                id: mouseArea;
                anchors.fill: parent;
                onClicked: {
                    if(redRect.state == "" || redRect.state == "default") {
                        redRect.state = "reparent";
                    }else {
                        redRect.state = "default";
                    }
                }
            }

            states: [
                State {
                    name: "reparent";
                    PropertyChanges {
                        target: redRect;
                        parent: blueRect;
                        width: 50;
                        height: 50;
                        x: 30;
                        y: 30;
                        rotation: 45;
                    }
                },
                State {
                    name: "default";
                    ParentChange {
                        target: redRect;
                        parent: rootItem;
                        width: 100;
                        height: 100;
                        x: blueRect.x + blueRect.width + 8;
                        y: blueRect.y;
                    }
                }
            ]

            transitions: [
                Transition {
                    ParentAnimation {
                        NumberAnimation {
                            property: "x,y";
                            duration: 1000;
                        }
                    }
                }
            ]
        }

    }
}

7.3、AnchorAnimation

AnchorAnimation只能与Transition、AnchorChanges联合使用,不能在Behavior或其他的动画元素中使用。

可以设定duration、easing及targets属性,不过与Transition结合使用,一般不必设置targets属性。

在这里插入图片描述

import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    id: root;
    width: 400;
    height: 400;
    visible: true;

    Rectangle {
        id: rootItem;
        width: 400;
        height: 360;
        color: "#EEEEEE";

        Rectangle {
            id: blueRect;
            width: 200;
            height: 180;
            color: "blue";
            x: 8;
            y: 8;
        }

        Rectangle {
            id: redRect;
            width: 100;
            height: 100;
            color: "red";
            anchors.leftMargin: 10;
            anchors.top: blueRect.top;

            MouseArea {
                id: mouseArea;
                anchors.fill: parent;
                onClicked: {
                    if(redRect.state == "" || redRect.state == "default") {
                        redRect.state = "reanchor";
                    }else {
                        redRect.state = "default";
                    }
                }
            }

            states: [
                State {
                    name: "reanchor";
                    changes: [
                        AnchorChanges {
                            target: redRect;
                            anchors.top: blueRect.bottom;
                            anchors.left: rootItem.left;
                        },
                        PropertyChanges {
                            target: redRect;
                            height: 40;
                            anchors.topMargin: 4;
                        }
                    ]
                },
                State {
                    name: "default";
                    AnchorChanges {
                        target: redRect;
                        anchors.left: blueRect.right;
                        anchors.top: blueRect.top;
                    }
                }
            ]

            state: "default";

            transitions: [
                Transition {
                    AnchorAnimation {
                        duration: 1000;
                        easing.type: Easing.OutInCubic;
                    }
                }
            ]
        }

    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

贝勒里恩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值